Signatur
Beschreibung
Der Konstruktor MediaStreamTrackProcessor() gehört zur Spezifikation MediaStream Insertable Streams (mediacapture-transform). Er verbindet einen Live-MediaStreamTrack, etwa von einer Kamera, einer Bildschirmfreigabe oder einem Canvas-Capture, mit der WHATWG-Streams-API. Das neue Objekt hat die Eigenschaft readable, einen ReadableStream. Bei Video-Tracks liefert er VideoFrame-Objekte. In Chromium liefert er bei Audio-Tracks zusätzlich AudioData-Objekte.
Damit kannst du einzelne Frames roh verarbeiten, ohne den Umweg über <video> und <canvas>. Typische Anwendungen sind Hintergrund-Unschärfe, Filter, Bilderkennung mit ML-Modellen, das Kodieren mit VideoEncoder (WebCodecs) oder eigene Aufzeichnungs-Pipelines. Oft kombiniert man den Prozessor mit einem TransformStream und einem MediaStreamTrackGenerator bzw. VideoTrackGenerator. So entsteht aus den bearbeiteten Frames wieder ein abspielbarer Track.
Laut Spezifikation ist die Schnittstelle nur in Dedicated Workern verfügbar. So bleibt aufwendige Frame-Verarbeitung außerhalb des Haupt-Threads. Chromium stellt den Konstruktor auch im Window-Kontext bereit. Im Worker ist er trotzdem meist die bessere Wahl.
Wichtig: Jedes gelesene VideoFrame belegt knappen (GPU-)Speicher und muss mit frame.close() freigegeben werden. Liest du den Stream nicht schnell genug, verwirft der Prozessor alte Frames. Wie viele Frames höchstens gepuffert werden, legt maxBufferSize fest.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $options Pflicht | Object | Konfigurationsobjekt mit den folgenden Eigenschaften. | |
| $options.track Pflicht | MediaStreamTrack | Der Track, der verarbeitet werden soll, z. B. aus getUserMedia() oder getDisplayMedia(). Laut Spezifikation nur kind === "video"; Chromium akzeptiert auch Audio-Tracks. |
|
| $options.maxBufferSize | number | implementierungsabhängig (Chromium: wenige Frames) | Höchstzahl gepufferter Frames. Ist der Puffer voll, werden die ältesten Frames verworfen. Größere Werte glätten Lastspitzen, erhöhen aber Latenz und Speicherbedarf. |
Rückgabewert
MediaStreamTrackProcessor-Objekt. Seine Eigenschaft readable ist ein ReadableStream<VideoFrame> (bei Audio in Chromium ein ReadableStream<AudioData>).Beispiele
Kamera-Frames lesen und Metadaten ausgeben (Chromium, Haupt-Thread)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();
let count = 0;
while (count < 5) {
const { value: frame, done } = await reader.read();
if (done) break;
console.log(`Frame ${count}: ${frame.displayWidth}x${frame.displayHeight}, ts=${frame.timestamp}µs`);
frame.close(); // Speicher sofort freigeben!
count++;
}
reader.releaseLock();
track.stop();
Frames in einem Worker verarbeiten (ReadableStream übertragen)
// main.js
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const { readable } = new MediaStreamTrackProcessor({ track, maxBufferSize: 2 });
const worker = new Worker('frame-worker.js');
worker.postMessage({ readable }, [readable]); // Stream ist übertragbar
worker.onmessage = (e) => console.log('Worker:', e.data);
// frame-worker.js
self.onmessage = async ({ data: { readable } }) => {
let frames = 0;
const started = performance.now();
for await (const frame of readable) {
frames++;
frame.close();
if (frames % 30 === 0) {
const fps = frames / ((performance.now() - started) / 1000);
self.postMessage(`${fps.toFixed(1)} fps`);
}
}
};
Pipeline: Graustufen-Filter mit TransformStream und MediaStreamTrackGenerator
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const generator = new MediaStreamTrackGenerator({ kind: 'video' });
const canvas = new OffscreenCanvas(640, 480);
const ctx = canvas.getContext('2d');
const grayscale = new TransformStream({
transform(frame, controller) {
ctx.filter = 'grayscale(1)';
ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
const out = new VideoFrame(canvas, { timestamp: frame.timestamp });
frame.close();
controller.enqueue(out);
}
});
processor.readable.pipeThrough(grayscale).pipeTo(generator.writable);
document.querySelector('video').srcObject = new MediaStream([generator]);