Merge pull request #722 from mvanhorn/fix/642-preview-audio-drops

fix: preview source audio stops around 20s due to unreliable reported duration
This commit is contained in:
ExtraBinoss
2026-07-02 22:43:53 +02:00
committed by GitHub
3 changed files with 68 additions and 1 deletions
@@ -6,6 +6,7 @@ import {
enablePitchPreservingPlayback,
estimateCompanionAudioStartDelaySeconds,
getMediaSyncPlaybackRate,
resolvePreviewMediaDuration,
} from "@/lib/mediaTiming";
import type { AudioRegion, SpeedRegion } from "../types";
@@ -381,7 +382,7 @@ export function useAudioPreviewSync({
audio.volume = Math.max(0, Math.min(1, getSourceTrackPreviewGain(sourceAudioPath) * (isCurrentClipMuted ? 0 : previewVolume)));
enablePitchPreservingPlayback(audio);
const audioDuration = Number.isFinite(audio.duration) ? audio.duration : null;
const audioDuration = resolvePreviewMediaDuration(audio.duration, duration);
const isMicCompanionTrack = /\.mic\./i.test(sourceAudioPath);
const rawStartDelaySeconds = estimateCompanionAudioStartDelaySeconds(
duration,
+25
View File
@@ -7,6 +7,7 @@ import {
getEffectiveRecordingDurationMs,
getEffectiveVideoStreamDurationSeconds,
getMediaSyncPlaybackRate,
resolvePreviewMediaDuration,
} from "./mediaTiming";
describe("clampMediaTimeToDuration", () => {
@@ -21,6 +22,30 @@ describe("clampMediaTimeToDuration", () => {
});
});
describe("resolvePreviewMediaDuration", () => {
it("keeps reported durations that approximately match the timeline", () => {
expect(resolvePreviewMediaDuration(120, 120)).toBe(120);
expect(resolvePreviewMediaDuration(119.25, 120)).toBe(119.25);
});
it("treats non-finite or missing reported durations as unknown", () => {
expect(resolvePreviewMediaDuration(Number.POSITIVE_INFINITY, 120)).toBeNull();
expect(resolvePreviewMediaDuration(Number.NaN, 120)).toBeNull();
expect(resolvePreviewMediaDuration(null, 120)).toBeNull();
});
it("treats implausibly short reported durations as unknown", () => {
expect(resolvePreviewMediaDuration(20, 120)).toBeNull();
expect(resolvePreviewMediaDuration(0, 120)).toBeNull();
});
it("falls back to the reported duration when the timeline duration is unknown", () => {
expect(resolvePreviewMediaDuration(20, Number.NaN)).toBe(20);
expect(resolvePreviewMediaDuration(20, null)).toBe(20);
expect(resolvePreviewMediaDuration(Number.NaN, Number.NaN)).toBeNull();
});
});
describe("estimateCompanionAudioStartDelaySeconds", () => {
it("keeps small inferred offsets when the companion audio is only slightly shorter", () => {
expect(estimateCompanionAudioStartDelaySeconds(10, 9.6)).toBeCloseTo(0.4);
+41
View File
@@ -7,6 +7,47 @@ export function clampMediaTimeToDuration(targetTime: number, duration?: number |
return Math.max(0, Math.min(safeTargetTime, Math.max(0, duration)));
}
const MIN_PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_SECONDS = 0.5;
const MAX_PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_SECONDS = 1;
const PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_RATIO = 0.01;
export function resolvePreviewMediaDuration(
reportedDuration?: number | null,
timelineDuration?: number | null,
): number | null {
if (!Number.isFinite(reportedDuration)) {
return null;
}
const safeReportedDuration = Math.max(0, reportedDuration ?? 0);
if (
!Number.isFinite(timelineDuration) ||
timelineDuration === null ||
timelineDuration === undefined
) {
return safeReportedDuration;
}
const safeTimelineDuration = Math.max(0, timelineDuration);
if (safeTimelineDuration === 0) {
return safeReportedDuration;
}
const shortfallToleranceSeconds = Math.min(
MAX_PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_SECONDS,
Math.max(
MIN_PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_SECONDS,
safeTimelineDuration * PREVIEW_MEDIA_DURATION_SHORTFALL_TOLERANCE_RATIO,
),
);
if (safeTimelineDuration - safeReportedDuration > shortfallToleranceSeconds) {
return null;
}
return safeReportedDuration;
}
const MIN_COMPANION_AUDIO_DELAY_SECONDS = 0.025;
const MAX_INFERRED_COMPANION_AUDIO_DELAY_SECONDS = 0.5;