Signatur
Beschreibung
requestVideoFrameCallback() (kurz rVFC) funktioniert ähnlich wie requestAnimationFrame(), ist aber an die Bildrate des Videos gebunden und nicht an die Bildwiederholrate des Bildschirms. Der Callback wird ausgeführt, wenn der Browser ein neues Videobild zum Anzeigen an den Kompositor übergibt. Er läuft also genau einmal pro tatsächlich dargestelltem Bild und nicht bei jedem Neuzeichnen der Seite.
Der Callback bekommt zwei Argumente: einen DOMHighResTimeStamp (now) und ein Metadaten-Objekt mit Feldern wie mediaTime (Zeitstempel des Bildes im Medium), presentedFrames, width/height, expectedDisplayTime, presentationTime sowie bei WebRTC-Quellen optional captureTime, receiveTime und rtpTimestamp.
Typische Einsatzfälle sind:
- Videobilder bildgenau auf ein
<canvas>oder in WebGL/WebGPU übertragen, etwa für Filter oder Effekte. - Einzelbilder für maschinelles Lernen analysieren, zum Beispiel Gesichts- oder Posenerkennung.
- Untertitel, Overlays oder Messwerte genau mit dem Bild synchronisieren.
- Die Wiedergabequalität messen, also Bildrate und ausgelassene Bilder.
Der Callback ist einmalig. Wer fortlaufend benachrichtigt werden will, muss innerhalb des Callbacks erneut requestVideoFrameCallback() aufrufen. Die Methode gibt eine ID zurück, mit der man die Registrierung über cancelVideoFrameCallback() wieder aufheben kann.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $callback Pflicht | (now: DOMHighResTimeStamp, metadata: VideoFrameCallbackMetadata) => void | Funktion, die beim nächsten dargestellten Videobild aufgerufen wird. now ist der Zeitpunkt, zu dem der Callback eingeplant wurde. metadata enthält u. a. mediaTime, presentedFrames, width, height, presentationTime, expectedDisplayTime und optional processingDuration, captureTime, receiveTime und rtpTimestamp. |
Rückgabewert
HTMLVideoElement.cancelVideoFrameCallback(id) abmelden lässt.Beispiele
Videobilder bildgenau auf ein Canvas übertragen
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const drawFrame = (now, metadata) => {
canvas.width = metadata.width;
canvas.height = metadata.height;
ctx.drawImage(video, 0, 0);
// Callback erneut registrieren, sonst läuft er nur einmal
video.requestVideoFrameCallback(drawFrame);
};
video.requestVideoFrameCallback(drawFrame);
video.play();
Bildrate und ausgelassene Bilder messen
const video = document.querySelector('video');
let lastMediaTime = 0;
let lastFrames = 0;
const measure = (now, { mediaTime, presentedFrames }) => {
const frameDiff = presentedFrames - lastFrames;
const timeDiff = mediaTime - lastMediaTime;
if (timeDiff > 0) {
const fps = (frameDiff / timeDiff).toFixed(1);
console.log(`Bilder: ${presentedFrames}, ca. ${fps} fps`);
if (frameDiff > 1) console.warn(`${frameDiff - 1} Bild(er) ausgelassen`);
}
lastMediaTime = mediaTime;
lastFrames = presentedFrames;
video.requestVideoFrameCallback(measure);
};
video.requestVideoFrameCallback(measure);
Callback abbrechen und Fallback bei fehlender Unterstützung
const video = document.querySelector('video');
let handle = 0;
const onFrame = (now, metadata) => {
console.log('Medienzeit:', metadata.mediaTime.toFixed(3));
handle = video.requestVideoFrameCallback(onFrame);
};
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
handle = video.requestVideoFrameCallback(onFrame);
// Beim Pausieren die Verarbeitung beenden
video.addEventListener('pause', () => video.cancelVideoFrameCallback(handle));
video.addEventListener('play', () => {
handle = video.requestVideoFrameCallback(onFrame);
});
} else {
// Fallback: an die Bildwiederholrate gebunden, nicht bildgenau
const loop = () => {
console.log('currentTime:', video.currentTime.toFixed(3));
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
// Wichtig · Fallstricke
- Einmalig: Jeder Callback läuft nur einmal. Für eine Schleife muss er sich im Callback selbst neu registrieren. Registriert man ihn mehrfach, ohne ihn abzumelden, entstehen doppelte Schleifen.
- Kein Bild, kein Callback: Solange das Video pausiert ist, gesucht wird (Seeking) oder keine neuen Bilder kommen, wird der Callback nicht ausgelöst. In einem Tab im Hintergrund kann er stark gedrosselt werden oder ganz ausbleiben.
- Bilder können übersprungen werden: Ist der Hauptthread ausgelastet, bekommt man eventuell nicht jedes Bild. Ob Bilder fehlen, zeigt ein Vergleich von
presentedFrameszwischen zwei Aufrufen. - Bildrate ist begrenzt: Der Callback läuft höchstens mit der kleineren von zwei Raten, also Videobildrate oder Bildwiederholrate des Bildschirms. Ein 60-fps-Video auf einem 30-Hz-Bildschirm liefert nur rund 30 Callbacks pro Sekunde.
mediaTimeist genauer alsvideo.currentTime, weil es den Zeitstempel des tatsächlich dargestellten Bildes angibt.- Kompatibilität: Chromium-Browser unterstützen die Methode ab Version 83, Safari ab 15.4 und Firefox erst ab 132. Deshalb vorher mit
'requestVideoFrameCallback' in HTMLVideoElement.prototypeprüfen und notfalls aufrequestAnimationFrame()ausweichen. - Bei Videos von anderen Ursprüngen ohne CORS-Freigabe (
crossorigin) wird das Canvas nachdrawImage()als verunreinigt markiert (tainted).getImageData()undtoDataURL()werfen dann einen Fehler.