{"record":{"id":"4fb913ead503f560","repo":"alyssaxuu/screenity","slug":"camera-track-has-ended","errorCode":null,"errorMessage":"Camera track has ended","messagePattern":"Camera track has ended","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/pages/CloudRecorder/CloudRecorder.jsx","lineNumber":4234,"sourceCode":"              camera: offset,\n              ts: now,\n            };\n            patchTrackState(\"camera\", {\n              lastUploadOffset: offset,\n              uploaderUpdatedAt: now,\n            });\n            if (now - (lastUploadProgress.current.lastPersistAt || 0) > 2000) {\n              lastUploadProgress.current.lastPersistAt = now;\n              persistSessionState();\n            }\n          },\n          onStall: onStall(\"camera\"),\n          onTelemetry: onUploaderTelemetry(\"camera\"),\n          onStateChange: onUploaderStateChange(\"camera\"),\n        });\n        const track = cameraStream.current.getVideoTracks()[0];\n        if (track?.readyState === \"ended\") {\n          throw new Error(\"Camera track has ended\");\n        }\n        let { width, height } = track.getSettings();\n        if (encoderKinds.camera === \"webcodecs\") {\n          const downscaled = computeEncodedDimensions({ width, height });\n          if (downscaled.width > 0 && downscaled.height > 0) {\n            width = downscaled.width;\n            height = downscaled.height;\n          }\n        }\n        // Omit linkedMediaId; server groups partners by shared sceneId\n        // in usedIn now. Lifts the serial wait on screen's mediaId\n        // (~3s dev, 500ms-1s prod).\n        cameraInitOpts = {\n          title: \"Camera Recording\",\n          type: \"camera\",\n          linkedMediaId: null,\n          width,\n          height,","sourceCodeStart":4216,"sourceCodeEnd":4252,"githubUrl":"https://github.com/alyssaxuu/screenity/blob/512606387b8d07dda5e63bb428bd063f0a2a3ed0/src/pages/CloudRecorder/CloudRecorder.jsx#L4216-L4252","documentation":"After setting up the camera uploader, CloudRecorder grabs the first video track from the stored camera MediaStream and throws if its readyState is already 'ended'. A video track ends when its source device is disconnected, revoked, or stopped, so recording cannot proceed. This guard prevents starting an encoder against a dead track.","triggerScenarios":"The webcam is unplugged or the USB hub drops between getUserMedia and uploader start; the user revokes camera permission via the browser's site controls; another application or code path calls track.stop() on the camera stream before this point.","commonSituations":"Laptop dock/undock during recording setup; user switches cameras in OS settings mid-flow; camera used exclusively by another app (Zoom/Teams) causing device release; long stalls before start where the OS turned the camera off.","solutions":["Retry getUserMedia to obtain a fresh camera stream, then rebuild the uploader with the new track.","Listen for track 'ended' events on cameraStream.current and prompt the user to reconnect/select a camera before starting the recording.","Check navigator.mediaDevices.enumerateDevices() for an available videoinput and guide the user to reselect it in device settings.","Stop and clean up the current recorder session (cancel encoders/uploads) so a stale stream is not reused on retry.","Avoid calling track.stop() on the camera stream anywhere before recording starts; audit shared references to the stream."],"exampleFix":"// before\nconst track = cameraStream.current.getVideoTracks()[0];\nif (track?.readyState === \"ended\") {\n  throw new Error(\"Camera track has ended\");\n}\n// after\nlet track = cameraStream.current.getVideoTracks()[0];\nif (!track || track.readyState === \"ended\") {\n  cameraStream.current = await navigator.mediaDevices.getUserMedia({ video: true });\n  track = cameraStream.current.getVideoTracks()[0];\n}","handlingStrategy":"type-guard","validationCode":"const track = cameraStream.current?.getVideoTracks()[0];\nif (!track || track.readyState !== \"live\") {\n  cameraStream.current = await navigator.mediaDevices.getUserMedia({ video: true });\n}","typeGuard":"function hasLiveCameraTrack(stream) {\n  const track = stream?.getVideoTracks()[0];\n  return !!track && track.readyState === \"live\";\n}","tryCatchPattern":"try {\n  startCameraUploader();\n} catch (err) {\n  if (err.message === \"Camera track has ended\") {\n    await reacquireCameraStream();\n    startCameraUploader();\n  } else {\n    throw err;\n  }\n}","preventionTips":["Subscribe to track 'ended' events and prompt reconnection immediately","Enumerate videoinput devices before starting and verify camera availability","Never call track.stop() on the camera stream before recording starts","Re-acquire the stream after devicechange events"],"tags":["webrtc","media-stream","camera","device","cloud-recorder"],"backgroundTag":"media-track-ended","analyzedSha":"512606387b8d07dda5e63bb428bd063f0a2a3ed0","analyzedAt":"2026-09-02T20:59:09.419Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}