diff --git a/electron/ipc/recording/diagnostics.test.ts b/electron/ipc/recording/diagnostics.test.ts index 9f7ddeb0..a0092a60 100644 --- a/electron/ipc/recording/diagnostics.test.ts +++ b/electron/ipc/recording/diagnostics.test.ts @@ -134,6 +134,42 @@ describe("getCompanionAudioFallbackPaths", () => { ]); }); + it("returns both mac sidecars instead of the video when a system sidecar exists", async () => { + const videoPath = path.join(tempRoot, "recording.mp4"); + const systemPath = path.join(tempRoot, "recording.system.m4a"); + const micPath = path.join(tempRoot, "recording.mic.m4a"); + + await Promise.all([ + fs.writeFile(videoPath, "video"), + fs.writeFile(systemPath, "system"), + fs.writeFile(micPath, "mic"), + ]); + + execFileMock.mockImplementation( + ( + _file: string, + _args: string[], + _options: Record, + callback: ExecFileCallback, + ) => { + const error = new Error("ffmpeg probe found embedded audio") as Error & { + stderr?: string; + }; + error.stderr = "Stream #0:1: Audio: aac"; + callback(error, "", error.stderr); + }, + ); + + const { getCompanionAudioFallbackPaths } = await import("./diagnostics"); + + // The inline mp4 track holds system audio only, so returning [videoPath] + // here silently dropped the microphone. + await expect(getCompanionAudioFallbackPaths(videoPath)).resolves.toEqual([ + systemPath, + micPath, + ]); + }); + it("prefers the mac mic companion alone when embedded audio already exists and no system sidecar is present", async () => { const videoPath = path.join(tempRoot, "recording.mp4"); const micPath = path.join(tempRoot, "recording.mic.m4a"); diff --git a/electron/ipc/recording/diagnostics.ts b/electron/ipc/recording/diagnostics.ts index 985209f2..7cc8d06e 100644 --- a/electron/ipc/recording/diagnostics.ts +++ b/electron/ipc/recording/diagnostics.ts @@ -524,7 +524,17 @@ export async function getCompanionAudioFallbackInfo(videoPath: string) { if (!hasUsableMacSystemCompanion && usableMacMicOnlyCompanions.length > 0) { paths = usableMacMicOnlyCompanions; } else if (hasUsableMacSystemCompanion) { - paths = [videoPath]; + // The inline mp4 audio track carries system audio only (the helper skips + // the microphone while system audio is captured), so returning the video + // alone drops the mic entirely. Hand over both mac sidecars instead and + // let the renderer route them as independent system/mic tracks. + paths = Array.from( + new Set( + companionCandidates.flatMap((candidate) => + candidate.platform === "mac" ? candidate.usablePaths : [], + ), + ), + ); } else { const companionPaths = Array.from( new Set(