fix: avoid false early decode failures
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user