Start · Sprachen · JavaScript · Referenz · VideoTrackGenerator.writable

VideoTrackGenerator.writable

Eigenschaft

Liefert einen <code>WritableStream</code>. Jedes <code>VideoFrame</code>, das Sie hineinschreiben, erscheint als Bild im zugehörigen <code>MediaStreamTrack</code> des Generators.

seit JavaScript Nur in Dedicated Workers; Safari 18+, noch nicht B Kategorie: webapi

Signatur

get VideoTrackGenerator.writable

Beschreibung

Die schreibgeschützte Eigenschaft writable eines VideoTrackGenerator ist die Eingabeseite des Generators. Es ist ein WritableStream, der VideoFrame-Objekte annimmt. Jedes geschriebene Bild wird als nächstes Bild im Video-Track generator.track ausgegeben. Diesen Track können Sie dann wie jeden anderen MediaStreamTrack verwenden, zum Beispiel in einem <video>-Element, in einer RTCPeerConnection oder mit einem MediaRecorder.

Wofür man das braucht:

  • Kamerabilder live bearbeiten: Sie lesen Bilder mit MediaStreamTrackProcessor, verändern sie in einem TransformStream (etwa für Filter, Hintergrund-Unschärfe oder Wasserzeichen) und leiten sie mit pipeTo(generator.writable) weiter.
  • Eigene Videoquellen erzeugen: Sie zeichnen Bilder auf einem OffscreenCanvas, erzeugen daraus ein VideoFrame und schreiben es über einen Writer (writable.getWriter()) in den Stream.

Der Stream folgt der normalen Streams-API: Er meldet Gegendruck (writer.ready, desiredSize). Wenn Sie ihn schließen (close()) oder abbrechen (abort()), wird der zugehörige Track beendet. Solange generator.muted den Wert true hat, werden geschriebene Bilder verworfen und nicht an den Track weitergegeben.

Rückgabewert

Typ
WritableStream<VideoFrame>
Beschreibung
Ein WritableStream, der VideoFrame-Objekte annimmt. Die Eigenschaft liefert bei jedem Zugriff dasselbe Stream-Objekt.

Beispiele

Kamerabilder im Worker verarbeiten und an einen neuen Track weiterleiten

// main.js
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const worker = new Worker('worker.js');
// Den Track an den Worker übertragen (transferable)
worker.postMessage({ track }, [track]);
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();
  // Den erzeugten Track zurück an den Hauptthread übertragen
  self.postMessage({ track: generator.track }, [generator.track]);

  const transformer = new TransformStream({
    transform(frame, controller) {
      // Hier könnte das Bild bearbeitet werden; wir reichen es nur weiter
      controller.enqueue(frame);
    }
  });

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

Eigene Bilder von einem OffscreenCanvas schreiben

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

self.postMessage({ track: generator.track }, [generator.track]);

for (let i = 0; i < 300; i++) {
  ctx.fillStyle = `hsl(${i % 360} 80% 50%)`;
  ctx.fillRect(0, 0, 640, 360);
  ctx.fillStyle = '#fff';
  ctx.font = '48px sans-serif';
  ctx.fillText(`Bild ${i}`, 40, 200);

  // Zeitstempel in Mikrosekunden (30 fps)
  const frame = new VideoFrame(canvas, { timestamp: i * 33_333 });
  await writer.ready;        // Gegendruck beachten
  await writer.write(frame); // Das Bild wird vom Generator übernommen
  await new Promise(r => setTimeout(r, 33));
}

await writer.close(); // Beendet den Track
console.log(generator.track.readyState);
ended

Stream bei einem Fehler abbrechen

const generator = new VideoTrackGenerator();
const writer = generator.writable.getWriter();

generator.track.addEventListener('ended', () => {
  console.log('Track beendet');
});

try {
  throw new Error('Quelle nicht verfügbar');
} catch (err) {
  await writer.abort(err);
}
Track beendet

// Wichtig · Fallstricke

  • Nur in Workern verfügbar: VideoTrackGenerator existiert laut Spezifikation (mediacapture-transform) nur in einem DedicatedWorkerGlobalScope. Im Hauptthread ist die Klasse undefined.
  • Browserunterstützung: Derzeit unterstützt vor allem Safari (ab 18) die Klasse. Chromium-Browser bieten stattdessen das ältere, nicht standardisierte MediaStreamTrackGenerator an, auch im Hauptthread. Prüfen Sie vorher, ob die Funktion vorhanden ist, zum Beispiel mit 'VideoTrackGenerator' in self.
  • Das Bild gehört danach dem Generator: Nach write(frame) gilt das VideoFrame als verbraucht und wird geschlossen. Verwenden Sie es danach nicht mehr. Bilder, die Sie verwerfen und nicht schreiben, müssen Sie selbst mit frame.close() schließen, sonst läuft der GPU- bzw. Videospeicher voll und die Kamera kann stehen bleiben.
  • Nur VideoFrame erlaubt: Wenn Sie andere Werte schreiben, lehnt der Stream sie mit einem TypeError ab.
  • Der Stream ist gesperrt: Solange ein Writer aktiv ist oder pipeTo() läuft, ist writable.locked auf true. Ein zweites getWriter() wirft dann einen Fehler. Geben Sie den Writer bei Bedarf mit writer.releaseLock() wieder frei.
  • Schließen beendet den Track: close() oder abort() auf dem Stream beendet generator.track endgültig. Einen beendeten Track können Sie nicht wieder starten.
  • Solange generator.muted den Wert true hat, werden geschriebene Bilder stillschweigend verworfen.