fix: properly dispose WebCodecs resources in video cache (#663)

The VideoCache was creating Input objects but not storing them,
so input.dispose() was never called. This caused WebCodecs
VideoDecoder resources to leak, leading to browser crashes
during video playback.

Changes:
- Store Input object in VideoSinkData
- Call input.dispose() on initialization errors
- Call input.dispose() when clearing video from cache
- Clear frame references when disposing

This follows the same pattern as AudioManager which properly
manages Input disposal.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Abelino Chavez 2026-01-30 21:06:30 -06:00
parent de5293b024
commit 80b229c3bf
1 changed files with 17 additions and 5 deletions

View File

@ -7,6 +7,7 @@ import {
} from "mediabunny";
interface VideoSinkData {
input: Input;
sink: CanvasSink;
iterator: AsyncGenerator<WrappedCanvas, void, unknown> | null;
currentFrame: WrappedCanvas | null;
@ -246,19 +247,21 @@ export class VideoCache {
mediaId: string;
file: File;
}): Promise<void> {
try {
const input = new Input({
source: new BlobSource(file),
formats: ALL_FORMATS,
});
const input = new Input({
source: new BlobSource(file),
formats: ALL_FORMATS,
});
try {
const videoTrack = await input.getPrimaryVideoTrack();
if (!videoTrack) {
input.dispose();
throw new Error("No video track found");
}
const canDecode = await videoTrack.canDecode();
if (!canDecode) {
input.dispose();
throw new Error("Video codec not supported for decoding");
}
@ -268,6 +271,7 @@ export class VideoCache {
});
this.sinks.set(mediaId, {
input,
sink,
iterator: null,
currentFrame: null,
@ -277,6 +281,7 @@ export class VideoCache {
prefetchPromise: null,
});
} catch (error) {
input.dispose();
console.error(`Failed to initialize video sink for ${mediaId}:`, error);
throw error;
}
@ -289,6 +294,13 @@ export class VideoCache {
void sinkData.iterator.return();
}
// Clear frame references
sinkData.currentFrame = null;
sinkData.nextFrame = null;
// Dispose the input to release WebCodecs resources
sinkData.input.dispose();
this.sinks.delete(mediaId);
}