Signatur
Beschreibung
encode() übergibt ein VideoFrame an die Kodierwarteschlange eines konfigurierten VideoEncoder. Die Methode kehrt sofort zurück. Die eigentliche Kodierung läuft im Hintergrund, meist hardwarebeschleunigt. Jedes fertige Ergebnis liefert der Browser als EncodedVideoChunk (zusammen mit optionalen Metadaten wie decoderConfig) an den output-Callback, den du im Konstruktor angegeben hast. Fehler landen im error-Callback.
Typische Einsatzfälle sind Videoaufnahmen im Browser (z. B. Kamera- oder Canvas-Inhalte in MP4/WebM schreiben), Echtzeit-Streaming über WebTransport oder WebRTC-Alternativen sowie Videoschnitt und Transkodierung ohne Server. Vor dem ersten Aufruf muss der Encoder mit configure() in den Zustand "configured" gebracht werden.
Mit dem zweiten Parameter kannst du pro Frame steuern, ob ein Keyframe erzeugt werden soll (keyFrame: true). Das ist wichtig für Suchpunkte (Seeking) und für neu hinzukommende Empfänger beim Streaming. Je nach Codec gibt es außerdem Optionen für den Quantisierer, z. B. vp9, av1, avc oder hevc jeweils mit { quantizer }, wenn bitrateMode: "quantizer" konfiguriert ist.
Weil die Kodierung asynchron läuft, solltest du die Warteschlange über encodeQueueSize und das Ereignis dequeue überwachen, damit sich keine Frames aufstauen. Nach dem letzten Frame sorgt flush() dafür, dass alle ausstehenden Chunks ausgegeben werden.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $frame Pflicht | VideoFrame | Das zu kodierende Bild. Der Encoder legt intern eine Kopie bzw. Referenz an. Den Frame selbst solltest du danach mit frame.close() freigeben. |
|
| $options | Object | {} | Optionale Kodieroptionen pro Frame:
|
Rückgabewert
output-Callback als EncodedVideoChunk.Beispiele
Canvas-Frames kodieren mit regelmäßigen Keyframes
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk, metadata) => {
// Chunk-Daten kopieren, z. B. für einen Muxer
const data = new Uint8Array(chunk.byteLength);
chunk.copyTo(data);
chunks.push({ type: chunk.type, timestamp: chunk.timestamp, data });
if (metadata?.decoderConfig) {
console.log('Decoder-Konfiguration:', metadata.decoderConfig.codec);
}
},
error: (e) => console.error('Kodierfehler:', e)
});
encoder.configure({
codec: 'vp8',
width: 640,
height: 480,
bitrate: 1_000_000,
framerate: 30
});
const canvas = new OffscreenCanvas(640, 480);
const ctx = canvas.getContext('2d');
for (let i = 0; i < 90; i++) {
ctx.fillStyle = `hsl(${i * 4}, 80%, 50%)`;
ctx.fillRect(0, 0, 640, 480);
const frame = new VideoFrame(canvas, { timestamp: i * (1_000_000 / 30) });
// Jede Sekunde (alle 30 Frames) ein Keyframe
encoder.encode(frame, { keyFrame: i % 30 === 0 });
frame.close(); // Speicher sofort freigeben
}
await encoder.flush();
console.log(`${chunks.length} Chunks, Keyframes: ${chunks.filter(c => c.type === 'key').length}`);
Kamera-Stream mit Rückstau-Kontrolle kodieren
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 } });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();
const encoder = new VideoEncoder({
output: (chunk) => sendOverNetwork(chunk),
error: (e) => console.error(e)
});
encoder.configure({
codec: 'avc1.42001f',
width: 1280,
height: 720,
bitrate: 2_500_000,
latencyMode: 'realtime'
});
let frameCount = 0;
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;
// Frames verwerfen, wenn der Encoder nicht hinterherkommt
if (encoder.encodeQueueSize > 2) {
frame.close();
continue;
}
encoder.encode(frame, { keyFrame: frameCount++ % 150 === 0 });
frame.close();
}
function sendOverNetwork(chunk) {
console.log(chunk.type, chunk.byteLength, 'Bytes');
}
Unterstützung prüfen und Zustand absichern
const config = { codec: 'vp09.00.10.08', width: 1920, height: 1080, bitrate: 4_000_000 };
if (!('VideoEncoder' in globalThis)) {
console.warn('WebCodecs wird nicht unterstützt');
} else {
const { supported } = await VideoEncoder.isConfigSupported(config);
console.log('VP9 unterstützt:', supported);
const encoder = new VideoEncoder({ output: () => {}, error: console.error });
const frame = new VideoFrame(new OffscreenCanvas(1920, 1080), { timestamp: 0 });
try {
encoder.encode(frame); // noch nicht konfiguriert
} catch (e) {
console.log(e.name);
} finally {
frame.close();
}
}
// Wichtig · Fallstricke
- Zustand: Ist der Encoder nicht im Zustand
"configured", wirftencode()einenInvalidStateError. Das passiert vorconfigure(), nachclose()und nach einem Fehler. - Geschlossener Frame: Ein bereits geschlossener (detached)
VideoFrameführt zu einemTypeError. - Speicher: Rufe nach
encode()immerframe.close()auf. Nicht geschlossene Frames belegen GPU- bzw. Videospeicher und können die Kamera-Pipeline blockieren. Der Browser gibt dann Warnungen aus. - Rückstau:
encode()blockiert nie. ÜberwacheencodeQueueSizebzw. dasdequeue-Ereignis, sonst wächst die Warteschlange unbegrenzt. - Zeitstempel werden in Mikrosekunden angegeben. Monoton steigende Werte sind für die meisten Muxer Pflicht.
- Keyframes: Der erste ausgegebene Chunk ist immer ein Keyframe. Der Encoder darf auch ohne
keyFrame: trueselbst Keyframes einfügen. - Kompatibilität: WebCodecs gibt es nur in sicheren Kontexten (HTTPS). Es läuft im Fenster und in Dedicated Workers. Die Codec-Unterstützung unterscheidet sich je nach Browser und Hardware, prüfe sie deshalb vorher mit
VideoEncoder.isConfigSupported(). Die Quantisierer-Optionen sind neuer und werden nicht überall unterstützt.