Signatur
Beschreibung
VideoTrackGenerator gehört zur Insertable Streams for MediaStreamTrack API (Breakout-Box). Die Schnittstelle ist das Gegenstück zu MediaStreamTrackProcessor: Der Processor zerlegt einen Track in einzelne VideoFrame-Objekte, der Generator setzt einzelne Frames wieder zu einem abspielbaren Track zusammen. Damit lassen sich eigene Videoquellen oder Filter bauen, etwa Hintergrund-Unschärfe, Wasserzeichen, Bildanalyse oder per Canvas gerenderte Inhalte, die dann per WebRTC verschickt, mit MediaRecorder aufgenommen oder in einem <video>-Element angezeigt werden.
Die Schnittstelle ist bewusst schlank:
new VideoTrackGenerator()– Konstruktor ohne Parameter.writable(nur lesbar) – einWritableStream, in denVideoFrame-Objekte geschrieben werden.track(nur lesbar) – der erzeugteMediaStreamTrackvom Typ"video".muted(les- und schreibbar) – ist der Werttrue, wird der Track stummgeschaltet (track.mutedwirdtrue, geschriebene Frames werden verworfen).
Laut Spezifikation steht VideoTrackGenerator nur in Dedicated Workers zur Verfügung. So läuft die rechenintensive Bildverarbeitung nicht auf dem Haupt-Thread. Den fertigen Track überträgt man per postMessage() als transferierbares Objekt zurück ins Hauptfenster.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $(keine) | — | Der Konstruktor new VideoTrackGenerator() erwartet keine Argumente. |
Rückgabewert
writable (WritableStream<VideoFrame>), track (MediaStreamTrack) und muted (boolean).Beispiele
Kamerabild im Worker filtern (worker.js)
// worker.js – empfängt einen Kamera-Track, verarbeitet ihn und sendet einen neuen Track zurück
self.onmessage = async ({ data }) => {
const processor = new MediaStreamTrackProcessor({ track: data.track });
const generator = new VideoTrackGenerator();
const transformer = new TransformStream({
async transform(frame, controller) {
// Beispiel: Frame horizontal spiegeln
const canvas = new OffscreenCanvas(frame.displayWidth, frame.displayHeight);
const ctx = canvas.getContext('2d');
ctx.scale(-1, 1);
ctx.drawImage(frame, -canvas.width, 0);
const newFrame = new VideoFrame(canvas, { timestamp: frame.timestamp });
frame.close(); // Speicher des Original-Frames freigeben!
controller.enqueue(newFrame);
}
});
processor.readable
.pipeThrough(transformer)
.pipeTo(generator.writable)
.catch((err) => console.error('Pipeline abgebrochen:', err));
// Erzeugten Track an das Hauptfenster übertragen
self.postMessage({ track: generator.track }, [generator.track]);
};
Hauptfenster: Track übergeben und Ergebnis anzeigen (main.js)
// main.js
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [cameraTrack] = stream.getVideoTracks();
const worker = new Worker('worker.js');
worker.postMessage({ track: cameraTrack }, [cameraTrack]);
worker.onmessage = ({ data }) => {
const video = document.querySelector('video');
video.srcObject = new MediaStream([data.track]);
video.play();
console.log('Gefilterter Track:', data.track.kind);
};
Eigene Videoquelle aus OffscreenCanvas erzeugen
// worker.js – erzeugt einen animierten Testbild-Track
const generator = new VideoTrackGenerator();
const writer = generator.writable.getWriter();
const canvas = new OffscreenCanvas(640, 360);
const ctx = canvas.getContext('2d');
let n = 0;
const drawFrame = async () => {
ctx.fillStyle = `hsl(${n % 360} 70% 50%)`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '48px sans-serif';
ctx.fillText(`Frame ${n}`, 40, 200);
const frame = new VideoFrame(canvas, { timestamp: n * 33333 }); // Mikrosekunden
await writer.write(frame); // der Generator übernimmt und schließt den Frame
n++;
};
setInterval(drawFrame, 1000 / 30);
self.postMessage({ track: generator.track }, [generator.track]);
Track stummschalten und beenden
// worker.js
const generator = new VideoTrackGenerator();
generator.muted = true;
console.log(generator.track.muted); // Stummschaltung ist im Track sichtbar
generator.muted = false;
// Schließen des Streams beendet den Track
await generator.writable.close();
console.log(generator.track.readyState);
// Wichtig · Fallstricke
- Nur im Worker: Im Fenster-Kontext ist
VideoTrackGeneratornicht definiert. Tracks müssen perpostMessage(…, [track])übertragen werden. Das setzt transferierbareMediaStreamTrack-Objekte voraus. - Browser-Unterstützung: Stand 2024 nur in Safari 18+. Chromium-Browser bieten stattdessen das ältere, nicht standardisierte
MediaStreamTrackGenerator(auch im Haupt-Thread, mit{ kind: 'video' }). Eine Feature-Erkennung per'VideoTrackGenerator' in selfmit Rückfall auf die alte Variante ist empfehlenswert. - Speicherlecks:
VideoFrame-Objekte belegen oft GPU-Speicher. Nicht mehr benötigte Frames (z. B. Originale nach einer Transformation) müssen mitframe.close()freigegeben werden, sonst bleibt die Pipeline hängen oder der Speicher läuft voll. In den Generator geschriebene Frames gibt der Generator selbst frei. - Zeitstempel:
timestampwird in Mikrosekunden angegeben und sollte monoton steigen, sonst kann es bei Encoder oderMediaRecorderzu Problemen kommen. - Lebensdauer: Durch
writable.close()oderabort()wird der Track beendet (readyState === "ended"). Umgekehrt führttrack.stop()dazu, dass weitere Schreibversuche fehlschlagen. - Den Track können Sie mit
track.clone()mehrfach verwenden, ohne einen zweiten Generator anzulegen.