Start · Sprachen · JavaScript · Referenz · new VideoTrackGenerator()

new VideoTrackGenerator()

Methode

Erzeugt ein <code>VideoTrackGenerator</code>-Objekt, das <code>VideoFrame</code>-Objekte über einen <code>WritableStream</code> annimmt und als <code>MediaStreamTrack</code> ausgibt.

seit JavaScript Safari 18+ (nur in Dedicated Workers); in Chromium Kategorie: webapi

Signatur

new VideoTrackGenerator()

Beschreibung

Der Konstruktor VideoTrackGenerator() gehört zur Spezifikation MediaStreamTrack Insertable Media (mediacapture-transform). Er erstellt ein Objekt, mit dem du aus einzelnen VideoFrame-Objekten einen echten Video-MediaStreamTrack erzeugst. Diesen Track kannst du danach wie jede Kamera- oder Bildschirmaufnahme verwenden, etwa in einem <video>-Element, mit MediaRecorder oder in einer RTCPeerConnection.

Das erzeugte Objekt hat drei Eigenschaften:

  • writable: ein WritableStream, in den du VideoFrame-Objekte schreibst.
  • track: der daraus entstehende MediaStreamTrack (kind === "video").
  • muted: ein Wahrheitswert, mit dem du den Track stummschalten kannst (lesen und schreiben).

Meist wird VideoTrackGenerator zusammen mit MediaStreamTrackProcessor benutzt. Der Processor zerlegt einen vorhandenen Track in Einzelbilder, ein TransformStream bearbeitet sie (Filter, Wasserzeichen, Hintergrund-Unschärfe, Bildanalyse), und der Generator setzt sie wieder zu einem Track zusammen. Du kannst aber auch komplett künstliche Videoquellen bauen, zum Beispiel aus einem OffscreenCanvas.

Wichtig: Laut Spezifikation gibt es den Konstruktor nur in Dedicated Workers. Die Bildverarbeitung läuft also außerhalb des Haupt-Threads. Den Track überträgst du per postMessage() mit Transfer-Liste zurück an die Seite.

Rückgabewert

Typ
VideoTrackGenerator
Beschreibung
Ein neues VideoTrackGenerator-Objekt mit den Eigenschaften writable (WritableStream<VideoFrame>), track (MediaStreamTrack) und muted (boolean).

Beispiele

Generator im Worker anlegen und Eigenschaften prüfen

// worker.js – läuft in einem Dedicated Worker
const generator = new VideoTrackGenerator();

console.log(generator.track.kind);          // Art des Tracks
console.log(generator.track.readyState);    // Zustand des Tracks
console.log(generator.muted);               // anfangs nicht stumm
console.log(generator.writable instanceof WritableStream);
video live false true

Kamerabild im Worker in Graustufen umwandeln

// main.js
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [cameraTrack] = stream.getVideoTracks();

const worker = new Worker('filter-worker.js');
// MediaStreamTrack ist übertragbar (Transfer-Liste)
worker.postMessage({ track: cameraTrack }, [cameraTrack]);

worker.onmessage = ({ data }) => {
  const video = document.querySelector('video');
  video.srcObject = new MediaStream([data.track]);
  video.play();
};

// filter-worker.js
self.onmessage = async ({ data }) => {
  const processor = new MediaStreamTrackProcessor({ track: data.track });
  const generator = new VideoTrackGenerator();

  // Fertigen Track an den Haupt-Thread zurückgeben
  self.postMessage({ track: generator.track }, [generator.track]);

  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 out = new VideoFrame(canvas, { timestamp: frame.timestamp });
      frame.close(); // Originalbild sofort freigeben
      controller.enqueue(out);
    }
  });

  await processor.readable
    .pipeThrough(grayscale)
    .pipeTo(generator.writable);
};

Künstliche Videoquelle mit Zeitstempel-Einblendung

// clock-worker.js
const generator = new VideoTrackGenerator();
self.postMessage({ track: generator.track }, [generator.track]);

const canvas = new OffscreenCanvas(640, 360);
const ctx = canvas.getContext('2d');
const writer = generator.writable.getWriter();

let running = true;
const start = performance.now();

while (running) {
  ctx.fillStyle = '#1e1e1e';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#fff';
  ctx.font = '48px sans-serif';
  ctx.fillText(new Date().toLocaleTimeString('de-DE'), 180, 200);

  const frame = new VideoFrame(canvas, {
    timestamp: Math.round((performance.now() - start) * 1000) // Mikrosekunden
  });
  await writer.write(frame);
  await new Promise((r) => setTimeout(r, 1000 / 30)); // ca. 30 fps
}

// Beim Beenden: writer.close() beendet auch den Track

Track vorübergehend stummschalten

// im Worker
const generator = new VideoTrackGenerator();

self.onmessage = ({ data }) => {
  if (data.type === 'pause') generator.muted = true;
  if (data.type === 'resume') generator.muted = false;
};

generator.track.onmute = () => console.log('Track stumm');
generator.track.onunmute = () => console.log('Track wieder aktiv');
Track stumm Track wieder aktiv

// Wichtig · Fallstricke

  • Nur im Worker: Im Haupt-Thread gibt es VideoTrackGenerator nicht (ReferenceError). Prüfe vorher mit 'VideoTrackGenerator' in self, ob die Schnittstelle vorhanden ist.
  • Browser-Unterstützung: Derzeit nur Safari 18 und neuer. Chromium-Browser bieten stattdessen das ältere, nicht standardisierte MediaStreamTrackGenerator (new MediaStreamTrackGenerator({ kind: 'video' })), das auch im Haupt-Thread läuft. Für Code, der in mehreren Browsern funktionieren soll, brauchst du eine Prüfung der vorhandenen Funktionen und beide Varianten.
  • Speicher: VideoFrame-Objekte belegen oft GPU-Speicher. Eingangsbilder, die du nicht weitergibst, musst du selbst mit frame.close() freigeben, sonst stockt die Kamera oder der Speicher läuft voll. Bilder, die du in writable schreibst, übernimmt der Generator und schließt sie selbst.
  • Lebensdauer: Wenn du den writable-Stream schließt oder abbrichst, endet der Track (readyState === "ended"). Umgekehrt führt track.stop() dazu, dass weitere Schreibvorgänge fehlschlagen.
  • Zeitstempel: Gib neuen VideoFrame-Objekten stetig steigende Zeitstempel in Mikrosekunden mit. Sonst können Encoder oder MediaRecorder falsch reagieren.
  • Übertragen: Den Track musst du beim Senden per postMessage() in die Transfer-Liste aufnehmen. Er lässt sich nicht einfach kopieren.