From 80b229c3bf5525ec3e2768e922bb23c6c264e1fb Mon Sep 17 00:00:00 2001 From: Abelino Chavez Date: Fri, 30 Jan 2026 21:06:30 -0600 Subject: [PATCH] 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 --- apps/web/src/services/video-cache/service.ts | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/web/src/services/video-cache/service.ts b/apps/web/src/services/video-cache/service.ts index 36441981..cb23df8e 100644 --- a/apps/web/src/services/video-cache/service.ts +++ b/apps/web/src/services/video-cache/service.ts @@ -7,6 +7,7 @@ import { } from "mediabunny"; interface VideoSinkData { + input: Input; sink: CanvasSink; iterator: AsyncGenerator | null; currentFrame: WrappedCanvas | null; @@ -246,19 +247,21 @@ export class VideoCache { mediaId: string; file: File; }): Promise { - 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); }