fix: avoid false early decode failures

This commit is contained in:
Siddharth
2026-03-17 19:30:47 -07:00
parent 0f123283b3
commit de18a2f46f
2 changed files with 82 additions and 4 deletions
+40
View File
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { shouldFailDecodeEndedEarly } from "./streamingDecoder";
describe("shouldFailDecodeEndedEarly", () => {
it("does not fail once every segment has been satisfied", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
segmentsLength: 1,
completedSegments: 1,
lastDecodedFrameSec: 5.33,
requiredEndSec: 6.498,
}),
).toBe(false);
});
it("fails when decode stops before the remaining segments can be covered", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
segmentsLength: 2,
completedSegments: 1,
lastDecodedFrameSec: 5.33,
requiredEndSec: 6.498,
}),
).toBe(true);
});
it("fails when no frame could be decoded for a non-empty timeline", () => {
expect(
shouldFailDecodeEndedEarly({
cancelled: false,
segmentsLength: 1,
completedSegments: 0,
lastDecodedFrameSec: null,
requiredEndSec: 1,
}),
).toBe(true);
});
});
+42 -4
View File
@@ -12,6 +12,38 @@ export interface DecodedVideoInfo {
audioCodec?: string;
}
type EarlyDecodeEndCheck = {
cancelled: boolean;
segmentsLength: number;
completedSegments: number;
lastDecodedFrameSec: number | null;
requiredEndSec: number;
};
export function shouldFailDecodeEndedEarly({
cancelled,
segmentsLength,
completedSegments,
lastDecodedFrameSec,
requiredEndSec,
}: EarlyDecodeEndCheck): boolean {
if (cancelled || segmentsLength === 0) {
return false;
}
// If we already satisfied every segment, the exporter has successfully
// filled any short metadata tail using the last decoded frame.
if (completedSegments >= segmentsLength) {
return false;
}
if (lastDecodedFrameSec === null) {
return true;
}
return requiredEndSec - lastDecodedFrameSec > 1;
}
/** Caller must close the VideoFrame after use. */
type OnFrameCallback = (
frame: VideoFrame,
@@ -366,12 +398,18 @@ export class StreamingVideoDecoder {
const requiredEndSec = segments.length > 0 ? segments[segments.length - 1].endSec : 0;
if (
!this.cancelled &&
lastDecodedFrameSec !== null &&
requiredEndSec - lastDecodedFrameSec > 1
shouldFailDecodeEndedEarly({
cancelled: this.cancelled,
segmentsLength: segments.length,
completedSegments: segmentIdx,
lastDecodedFrameSec,
requiredEndSec,
})
) {
const decodedAtLabel =
lastDecodedFrameSec === null ? "no decoded frame" : `${lastDecodedFrameSec.toFixed(3)}s`;
throw new Error(
`Video decode ended early at ${lastDecodedFrameSec.toFixed(3)}s (needed ${requiredEndSec.toFixed(3)}s).`,
`Video decode ended early at ${decodedAtLabel} (needed ${requiredEndSec.toFixed(3)}s).`,
);
}
}