Fix: stop mic fallback recorder when cancelling native recording

Cancelling a native-capture recording (clicking the "X"/cancel button)
never stopped the mic fallback recorder's getUserMedia stream. The
stale mic track stayed open, so starting the next recording opened a
second concurrent mic stream and produced silent/dead audio until the
user manually reselected the microphone.

cancelRecording's native-capture branch now stops the mic fallback
recorder alongside discarding the native capture, mirroring the
cleanup stopRecording already performs.

Fixes #699

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Som Samantray
2026-09-20 08:11:06 +05:30
co-authored by Claude Sonnet 5
parent 49926862f8
commit aafa44014c
2 changed files with 27 additions and 2 deletions
+19
View File
@@ -397,6 +397,7 @@ function cancelRecording(
chunks: { current: Blob[] },
webcamRecorder?: ReturnType<typeof createMockMediaRecorder> | null,
webcamChunks?: { current: Blob[] },
stopMicFallbackRecorder?: () => Promise<Blob | null>,
) {
if (webcamChunks) webcamChunks.current = [];
if (webcamRecorder && webcamRecorder.state !== "inactive") {
@@ -404,6 +405,7 @@ function cancelRecording(
}
if (isNativeRecording) {
void stopMicFallbackRecorder?.();
return { cancelled: true, wasNative: true };
}
@@ -740,6 +742,23 @@ describe("useScreenRecorder state machine", () => {
expect(recorder.stop).not.toHaveBeenCalled();
});
it("stops the mic fallback recorder when cancelling native recording", () => {
const chunks = { current: [] as Blob[] };
const stopMicFallbackRecorder = vi.fn(() => Promise.resolve(null));
const result = cancelRecording(
recorder,
true,
chunks,
null,
undefined,
stopMicFallbackRecorder,
);
expect(result.wasNative).toBe(true);
expect(stopMicFallbackRecorder).toHaveBeenCalled();
});
it("handles cancel when recorder is already inactive", () => {
const inactiveRecorder = createMockMediaRecorder("inactive");
const chunks = { current: [new Blob(["data"])] };
+8 -2
View File
@@ -2394,7 +2394,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
setRecording(false);
window.electronAPI?.setRecordingState(false);
void (async () => {
await discardActiveNativeCapture();
await Promise.allSettled([discardActiveNativeCapture(), stopMicFallbackRecorder()]);
})();
return;
}
@@ -2408,7 +2408,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
setRecording(false);
window.electronAPI?.setRecordingState(false);
}
}, [cleanupCapturedMedia, discardActiveNativeCapture, markRecordingResumed, recording]);
}, [
cleanupCapturedMedia,
discardActiveNativeCapture,
markRecordingResumed,
recording,
stopMicFallbackRecorder,
]);
const toggleRecording = async () => {
if (starting || countdownActive || finalizing) {