Signatur
Beschreibung
Über readable kommt man an die Rohdaten eines Kamera-, Bildschirm- oder Mikrofon-Tracks heran. Der MediaStreamTrackProcessor teilt den übergebenen MediaStreamTrack in einzelne Medien-Frames auf. Diese Frames liefert er als Einträge (Chunks) eines ReadableStream aus. Bei Video-Tracks ist jeder Chunk ein VideoFrame, bei Audio-Tracks ein AudioData-Objekt. Audio-Tracks unterstützt bisher nur Chromium.
Typische Einsatzzwecke sind die Echtzeit-Verarbeitung von Video mit WebCodecs und die Bildanalyse per Canvas, WebGL oder WebGPU. Beispiele dafür sind weichgezeichnete Hintergründe, Filter, Wasserzeichen und maschinelles Lernen. Ein weiterer Einsatzzweck ist das direkte Kodieren mit einem VideoEncoder. Den Stream liest man entweder mit einem Reader (getReader()) oder man leitet ihn mit pipeThrough() durch einen TransformStream. Das Ergebnis leitet man danach mit pipeTo() in den writable-Stream eines VideoTrackGenerator bzw. MediaStreamTrackGenerator. So entsteht wieder ein normaler Track, den man z. B. in einem <video>-Element abspielen oder über WebRTC senden kann.
Die Eigenschaft ist nur lesbar und gibt bei jedem Zugriff dasselbe Stream-Objekt zurück. Wenn der Track endet, wird der Stream geschlossen. Liest der Verbraucher zu langsam, verwirft der Processor ältere Frames. Wie viele Frames er höchstens zwischenspeichert, legt die Konstruktor-Option maxBufferSize fest.
Rückgabewert
ReadableStream, dessen Chunks die Frames des Tracks sind: VideoFrame-Objekte bei Video-Tracks, AudioData-Objekte bei Audio-Tracks.Beispiele
Kamera-Frames einzeln lesen und Metadaten ausgeben
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}, t=${frame.timestamp}µs`);
frame.close(); // Speicher sofort freigeben!
count++;
}
reader.releaseLock();
track.stop();
Graustufen-Filter mit TransformStream und MediaStreamTrackGenerator (Chromium)
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');
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 newFrame = new VideoFrame(canvas, { timestamp: frame.timestamp });
frame.close();
controller.enqueue(newFrame);
}
});
processor.readable
.pipeThrough(grayscale)
.pipeTo(generator.writable)
.catch((err) => console.error('Pipeline beendet:', err));
document.querySelector('video').srcObject = new MediaStream([generator]);
Standardkonform im Worker verarbeiten (Safari/Spezifikation)
// 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 ist übertragbar
// worker.js
self.onmessage = async ({ data: { track } }) => {
const processor = new MediaStreamTrackProcessor({ track, maxBufferSize: 2 });
const generator = new VideoTrackGenerator();
await processor.readable
.pipeThrough(new TransformStream({
transform(frame, controller) {
// hier z. B. Analyse oder Effekte
controller.enqueue(frame);
}
}))
.pipeTo(generator.writable);
};
// Wichtig · Fallstricke
- Frames immer schließen: Jeder
VideoFrameund jedesAudioData-Objekt belegt Speicher auf der GPU oder in Decoder-Puffern. Ohneframe.close()stockt der Stream schnell, und der Browser gibt Warnungen aus. Das gilt auch dann, wenn ein Frame nur verworfen wird. - Browser-Unterstützung: Laut Spezifikation ist
MediaStreamTrackProcessornur in Dedicated Workern verfügbar. Safari hält sich daran. Chromium erlaubt die Nutzung zusätzlich im Hauptthread und stellt stattVideoTrackGeneratorden nicht standardisiertenMediaStreamTrackGeneratorbereit. Firefox unterstützt die API nicht. Verfügbarkeit daher vorab prüfen mit'MediaStreamTrackProcessor' in self. - Nur ein Leser: Der Stream kann nur einmal gesperrt werden. Ein zweites
getReader()oderpipeTo()wirft einenTypeError. Wer die Frames an mehrere Verbraucher verteilen will, nutztreadable.tee()und schließt dann in beiden Zweigen jeden Frame. - Latenz vs. Vollständigkeit: Ist die Verarbeitung zu langsam, verwirft der Processor Frames. Ein kleiner Wert für
maxBufferSizehält die Latenz niedrig. - Datenschutz: Die Frames enthalten die unverarbeiteten Kamera- und Mikrofondaten. Daher ist die API nur in sicheren Kontexten (HTTPS) und nach Erteilung der Medienberechtigung nutzbar.