Live demo
How it works
The pipeline mirrors ffplay. Mediabunny demuxes MP4 and WebM from HTTP byte ranges into packets. A read loop
keeps a few seconds of packets queued, a decode loop feeds one long-lived VideoDecoder, and a
presenter picks the frame for the clock or for the seek target. A seek that lands ahead in the same GOP keeps
decoding forward. Any other seek bumps a serial number, resets the same decoder and drops stale packets and
frames. Events go through a MessageChannel instead of timers, so nothing waits for a hidden tab.
worker.js
import { CanvasVideoElement } from "html-video-element-webworker-polyfill/canvas-video";
const video = new CanvasVideoElement({ canvas: offscreenCanvas });
video.src = "movie.mp4";
await new Promise((resolve) => (video.onloadeddata = resolve));
for (let i = 0; i < frameCount; i++) {
video.currentTime = (i + 0.5) / fps;
await new Promise((resolve) => (video.onseeked = resolve));
process(video.currentFrame);
}