Signatur
Beschreibung
MediaStreamTrackProcessor gehört zur Insertable Streams for MediaStreamTrack API (auch „Breakout Box“ genannt). Das Interface nimmt einen laufenden MediaStreamTrack entgegen, zum Beispiel von Kamera, Bildschirmfreigabe oder canvas.captureStream(), und stellt dessen Inhalt über die Eigenschaft readable als ReadableStream bereit. Jeder Chunk in diesem Stream ist ein VideoFrame aus der WebCodecs-API. In Chromium-Browsern erzeugt ein Audio-Track stattdessen AudioData-Objekte.
Typische Einsatzgebiete sind Echtzeit-Videoeffekte wie Hintergrund-Unschärfe, Filter oder Wasserzeichen, maschinelles Sehen (Gesichts- oder Objekterkennung), Kodierung mit einem VideoEncoder und eigene Übertragungswege, etwa über WebTransport. Die verarbeiteten Einzelbilder lassen sich über einen VideoTrackGenerator (Spezifikation) bzw. MediaStreamTrackGenerator (Chromium) wieder in einen abspielbaren Track zurückführen. Meist verbindet man die beiden mit pipeThrough() und einem TransformStream.
Die Schnittstelle hat nur ein Mitglied: die schreibgeschützte Eigenschaft readable. Erzeugt wird sie mit new MediaStreamTrackProcessor({ track, maxBufferSize }). Nach der Spezifikation gibt es den Konstruktor nur in Dedicated Workers, damit die rechenintensive Bildverarbeitung den Hauptthread nicht blockiert. Chromium bietet ihn zusätzlich im Hauptthread an.
Wichtig: Jeder gelesene VideoFrame belegt Grafik- bzw. Decoder-Speicher. Man muss ihn nach der Verarbeitung mit frame.close() freigeben. Sonst gerät die Quelle ins Stocken, weil der interne Puffer voll bleibt.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $init Pflicht | Object | Konfigurationsobjekt für den Konstruktor new MediaStreamTrackProcessor(init). |
|
| $init.track Pflicht | MediaStreamTrack | Der Quell-Track, dessen Einzelbilder gelesen werden sollen. Nach der Spezifikation muss es ein Video-Track sein. Chromium akzeptiert auch Audio-Tracks und liefert dann AudioData. |
|
| $init.maxBufferSize | number | implementierungsabhängig (klein, z. B. 1 bei Video) | Wie viele Einzelbilder höchstens zwischengespeichert werden, wenn der Leser nicht schnell genug liest. Ist der Puffer voll, werden die ältesten Bilder verworfen. So bleibt die Latenz niedrig, dafür können Bilder ausfallen. |
Rückgabewert
readable (ReadableStream<VideoFrame>) liefert die Einzelbilder des Tracks. Endet der Track (track.stop() oder Ereignis ended), wird der Stream geschlossen.Beispiele
Kamerabild in Graustufen umwandeln (Chromium, Hauptthread)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const generator = new MediaStreamTrackGenerator({ kind: 'video' });
const canvas = new OffscreenCanvas(1, 1);
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const grayscale = new TransformStream({
transform(frame, controller) {
canvas.width = frame.displayWidth;
canvas.height = frame.displayHeight;
ctx.filter = 'grayscale(1)';
ctx.drawImage(frame, 0, 0);
const timestamp = frame.timestamp;
frame.close(); // Originalbild sofort freigeben
controller.enqueue(new VideoFrame(canvas, { timestamp }));
}
});
processor.readable.pipeThrough(grayscale).pipeTo(generator.writable);
// Verarbeiteten Track anzeigen
document.querySelector('video').srcObject = new MediaStream([generator]);
Einzelbilder direkt mit einem Reader lesen und zählen
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track, maxBufferSize: 2 });
const reader = processor.readable.getReader();
let count = 0;
setTimeout(() => track.stop(), 3000); // nach 3 s beenden
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;
if (count === 0) {
console.log(`Format: ${frame.format}, ${frame.displayWidth}x${frame.displayHeight}`);
}
count++;
frame.close();
}
console.log(`Gelesene Bilder: ${count}`);
Verarbeitung im Worker (spezifikationskonform, z. B. Safari)
// main.js
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const worker = new Worker('worker.js');
worker.postMessage({ track }, [track]); // Track wird übertragen
worker.onmessage = ({ data }) => {
document.querySelector('video').srcObject = new MediaStream([data.track]);
};
// worker.js
self.onmessage = async ({ data }) => {
const processor = new MediaStreamTrackProcessor({ track: data.track });
const generator = new VideoTrackGenerator();
self.postMessage({ track: generator.track }, [generator.track]);
const passThrough = new TransformStream({
transform(frame, controller) {
// hier z. B. Analyse oder Effekte
controller.enqueue(frame);
}
});
await processor.readable.pipeThrough(passThrough).pipeTo(generator.writable);
};