fix: avoid duplicate mic audio in native mac recordings

This commit is contained in:
webadderall
2026-06-02 10:24:50 +10:00
parent 1f9912bef6
commit 0c690bd5c2
5 changed files with 55 additions and 11 deletions
@@ -134,6 +134,32 @@ describe("getCompanionAudioFallbackPaths", () => {
]);
});
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");
await Promise.all([fs.writeFile(videoPath, "video"), 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");
await expect(getCompanionAudioFallbackPaths(videoPath)).resolves.toEqual([micPath]);
});
it("loads saved sidecar timing metadata alongside companion audio paths", async () => {
const videoPath = path.join(tempRoot, "recording.mp4");
const micPath = path.join(tempRoot, "recording.mic.webm");
+29 -8
View File
@@ -508,20 +508,41 @@ export async function getCompanionAudioFallbackInfo(videoPath: string) {
let paths: string[];
if (await hasEmbeddedAudioStream(videoPath)) {
const companionPaths = Array.from(
const hasUsableMacSystemCompanion = companionCandidates.some(
(candidate) =>
candidate.platform === "mac" &&
candidate.usablePaths.includes(candidate.systemPath),
);
const usableMacMicOnlyCompanions = Array.from(
new Set(
companionCandidates.flatMap((candidate) =>
candidate.usablePaths.filter(
(companionPath) => companionPath === candidate.micPath,
),
candidate.platform === "mac" &&
!candidate.usablePaths.includes(candidate.systemPath) &&
candidate.usablePaths.includes(candidate.micPath)
? [candidate.micPath]
: [],
),
),
);
if (companionPaths.length === 0) {
return { paths: [], startDelayMsByPath: {} };
}
paths = [videoPath, ...companionPaths];
if (!hasUsableMacSystemCompanion && usableMacMicOnlyCompanions.length > 0) {
paths = usableMacMicOnlyCompanions;
} else {
const companionPaths = Array.from(
new Set(
companionCandidates.flatMap((candidate) =>
candidate.usablePaths.filter(
(companionPath) => companionPath === candidate.micPath,
),
),
),
);
if (companionPaths.length === 0) {
return { paths: [], startDelayMsByPath: {} };
}
paths = [videoPath, ...companionPaths];
}
} else {
paths = Array.from(
new Set(companionCandidates.flatMap((candidate) => candidate.usablePaths)),
@@ -77,9 +77,6 @@ final class ScreenCaptureRecorder: NSObject, SCStreamOutput, SCStreamDelegate {
}
writesSystemAudioToSeparateTrack = capturesSystemAudio
writesMicrophoneToSeparateTrack = capturesSystemAudio && capturesMicrophone
if capturesMicrophone && !capturesSystemAudio {
writesMicrophoneToSeparateTrack = true
}
let requestedFPS = max(targetCaptureFPS, config.fps ?? targetCaptureFPS)
streamConfig.minimumFrameInterval = CMTime(value: 1, timescale: CMTimeScale(requestedFPS))
streamConfig.queueDepth = 6