From cbef8e4e8f5b3cdf7a8b53ff1dd08af61e0dba4b Mon Sep 17 00:00:00 2001 From: josiahcoad Date: Sun, 12 Jul 2026 14:07:35 -0500 Subject: [PATCH 1/2] fix(recording): start webcam recorder after native macOS capture begins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the native macOS path the webcam MediaRecorder was started while the ScreenCaptureKit helper was still spinning up (permission checks, stream construction, first-frame latency — ~1s). The webcam sidecar therefore contained ~1s of pre-roll footage from before the screen/mic recording began, and since the editor and exporter align both files at t=0, the webcam PiP lagged the voice/screen by that startup latency in both preview and export. Start the webcam recorder only after startNativeMacRecording resolves, which happens when the helper emits recording-started (first video frame written), so both recordings share the same time origin. Co-Authored-By: Claude Fable 5 --- src/hooks/useScreenRecorder.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index 72ddc529b..ae2cfd64d 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -961,20 +961,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 = { @@ -1021,19 +1013,25 @@ 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) { + nativeWebcamRecorder = createRecorderHandle(webcamStream.current, { + mimeType: selectMimeType(), + videoBitsPerSecond: BITRATE_BASE, + }); + } + recordingId.current = result.recordingId; nativeMacRecording.current = { recordingId: result.recordingId, From a04e6bccd0487555c2eaf76e26db56ce215fa876 Mon Sep 17 00:00:00 2001 From: Etienne Lescot Date: Wed, 15 Jul 2026 10:03:56 +0200 Subject: [PATCH 2/2] fix(recording): stop native recording if late webcam recorder creation fails The webcam RecorderHandle is now created after startNativeMacRecording resolves, so the native helper is already recording when createRecorderHandle runs. If it throws (e.g. MediaRecorder rejects the mime type / stream), the surrounding catch only logs and rethrows, leaving the helper recording with no handle to finalize it. Stop the native recording before rethrowing so a failed webcam sidecar tears down the whole capture cleanly, as it did when creation ran earlier. Co-Authored-By: Claude Opus 4.8 --- src/hooks/useScreenRecorder.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index ae2cfd64d..9c8092b1f 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -1026,10 +1026,18 @@ export function useScreenRecorder(): UseScreenRecorderReturn { // (~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) { - nativeWebcamRecorder = createRecorderHandle(webcamStream.current, { - mimeType: selectMimeType(), - videoBitsPerSecond: BITRATE_BASE, - }); + 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;