Signatur
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 einemTransformStream(etwa für Filter, Hintergrund-Unschärfe oder Wasserzeichen) und leiten sie mitpipeTo(generator.writable)weiter. - Eigene Videoquellen erzeugen: Sie zeichnen Bilder auf einem
OffscreenCanvas, erzeugen daraus einVideoFrameund 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
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);
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);
}
// Wichtig · Fallstricke
- Nur in Workern verfügbar:
VideoTrackGeneratorexistiert laut Spezifikation (mediacapture-transform) nur in einemDedicatedWorkerGlobalScope. Im Hauptthread ist die Klasseundefined. - Browserunterstützung: Derzeit unterstützt vor allem Safari (ab 18) die Klasse. Chromium-Browser bieten stattdessen das ältere, nicht standardisierte
MediaStreamTrackGeneratoran, 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 dasVideoFrameals verbraucht und wird geschlossen. Verwenden Sie es danach nicht mehr. Bilder, die Sie verwerfen und nicht schreiben, müssen Sie selbst mitframe.close()schließen, sonst läuft der GPU- bzw. Videospeicher voll und die Kamera kann stehen bleiben. - Nur
VideoFrameerlaubt: Wenn Sie andere Werte schreiben, lehnt der Stream sie mit einemTypeErrorab. - Der Stream ist gesperrt: Solange ein Writer aktiv ist oder
pipeTo()läuft, istwritable.lockedauftrue. Ein zweitesgetWriter()wirft dann einen Fehler. Geben Sie den Writer bei Bedarf mitwriter.releaseLock()wieder frei. - Schließen beendet den Track:
close()oderabort()auf dem Stream beendetgenerator.trackendgültig. Einen beendeten Track können Sie nicht wieder starten. - Solange
generator.mutedden Werttruehat, werden geschriebene Bilder stillschweigend verworfen.