From b497142b6df171802791e17702bf2468eec14da3 Mon Sep 17 00:00:00 2001 From: Jonny Burger Date: Tue, 17 Mar 2026 16:20:07 +0100 Subject: [PATCH] `template-recorder`: Stop recording when tab sharing ends Co-Authored-By: Claude Opus 4.6 (1M context) --- .../template-recorder/src/RecordButton.tsx | 26 ++++++++++++++++++- .../src/helpers/start-media-recorder.ts | 26 ++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/template-recorder/src/RecordButton.tsx b/packages/template-recorder/src/RecordButton.tsx index 51317857046..ec579599f7e 100644 --- a/packages/template-recorder/src/RecordButton.tsx +++ b/packages/template-recorder/src/RecordButton.tsx @@ -127,11 +127,35 @@ export const RecordButton: React.FC<{ }); }, [recordingStatus, setRecordingStatus]); + useEffect(() => { + if (recordingStatus.type !== "recording") { + return; + } + + const controller = new AbortController(); + + for (const rec of recordingStatus.ongoing.recorders) { + for (const track of rec.recorder.stream.getTracks()) { + track.addEventListener( + "ended", + () => { + onStop(); + }, + { once: true, signal: controller.signal }, + ); + } + } + + return () => { + controller.abort(); + }; + }, [recordingStatus, onStop]); + useEffect(() => { return () => { if (recordingStatus.type === "recording") { recordingStatus.ongoing.recorders.forEach((r) => { - r.recorder.stop(); + r.stopAndWaitUntilDone(); }); } }; diff --git a/packages/template-recorder/src/helpers/start-media-recorder.ts b/packages/template-recorder/src/helpers/start-media-recorder.ts index c020ff3c650..56a44d69e31 100644 --- a/packages/template-recorder/src/helpers/start-media-recorder.ts +++ b/packages/template-recorder/src/helpers/start-media-recorder.ts @@ -67,13 +67,19 @@ export const startMediaRecorder = async ({ }, ); - const stopAndWaitUntilDone = () => { + let stoppedPromise: Promise | null = null; + + const stopRecorder = (): Promise => { + if (stoppedPromise) { + return stoppedPromise; + } + periodicSaveController.abort(); const { resolve, reject, promise } = Promise.withResolvers(); + stoppedPromise = promise; const controller = new AbortController(); - recorder.stop(); recorder.addEventListener( "error", (event) => { @@ -112,13 +118,27 @@ export const startMediaRecorder = async ({ }, ); + recorder.stop(); + promise.finally(() => controller.abort()); return promise; }; + // Stop the recorder immediately when any track ends to prevent + // corrupt timestamps in the output file + for (const track of source.stream.getTracks()) { + track.addEventListener( + "ended", + () => { + stopRecorder(); + }, + { once: true }, + ); + } + // Trigger a save every 10 seconds recorder.start(10_000); - return { recorder, stopAndWaitUntilDone, mimeType }; + return { recorder, stopAndWaitUntilDone: stopRecorder, mimeType }; };