demo · v150

Worker Processor

Transfer the processor's .readable stream to a dedicated Worker for the heavy per-frame work, and read totalFrames / discardedFrames from the MediaStreamTrackProcessor on the main thread. A ReadableStream is transferable, so the frames cross to the worker — but a CanvasCaptureMediaStreamTrack is not, so the track object is never posted.

This demo uses a canvas-based synthetic MediaStream (no camera permission required). The main thread builds the MediaStreamTrackProcessor and transfers its .readable stream to an inline Blob Worker that drains the frames. The counters are read from the processor on the main thread at 1 Hz. Slow down the worker consumer with the slider to induce frame drops.

Source (main thread)

Worker: idle
Consumer delay: 0 ms

Frame counter stats (posted from worker)

0
totalFrames
+0/s
0
discardedFrames
+0/s
0
fps (producer)
health
Discard rate: 0%

postMessage log (worker → main)

Architecture

Main thread

Canvas draw loop (30 fps)
canvas.captureStream(30)
new MediaStreamTrackProcessor(track)
Transfer processor.readable to Worker
Read totalFrames / discardedFrames @ 1 Hz

Dedicated Worker

Receive transferred ReadableStream
Pull frames from reader.read()
Simulated per-frame work (configurable delay)
frame.close()
Post drain progress to main
// main.js — keep the processor + counters on the main thread
const track = canvas.captureStream(30).getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({ track });

// A ReadableStream IS transferable; a CanvasCaptureMediaStreamTrack is NOT,
// so transfer the stream and never postMessage the track object.
const worker = new Worker(workerUrl);
worker.postMessage({ readable: processor.readable }, [processor.readable]);

setInterval(() => {
  console.log(processor.totalFrames, processor.discardedFrames);
}, 1000);

// worker.js — heavy per-frame work drains the transferred stream
self.onmessage = ({ data }) => {
  const reader = data.readable.getReader();
  (async () => {
    while (true) {
      const { done, value: frame } = await reader.read();
      if (done) break;
      // ... per-frame work ...
      frame.close();
    }
  })();
};

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗