Start · Sprachen · JavaScript · Referenz · MediaStreamTrackProcessor

MediaStreamTrackProcessor

Schnittstelle

Macht aus einem Video-<code>MediaStreamTrack</code> einen <code>ReadableStream</code> aus <code>VideoFrame</code>-Objekten, damit man jedes Einzelbild lesen und verarbeiten kann.

seit JavaScript Chrome/Edge 94+ (auch im Hauptthread), Safari 18+ Kategorie: webapi

Signatur

interface MediaStreamTrackProcessor

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

Typ
MediaStreamTrackProcessor
Beschreibung
Eine neue Instanz. Ihre Eigenschaft 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}`);
Format: NV12, 640x480 Gelesene Bilder: 89

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);
};