fix: keep mic audio when system audio is enabled

getCompanionAudioFallbackInfo returned only the video path when a macOS
system sidecar existed, assuming the inline mp4 track was a complete mix.
The capture helper writes system audio alone to that track, so the
microphone was dropped from preview and export whenever both sources were
enabled — the recorded narration was silent even though it had been
captured correctly to recording-<ts>.mic.m4a.

Return both macOS sidecars instead, so the renderer routes them as
separate system and mic tracks and mutes the silent embedded track.
Existing recordings recover their audio on reopen.

Fixes webadderallorg/Recordly#912
This commit is contained in:
Abhijeet Dash
2026-09-09 17:12:13 +05:30
parent 4b20a1a76e
commit 1c2df9325e
2 changed files with 47 additions and 1 deletions
@@ -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<string, unknown>,
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");
+11 -1
View File
@@ -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(