Découvrez comment utiliser requestVideoFrameCallback() pour travailler plus efficacement avec les vidéos dans le navigateur.
Publié le 8 janvier 2023
La méthode HTMLVideoElement.requestVideoFrameCallback() permet aux auteurs Web d'enregistrer un rappel qui s'exécute lors des étapes de rendu lorsqu'une nouvelle image vidéo est envoyée au compositeur.
Cela permet aux développeurs d'effectuer des opérations efficaces par frame vidéo, telles que le traitement vidéo et la peinture sur un canevas, l'analyse vidéo ou la synchronisation avec des sources audio externes.
Différence avec requestAnimationFrame()
Les opérations effectuées avec cette API, comme le dessin d'une image vidéo sur un canevas avec drawImage(), sont synchronisées au mieux avec la fréquence d'images de la vidéo en cours de lecture à l'écran. C'est différent de window.requestAnimationFrame(), qui se déclenche généralement environ 60 fois par seconde.
requestVideoFrameCallback() est lié à la fréquence d'images réelle de la vidéo, avec une exception importante :
La fréquence effective à laquelle les rappels sont exécutés est la plus faible entre la fréquence de la vidéo et celle du navigateur. Cela signifie qu'une vidéo à 25 FPS lue dans un navigateur qui affiche à 60 Hz déclenchera des rappels à 25 Hz. Une vidéo à 120 fps dans ce même navigateur à 60 Hz déclenchera des rappels à 60 Hz.
Détection de fonctionnalités
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Rembourrage
Un polyfill pour la méthode requestVideoFrameCallback() basé sur Window.requestAnimationFrame() et HTMLVideoElement.getVideoPlaybackQuality() est disponible. Avant d'utiliser cette fonctionnalité, tenez compte des limites mentionnées dans README.
Utiliser la méthode
Si vous utilisez la méthode requestAnimationFrame(), vous reconnaîtrez la méthode requestVideoFrameCallback(). Enregistrez un rappel initial une seule fois, puis réenregistrez-le chaque fois que le rappel se déclenche.
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);
Dans le rappel, now est un DOMHighResTimeStamp et metadata est un dictionnaire VideoFrameMetadata avec les propriétés suivantes :
presentationTime, de typeDOMHighResTimeStamp: heure à laquelle l'agent utilisateur a soumis le frame pour composition.expectedDisplayTime, de typeDOMHighResTimeStamp: heure à laquelle l'user-agent s'attend à ce que le frame soit visible.width, de typeunsigned long: largeur du frame vidéo, en pixels média.height, de typeunsigned long: Hauteur du frame vidéo, en pixels média.mediaTime, de typedouble: code temporel de présentation (PTS) en secondes de la frame présentée (par exemple, son code temporel sur la timelinevideo.currentTime).presentedFrames, de typeunsigned long: nombre de frames envoyées pour la composition. Permet aux clients de déterminer si des frames ont été manqués entre les instances deVideoFrameRequestCallback.processingDuration, de typedouble: durée écoulée en secondes entre l'envoi du paquet encodé avec le même code temporel de présentation (PTS) que ce frame (par exemple, le même quemediaTime) au décodeur et le moment où le frame décodé était prêt à être présenté.
Pour les applications WebRTC, des propriétés supplémentaires peuvent s'afficher :
captureTime, de typeDOMHighResTimeStamp: Pour les images vidéo provenant d'une source locale ou distante, il s'agit de l'heure à laquelle l'image a été capturée par la caméra. Pour une source distante, l'heure de capture est estimée à l'aide de la synchronisation de l'horloge et des rapports de l'expéditeur RTCP pour convertir les codes temporels RTP en heure de capture.receiveTime, de typeDOMHighResTimeStamp: Pour les images vidéo provenant d'une source distante, il s'agit de l'heure à laquelle l'image encodée a été reçue par la plate-forme, c'est-à-dire l'heure à laquelle le dernier paquet appartenant à cette image a été reçu sur le réseau.rtpTimestamp, de typeunsigned long: Code temporel RTP associé à cette image vidéo.
mediaTime est un élément particulièrement intéressant dans cette liste.
L'implémentation de Chromium utilise l'horloge audio comme source de temps pour video.currentTime, tandis que mediaTime est directement renseigné par le presentationTimestamp du frame.
mediaTime est ce que vous devez utiliser si vous souhaitez identifier précisément les frames de manière reproductible, y compris pour identifier exactement les frames que vous avez manqués.
Si les éléments semblent décalés d'une image
La synchronisation verticale (ou vsync) est une technologie graphique qui synchronise la fréquence d'images d'une vidéo et la fréquence d'actualisation d'un écran.
Étant donné que requestVideoFrameCallback() s'exécute sur le thread principal, mais que, en coulisses, la composition vidéo se produit sur le thread du compositeur, tout ce qui provient de cette API est un effort maximal, et le navigateur n'offre aucune garantie stricte.
Il est possible que l'API soit en retard d'un vsync par rapport au moment où un frame vidéo est rendu. Il faut une synchronisation verticale pour que les modifications apportées à la page Web via l'API s'affichent à l'écran (comme pour window.requestAnimationFrame()). Par conséquent, si vous continuez à mettre à jour le mediaTime ou le numéro de frame sur votre page Web et que vous le comparez aux frames vidéo numérotées, la vidéo finira par sembler avoir une frame d'avance.
En réalité, le frame est prêt à la synchronisation verticale x, le rappel est déclenché et le frame est rendu à la synchronisation verticale x+1, et les modifications apportées dans le rappel sont rendues à la synchronisation verticale x+2.
Vous pouvez vérifier si le rappel est en retard sur la synchronisation verticale (et si le frame est déjà affiché à l'écran) en vérifiant si metadata.expectedDisplayTime est à peu près égal à now ou à une synchronisation verticale dans le futur.
Si elle se situe à environ cinq à dix microsecondes de now, le frame est déjà rendu. Si expectedDisplayTime se situe à environ seize millisecondes dans le futur (en supposant que votre navigateur se rafraîchisse à 60 Hz), vous êtes synchronisé avec le frame.
Démo
J'ai créé une petite démonstration qui montre comment les frames sont dessinées sur un canevas exactement à la fréquence d'images de la vidéo et où les métadonnées des frames sont enregistrées à des fins de débogage.
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);
Conclusions
Les utilisateurs traitent les images au niveau des frames depuis longtemps, sans avoir accès aux frames eux-mêmes, mais uniquement en fonction de video.currentTime.
La méthode requestVideoFrameCallback() améliore considérablement cette solution de contournement.
Remerciements
L'API requestVideoFrameCallback a été spécifiée et implémentée par Thomas Guilbert.
Cet article a été examiné par Joe Medley
et Kayce Basques.