Files
openscreen/src/lib/exporter/streamingDecoder.test.ts
T
Enriquefft 5e62ad3215 fix: validate export duration and fix audio trim in speed-aware path
Two bugs in the export pipeline:

1. Container duration from WebM metadata can be unreliable (Chromium bug
   on Linux — reports Infinity, 0, or inflated values). The pipeline
   trusted this value, causing inflated exports, frozen video, and
   "decode ended early" errors.

   Fix: scan actual packet timestamps in loadMetadata() and compare
   against container duration. Use packet-based ground truth when they
   diverge.

2. The speed-aware audio path (renderPitchPreservedTimelineAudio)
   recorded in real-time via MediaRecorder but never paused recording
   during trim-region seeks. Seek dead time was captured as audio,
   inflating the audio track beyond the video duration.

   Fix: pause MediaRecorder during trim seeks, skip past initial trim
   before recording starts, wait for seek completion before resuming.

Fixes #276, #433. Partially addresses #428.
2026-04-16 13:50:09 -05:00

79 lines
2.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { shouldFailDecodeEndedEarly, validateDuration } from "./streamingDecoder";
describe("validateDuration", () => {
it("returns scanned duration when container reports Infinity", () => {
expect(validateDuration(Infinity, 15.3)).toBe(15.3);
});
it("returns scanned duration when container reports 0", () => {
expect(validateDuration(0, 15.3)).toBe(15.3);
});
it("returns scanned duration when container reports NaN", () => {
expect(validateDuration(NaN, 15.3)).toBe(15.3);
});
it("returns scanned duration when container is inflated beyond threshold", () => {
expect(validateDuration(42, 15.3)).toBe(15.3);
});
it("returns container duration when values are close", () => {
expect(validateDuration(15.5, 15.3)).toBe(15.5);
});
it("returns container duration when scanned is slightly higher", () => {
// container < scanned (scanned overshoot from last frame duration)
expect(validateDuration(15.0, 15.3)).toBe(15.0);
});
it("returns container duration when scanned is zero (corrupted/empty file)", () => {
expect(validateDuration(10, 0)).toBe(10);
});
});
describe("shouldFailDecodeEndedEarly", () => {
it("does not fail once every segment has been satisfied", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
lastDecodedFrameSec: 5.33,
requiredEndSec: 6.498,
streamDurationSec: 5.33,
}),
).toBe(false);
});
it("fails when decode stops far before the required end", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
lastDecodedFrameSec: 5.33,
requiredEndSec: 10,
streamDurationSec: 5.33,
}),
).toBe(true);
});
it("fails when no frame could be decoded for a non-empty timeline", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
lastDecodedFrameSec: null,
requiredEndSec: 1,
}),
).toBe(true);
});
it("fails when the decoder has not reached the reported stream end", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
lastDecodedFrameSec: 4.9,
requiredEndSec: 6.498,
streamDurationSec: 5.33,
}),
).toBe(true);
});
});