Effiziente Vorgänge pro Videoframe für Videos mit requestVideoFrameCallback() ausführen

Hier erfahren Sie, wie Sie mithilfe von requestVideoFrameCallback() effizienter mit Videos im Browser arbeiten.

Mit der Methode HTMLVideoElement.requestVideoFrameCallback() können Webautoren einen Rückruf registrieren, der in den Rendering-Schritten ausgeführt wird, wenn ein neuer Videoframe an den Renderer gesendet wird. So können Entwickler effiziente Vorgänge pro Videoframe ausführen, z. B. Videoverarbeitung und Zeichnen auf einem Canvas, Videoanalyse oder Synchronisierung mit externen Audioquellen.

Unterschied zu requestAnimationFrame()

Vorgänge wie das Zeichnen eines Videoframes mit drawImage() über diese API auf einem Canvas werden nach Möglichkeit mit der Framerate des auf dem Bildschirm wiedergegebenen Videos synchronisiert. Im Gegensatz zu window.requestAnimationFrame(), das normalerweise etwa 60 Mal pro Sekunde ausgelöst wird, ist requestVideoFrameCallback() an die tatsächliche Framerate des Videos gebunden – mit einer wichtigen Ausnahme:

Die effektive Rate, mit der Callbacks ausgeführt werden, ist die niedrigere Rate zwischen der Rate des Videos und der Rate des Browsers. Das bedeutet, dass ein Video mit 25 fps, das in einem Browser abgespielt wird, der mit 60 Hz wiedergegeben wird, Rückrufe bei 25 Hz auslösen. Bei einem Video mit 120 fps in demselben 60-Hz-Browser werden Callbacks mit 60 Hz ausgelöst.

Tipps zur Benennung von Sitemaps

Aufgrund der Ähnlichkeit mit window.requestAnimationFrame() wurde die Methode ursprünglich als video.requestAnimationFrame() vorgeschlagen und in requestVideoFrameCallback() umbenannt, was nach einer längeren Diskussion vereinbart wurde.

Funktionserkennung

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
  // The API is supported!
}

Unterstützte Browser

Unterstützte Browser

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 132.
  • Safari: 15.4

Quelle

Polyfill

Es ist ein Polyfill für die requestVideoFrameCallback()-Methode verfügbar, das auf Window.requestAnimationFrame() und HTMLVideoElement.getVideoPlaybackQuality() basiert. Bevor Sie diese Funktion verwenden, sollten Sie die Einschränkungen in README beachten.

Mit der Methode „requestVideoFrameCallback()“

Wenn Sie schon einmal die Methode requestAnimationFrame() verwendet haben, werden Sie mit der Methode requestVideoFrameCallback() sofort vertraut sein. Sie registrieren einen ersten Callback einmal und registrieren ihn dann noch einmal, wenn der Callback ausgelöst wird.

const doSomethingWithTheFrame = (now, metadata) => {
  // Do something with the frame.
  console.log(now, metadata);
  // Re-register the callback to be notified about the next frame.
  video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
// Initially register the callback to be notified about the first frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);

Im Callback ist now ein DOMHighResTimeStamp und metadata ein VideoFrameMetadata-Wörterbuch mit den folgenden Eigenschaften:

  • presentationTime vom Typ DOMHighResTimeStamp: Der Zeitpunkt, zu dem der User-Agent den Frame zur Zusammensetzung gesendet hat.
  • expectedDisplayTime vom Typ DOMHighResTimeStamp: Der Zeitpunkt, zu dem der User-Agent erwartet, dass der Frame sichtbar ist.
  • width vom Typ unsigned long: Die Breite des Videoframes in Medienpixeln.
  • height vom Typ unsigned long: Die Höhe des Videoframes in Medienpixeln.
  • mediaTime vom Typ double: Der Zeitstempel der Medienpräsentation (PTS) in Sekunden des angezeigten Frames (z.B. sein Zeitstempel in der Zeitleiste video.currentTime).
  • presentedFrames vom Typ unsigned long: Anzahl der Frames, die für die Komposition eingereicht wurden. Ermöglicht es Clients, festzustellen, ob Frames zwischen den Instanzen von VideoFrameRequestCallback übersprungen wurden.
  • processingDuration vom Typ double: Die verstrichene Zeit in Sekunden, die vergangen ist, seit das codierte Paket mit demselben Präsentationszeitstempel (PTS) wie dieser Frame (z.B. dem mediaTime) an den Decoder gesendet wurde, bis der decodierte Frame zur Präsentation bereit war.

Für WebRTC-Anwendungen können zusätzliche Eigenschaften angezeigt werden:

  • captureTime vom Typ DOMHighResTimeStamp: Bei Videoframes, die von einer lokalen oder Remote-Quelle stammen, ist dies die Zeit, zu der der Frame von der Kamera aufgenommen wurde. Bei einer Remotequelle wird die Erfassungszeit anhand der Uhrsynchronisierung und RTCP-Senderberichte geschätzt, um RTP-Zeitstempel in die Erfassungszeit umzuwandeln.
  • receiveTime vom Typ DOMHighResTimeStamp: Bei Videoframes, die von einer Remotequelle stammen, ist dies die Zeit, zu der der codierte Frame von der Plattform empfangen wurde. Das ist die Zeit, zu der das letzte Paket, das zu diesem Frame gehört, über das Netzwerk empfangen wurde.
  • rtpTimestamp vom Typ unsigned long: der RTP-Zeitstempel, der diesem Videoframe zugeordnet ist.

Von besonderem Interesse in dieser Liste ist mediaTime. Bei der Chromium-Implementierung wird die Audiouhr als Zeitquelle verwendet, die video.currentTime unterstützt, während mediaTime direkt durch das presentationTimestamp des Frames ausgefüllt wird. Die mediaTime sollten Sie verwenden, wenn Sie Frames auf reproduzierbare Weise genau identifizieren möchten, einschließlich der Frames, die Sie verpasst haben.

Wenn etwas einen Frame zu spät erscheint…

Vertikale Synchronisierung (oder einfach vsync) ist eine Grafiktechnologie, die die Framerate eines Videos und die Aktualisierungsrate eines Monitors synchronisiert. Da requestVideoFrameCallback() im Haupt-Thread ausgeführt wird, das Video-Compositing aber im Compositor-Thread erfolgt, ist alles, was von dieser API zurückgegeben wird, ein Best-Effort-Verfahren und der Browser bietet keine strengen Garantien. Möglicherweise ist die API im Vergleich zum Rendern eines Videoframes um eine vsync zu spät. Es dauert einen VSync, bis Änderungen an der Webseite über die API auf dem Bildschirm angezeigt werden (wie bei window.requestAnimationFrame()). Wenn du also die mediaTime oder die Frame-Nummer auf deiner Webseite immer wieder aktualisierst und mit den nummerierten Videoframes vergleichst, sieht das Video irgendwann so aus, als wäre es einen Frame voraus.

Was tatsächlich passiert, ist, dass der Frame bei vsync x bereit ist, der Callback ausgelöst und bei vsync x+1 gerendert wird. Änderungen im Callback werden bei vsync x+2 gerendert. Sie können prüfen, ob der Rückruf um eine VSync-Periode zu spät erfolgt (und der Frame bereits auf dem Bildschirm gerendert wurde), indem Sie prüfen, ob metadata.expectedDisplayTime ungefähr now oder eine VSync-Periode in der Zukunft ist. Wenn der Wert innerhalb von etwa fünf bis zehn Mikrosekunden von now liegt, wurde der Frame bereits gerendert. Wenn expectedDisplayTime etwa 16 Millisekunden in der Zukunft liegt (vorausgesetzt, Ihr Browser/Bildschirm wird mit 60 Hz aktualisiert), sind Sie mit dem Frame synchron.

Demo

Ich habe eine kleine Demo auf Glitch erstellt, die zeigt, wie Frames mit genau der Framerate des Videos auf einem Canvas gezeichnet werden und wo die Frame-Metadaten zu Debug-Zwecken protokolliert werden.

let paintCount = 0;
let startTime = 0.0;

const updateCanvas = (now, metadata) => {
  if (startTime === 0.0) {
    startTime = now;
  }

  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

  const elapsed = (now - startTime) / 1000.0;
  const fps = (++paintCount / elapsed).toFixed(3);
  fpsInfo.innerText = `video fps: ${fps}`;
  metadataInfo.innerText = JSON.stringify(metadata, null, 2);

  video.requestVideoFrameCallback(updateCanvas);
};

video.requestVideoFrameCallback(updateCanvas);

Schlussfolgerungen

Nutzer haben schon lange die Verarbeitung auf Frameebene durchgeführt, ohne Zugriff auf die tatsächlichen Frames zu haben, nur basierend auf video.currentTime. Die requestVideoFrameCallback()-Methode verbessert diese Problemumgehung erheblich.

Danksagungen

Die requestVideoFrameCallback API wurde von Thomas Guilbert spezifiziert und implementiert. Dieser Beitrag wurde von Joe Medley und Kayce Basques überprüft. Hero-Image von Denise Jans auf Unsplash.