Start · Sprachen · JavaScript · Referenz · HTMLVideoElement.requestVideoFrameCallback()

HTMLVideoElement.requestVideoFrameCallback()

Methode

Registriert einen einmaligen Callback, der aufgerufen wird, sobald ein neues Videobild an den Kompositor übergeben wird. Mitgeliefert werden genaue Metadaten zum Bild.

seit JavaScript Baseline 2024: Chrome 83+, Edge 83+, Safari 15.4+, Kategorie: webapi

Signatur

HTMLVideoElement.prototype.requestVideoFrameCallback(callback)

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

Typ
number
Beschreibung
Eine ganzzahlige ID ungleich 0, mit der sich der Callback über 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);
Bilder: 42, ca. 30.0 fps Bilder: 43, ca. 30.0 fps

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);
}
Medienzeit: 1.033 Medienzeit: 1.067

// 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 presentedFrames zwischen 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.
  • mediaTime ist genauer als video.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.prototype prüfen und notfalls auf requestAnimationFrame() ausweichen.
  • Bei Videos von anderen Ursprüngen ohne CORS-Freigabe (crossorigin) wird das Canvas nach drawImage() als verunreinigt markiert (tainted). getImageData() und toDataURL() werfen dann einen Fehler.