Signatur
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: einWritableStream, in den duVideoFrame-Objekte schreibst.track: der daraus entstehendeMediaStreamTrack(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
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);
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');
// Wichtig · Fallstricke
- Nur im Worker: Im Haupt-Thread gibt es
VideoTrackGeneratornicht (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 mitframe.close()freigeben, sonst stockt die Kamera oder der Speicher läuft voll. Bilder, die du inwritableschreibst, ü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ührttrack.stop()dazu, dass weitere Schreibvorgänge fehlschlagen. - Zeitstempel: Gib neuen
VideoFrame-Objekten stetig steigende Zeitstempel in Mikrosekunden mit. Sonst können Encoder oderMediaRecorderfalsch reagieren. - Übertragen: Den Track musst du beim Senden per
postMessage()in die Transfer-Liste aufnehmen. Er lässt sich nicht einfach kopieren.