Signatur
Beschreibung
VideoFrame ist der zentrale Datentyp der Web Codecs API. Ein Objekt enthält die Pixeldaten eines einzelnen Videobilds und dazu Metadaten: format (z. B. "I420", "NV12", "RGBA", "BGRA"), codedWidth/codedHeight, visibleRect, displayWidth/displayHeight, timestamp und duration (beide in Mikrosekunden) sowie colorSpace.
Frames bekommst du vom output-Callback eines VideoDecoder oder aus einem MediaStreamTrackProcessor. Du kannst sie auch selbst erzeugen, und zwar auf zwei Wegen:
new VideoFrame(image, init)aus einer Bildquelle wieHTMLVideoElement,HTMLCanvasElement,HTMLImageElement,OffscreenCanvas,ImageBitmap,SVGImageElementoder einem anderenVideoFrame.new VideoFrame(data, init)aus rohen Pixeldaten (BufferSource). Dabei sindformat,codedWidth,codedHeightundtimestampPflichtangaben.
Danach kannst du den Frame an VideoEncoder.encode() übergeben, mit drawImage(), WebGL (texImage2D) oder WebGPU (importExternalTexture) zeichnen oder mit copyTo() in einen eigenen Puffer kopieren. Die wichtigsten Methoden: allocationSize(options) gibt an, wie viele Bytes copyTo() braucht. copyTo(destination, options) kopiert die Pixeldaten und liefert ein Promise mit dem Ebenen-Layout (offset, stride). clone() erzeugt eine weitere Referenz auf dieselben Daten, close() gibt die Ressourcen sofort frei.
Frames sind übertragbar (Transferable). Du kannst sie also ohne Kopie per postMessage() an einen Worker schicken. Das eignet sich gut, um rechenintensive Bildbearbeitung aus dem Haupt-Thread auszulagern.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $image | data Pflicht | CanvasImageSource | VideoFrame | BufferSource | Entweder eine Bildquelle (HTMLVideoElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap, VideoFrame …) oder rohe Pixeldaten als ArrayBuffer/TypedArray/DataView. |
|
| $init.timestamp | number | Zeitstempel in Mikrosekunden. Pflicht bei Rohdaten und bei Bildquellen, die keinen eigenen Zeitstempel haben (z. B. Canvas). | |
| $init.duration | number | Dauer des Frames in Mikrosekunden. | |
| $init.format | string | Pixelformat, nur bei Rohdaten und dort Pflicht: "I420", "I420A", "I422", "I444", "NV12", "RGBA", "RGBX", "BGRA", "BGRX". |
|
| $init.codedWidth / init.codedHeight | number | Breite und Höhe der Pixeldaten in Pixeln. Bei Rohdaten Pflicht. | |
| $init.visibleRect | DOMRectInit | ganzer Frame | Sichtbarer Ausschnitt, z. B. { x: 0, y: 0, width: 640, height: 360 }. |
| $init.displayWidth / init.displayHeight | number | Größe von visibleRect | Anzeigegröße, zum Beispiel für nicht-quadratische Pixel. |
| $init.layout | PlaneLayout[] | dicht gepackt | Nur bei Rohdaten: offset und stride für jede Ebene. |
| $init.colorSpace | VideoColorSpaceInit | Farbraum-Angaben (primaries, transfer, matrix, fullRange). |
|
| $init.alpha | "keep" | "discard" | "keep" | Bei Bildquellen: Alphakanal behalten oder verwerfen. |
| $init.transfer | ArrayBuffer[] | [] | Nur bei Rohdaten: Puffer, die der Frame ohne Kopie übernimmt. Danach sind sie beim Aufrufer detached. |
Rückgabewert
VideoFrame-Objekt. Es belegt Ressourcen (oft GPU-Speicher), bis du close() aufrufst.Beispiele
Frame aus einem Canvas erzeugen und kodieren
const canvas = document.querySelector('canvas');
const encoder = new VideoEncoder({
output: (chunk) => console.log('Chunk:', chunk.type, chunk.byteLength),
error: (e) => console.error(e)
});
encoder.configure({ codec: 'vp8', width: canvas.width, height: canvas.height });
let frameIndex = 0;
const captureFrame = () => {
// Zeitstempel in Mikrosekunden (30 fps)
const frame = new VideoFrame(canvas, {
timestamp: frameIndex * (1_000_000 / 30),
duration: 1_000_000 / 30
});
encoder.encode(frame, { keyFrame: frameIndex % 60 === 0 });
frame.close(); // sofort freigeben, der Encoder hat eine eigene Referenz
frameIndex++;
};
captureFrame();
Frame aus rohen RGBA-Pixeldaten erzeugen und zeichnen
const width = 4;
const height = 2;
const pixels = new Uint8Array(width * height * 4);
for (let i = 0; i < pixels.length; i += 4) {
pixels.set([255, 0, 0, 255], i); // rot, deckend
}
const frame = new VideoFrame(pixels, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: 0
});
console.log(frame.format, frame.codedWidth, frame.codedHeight);
const ctx = document.querySelector('canvas').getContext('2d');
ctx.drawImage(frame, 0, 0);
frame.close();
Pixeldaten mit allocationSize() und copyTo() auslesen
const readPixels = async (frame) => {
const options = { format: 'RGBA' }; // Umwandlung nach RGBA (in neueren Browsern)
const size = frame.allocationSize(options);
const buffer = new Uint8Array(size);
const layout = await frame.copyTo(buffer, options);
console.log('Bytes:', size, 'Layout:', layout);
return buffer;
};
const canvas = new OffscreenCanvas(320, 240);
const frame = new VideoFrame(canvas, { timestamp: 0 });
const data = await readPixels(frame);
frame.close();
Frames aus einer Kamera an einen Worker übertragen
// Haupt-Thread
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();
const worker = new Worker('frame-worker.js');
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;
// Übertragen statt kopieren: Danach ist frame hier geschlossen
worker.postMessage({ frame }, [frame]);
}
// frame-worker.js
// self.onmessage = ({ data: { frame } }) => {
// console.log(frame.timestamp, frame.displayWidth);
// frame.close();
// };