Start · Sprachen · JavaScript · Referenz · VideoEncoder

VideoEncoder

Schnittstelle

Die <code>VideoEncoder</code>-Schnittstelle der WebCodecs API kodiert rohe <code>VideoFrame</code>-Objekte in komprimierte <code>EncodedVideoChunk</code>-Objekte (z. B. H.264, VP8, VP9, AV1).

seit JavaScript WebCodecs API – Chrome/Edge 94+, Safari 16.4+, Fir Kategorie: webapi

Signatur

interface VideoEncoder extends EventTarget

Beschreibung

VideoEncoder gibt dir direkten Zugriff auf die Video-Encoder des Browsers, meist mit Hardware-Beschleunigung. Du übergibst rohe Einzelbilder (VideoFrame) und bekommst über einen Callback komprimierte Datenpakete (EncodedVideoChunk) zurück. Diese kannst du zum Beispiel in einen Container wie MP4 oder WebM schreiben (Muxing), über WebTransport oder WebSocket streamen oder weiterverarbeiten.

Die API arbeitet asynchron und zustandsbehaftet: Nach dem Konstruktor (new VideoEncoder({ output, error })) muss der Encoder mit configure() auf einen Codec, eine Auflösung und eine Bitrate eingestellt werden. Danach nimmt encode(frame, options) Frames in eine interne Warteschlange auf. Mit flush() wartest du, bis alle ausstehenden Frames kodiert sind, und mit close() gibst du die Ressourcen endgültig frei.

Die wichtigsten Mitglieder:

  • Eigenschaften: state ("unconfigured", "configured", "closed") und encodeQueueSize (Anzahl wartender Kodieraufträge).
  • Methoden: configure(config), encode(frame, options), flush(), reset(), close().
  • Statisch: VideoEncoder.isConfigSupported(config) prüft vorab, ob eine Konfiguration unterstützt wird.
  • Event: dequeue (bzw. ondequeue) wird ausgelöst, wenn encodeQueueSize kleiner wird. So lässt sich Gegendruck (Backpressure) steuern.

Einsatzgebiete sind Bildschirmaufnahmen im Browser, Videoschnitt im Web, Echtzeit-Streaming mit geringer Latenz und das Exportieren von Canvas- oder WebGL-Animationen als Video.

Parameter

Name Typ Default Beschreibung
$init Pflicht object Initialisierungsobjekt für den Konstruktor new VideoEncoder(init).
$init.output Pflicht (chunk: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) => void Wird für jeden kodierten Chunk aufgerufen. metadata.decoderConfig enthält beim ersten Keyframe die Konfiguration, die ein VideoDecoder braucht (inklusive description bei AVC/HEVC).
$init.error Pflicht (error: DOMException) => void Wird bei einem schweren Fehler aufgerufen. Der Encoder wechselt danach in den Zustand "closed".

Rückgabewert

Typ
VideoEncoder
Beschreibung
Eine neue VideoEncoder-Instanz im Zustand "unconfigured".

Beispiele

Encoder anlegen, Konfiguration prüfen und einrichten

const config = {
  codec: 'vp8',
  width: 1280,
  height: 720,
  bitrate: 2_000_000, // 2 Mbit/s
  framerate: 30,
};

const { supported } = await VideoEncoder.isConfigSupported(config);
if (!supported) throw new Error('Konfiguration nicht unterstützt');

const chunks = [];
const encoder = new VideoEncoder({
  output: (chunk, metadata) => {
    chunks.push(chunk);
    if (metadata?.decoderConfig) {
      console.log('Decoder-Config:', metadata.decoderConfig.codec);
    }
  },
  error: (e) => console.error('Encoder-Fehler:', e.message),
});

encoder.configure(config);
console.log(encoder.state);
configured

Canvas-Animation als Video kodieren (mit Keyframes)

const canvas = new OffscreenCanvas(640, 360);
const ctx = canvas.getContext('2d');
const chunks = [];

const encoder = new VideoEncoder({
  output: (chunk) => chunks.push(chunk),
  error: (e) => console.error(e),
});
encoder.configure({ codec: 'vp8', width: 640, height: 360, bitrate: 1_000_000, framerate: 30 });

const fps = 30;
for (let i = 0; i < 90; i++) {
  ctx.fillStyle = `hsl(${i * 4}, 80%, 50%)`;
  ctx.fillRect(0, 0, 640, 360);

  const frame = new VideoFrame(canvas, { timestamp: (i * 1_000_000) / fps }); // Mikrosekunden
  encoder.encode(frame, { keyFrame: i % 30 === 0 }); // jede Sekunde ein Keyframe
  frame.close(); // Speicher sofort freigeben!
}

await encoder.flush();
encoder.close();
console.log(`${chunks.length} Chunks, erster ist ${chunks[0].type}`);
90 Chunks, erster ist key

Kamera-Stream mit Backpressure über das dequeue-Event

const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 } });
const [track] = stream.getVideoTracks();
const reader = new MediaStreamTrackProcessor({ track }).readable.getReader();

const encoder = new VideoEncoder({
  output: (chunk) => sendOverNetwork(chunk),
  error: (e) => console.error(e),
});
encoder.configure({
  codec: 'avc1.42001f', // H.264 Baseline, Level 3.1
  width: 1280,
  height: 720,
  bitrate: 1_500_000,
  latencyMode: 'realtime',
  avc: { format: 'annexb' },
});

encoder.addEventListener('dequeue', () => {
  console.log('Warteschlange:', encoder.encodeQueueSize);
});

function sendOverNetwork(chunk) {
  const data = new Uint8Array(chunk.byteLength);
  chunk.copyTo(data);
  // z. B. über WebTransport senden …
}

let count = 0;
while (true) {
  const { value: frame, done } = await reader.read();
  if (done) break;
  if (encoder.encodeQueueSize > 2) {
    frame.close(); // Encoder überlastet: Frame verwerfen
    continue;
  }
  encoder.encode(frame, { keyFrame: count++ % 150 === 0 });
  frame.close();
}

// Wichtig · Fallstricke

  • Nach encode() immer frame.close() aufrufen. VideoFrames belegen oft GPU-Speicher. Wenn du sie nicht schließt, gerät der Browser schnell ins Stocken oder bricht mit einem Fehler ab. encode() legt eine eigene Kopie an, deshalb darfst du den Frame direkt danach schließen.
  • Zeitstempel sind in Mikrosekunden angegeben, nicht in Millisekunden.
  • Der erste Frame muss ein Keyframe sein. Er wird automatisch als Keyframe kodiert. Für Streaming solltest du aber regelmäßig { keyFrame: true } setzen, damit Empfänger später einsteigen können.
  • Die Codec-Strings müssen vollständig sein, zum Beispiel "avc1.42001f", "vp09.00.10.08" oder "av01.0.04M.08". Ein einfaches "h264" funktioniert nicht. Prüfe Konfigurationen vorher mit VideoEncoder.isConfigSupported(), denn die Unterstützung hängt von Browser, Betriebssystem und Hardware ab.
  • Viele Encoder verlangen gerade Werte für Breite und Höhe, vor allem bei H.264 und 4:2:0-Farbunterabtastung.
  • Nach einem Fehler im error-Callback ist der Encoder geschlossen. Jeder weitere Aufruf wirft eine InvalidStateError. Mit reset() kannst du einen noch nicht geschlossenen Encoder zurücksetzen. Noch ausstehende flush()-Promises werden dabei abgelehnt.
  • Achte auf encodeQueueSize und das dequeue-Event. Wenn du ungebremst Frames einspeist, wächst der Speicherverbrauch immer weiter.
  • Die output-Chunks sind reine Codec-Daten ohne Container. Für eine abspielbare MP4- oder WebM-Datei brauchst du einen Muxer, zum Beispiel die Bibliotheken mp4-muxer oder webm-muxer.
  • Kompatibilität: Die API ist nur in sicheren Kontexten (HTTPS) verfügbar, im Fenster und in Workern. Chromium unterstützt sie seit Version 94, Safari seit 16.4 und Firefox seit 130 (Desktop). Prüfe die Verfügbarkeit mit 'VideoEncoder' in globalThis. Für rechenintensive Aufgaben lohnt sich ein Dedicated Worker.