diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 8eb97c23c..af95b4722 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -974,20 +974,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn { if (!isCountdownRunActive(countdownRunToken)) { return true; } - if (webcamStream.current) { - nativeWebcamRecorder = createRecorderHandle(webcamStream.current, { - mimeType: selectMimeType(), - videoBitsPerSecond: BITRATE_BASE, - }); - } else { + if (!webcamStream.current) { webcamAcquireId.current++; setWebcamEnabledState(false); } } if (!isCountdownRunActive(countdownRunToken)) { - if (nativeWebcamRecorder && nativeWebcamRecorder.recorder.state !== "inactive") { - nativeWebcamRecorder.recorder.stop(); - } return true; } const request: NativeMacRecordingRequest = { @@ -1034,19 +1026,33 @@ export function useScreenRecorder(): UseScreenRecorderReturn { }; const result = await window.electronAPI.startNativeMacRecording(request); if (!result.success || !result.recordingId) { - if (nativeWebcamRecorder && nativeWebcamRecorder.recorder.state !== "inactive") { - nativeWebcamRecorder.recorder.stop(); - } throw new Error(result.error ?? "Native macOS capture failed."); } if (!isCountdownRunActive(countdownRunToken)) { - if (nativeWebcamRecorder && nativeWebcamRecorder.recorder.state !== "inactive") { - nativeWebcamRecorder.recorder.stop(); - } await window.electronAPI.stopNativeMacRecording(true); return true; } + // Start the webcam recorder only after the helper has emitted + // recording-started (startNativeMacRecording resolves on that event). + // Starting it earlier bakes the helper's capture startup latency + // (~1s of pre-roll) into the webcam file, desyncing it from the + // screen/mic recording that the editor and exporter align at t=0. + if (webcamEnabled && webcamStream.current) { + try { + nativeWebcamRecorder = createRecorderHandle(webcamStream.current, { + mimeType: selectMimeType(), + videoBitsPerSecond: BITRATE_BASE, + }); + } catch (webcamError) { + // The native helper is already recording at this point; stop it before + // surfacing the error so we don't leave it running with no handle to + // finalize it (this creation moved after startNativeMacRecording). + await window.electronAPI.stopNativeMacRecording(true); + throw webcamError; + } + } + recordingId.current = result.recordingId; nativeMacRecording.current = { recordingId: result.recordingId,