Découvrez comment utiliser requestVideoFrameCallback()
pour travailler plus efficacement avec les vidéos dans le navigateur.
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 sur chaque image de la vidéo, telles que le traitement vidéo et l'affichage sur un canevas, l'analyse vidéo ou la synchronisation avec des sources audio externes.
Différence avec requestAnimationFrame()
Les opérations telles que le dessin d'une image vidéo sur un canevas à l'aide de drawImage()
effectuées via cette API seront synchronisées au mieux avec la fréquence d'images de la vidéo en cours de lecture à l'écran.
Contrairement à 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, à une exception importante:
La vitesse effective à laquelle les rappels sont exécutés correspond au taux le plus faible entre la vitesse de la vidéo et celle du navigateur. Cela signifie qu'une vidéo à 25 FPS dans un navigateur dont la fréquence d'affichage est de 60 Hz déclencherait des rappels à 25 Hz. Une vidéo à 120 FPS dans ce même navigateur 60 Hz déclencherait des rappels à 60 Hz.
Qu'est-ce qui se cache derrière un nom ?
En raison de sa similarité avec window.requestAnimationFrame()
, la méthode a été initialement proposée comme video.requestAnimationFrame()
et renommée requestVideoFrameCallback()
, qui a fait l'objet d'un accord après une longue discussion.
Détection de fonctionnalités
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Prise en charge des navigateurs
Polyfill
Un polyfill pour la méthode requestVideoFrameCallback()
basé sur Window.requestAnimationFrame()
et HTMLVideoElement.getVideoPlaybackQuality()
est disponible. Avant de l'utiliser, tenez compte des limites mentionnées dans le README
.
Utiliser la méthode requestVideoFrameCallback()
Si vous avez déjà utilisé la méthode requestAnimationFrame()
, vous vous familiariserez immédiatement avec la méthode requestVideoFrameCallback()
.
Vous devez enregistrer un rappel initial une fois, puis le réenregistrer 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
: moment auquel le user-agent a envoyé le frame pour la composition.expectedDisplayTime
, de typeDOMHighResTimeStamp
: moment auquel le user-agent s'attend à ce que le frame soit visible.width
, de typeunsigned long
: largeur de l'image vidéo, en pixels du contenu multimédia.height
, de typeunsigned long
: hauteur de l'image vidéo, en pixels de contenu multimédia.mediaTime
, de typedouble
: code temporel de la présentation multimédia (PTS) en secondes de l'image présentée (par exemple, son code temporel sur la timelinevideo.currentTime
).presentedFrames
, de typeunsigned long
: nombre d'images 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 cette image (identique aumediaTime
) au décodeur jusqu'à ce que l'image décodée soit prête pour la présentation.
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, la durée de capture est estimée en utilisant la synchronisation de l'horloge et les rapports de l'expéditeur RTCP pour convertir les horodatages 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 la trame encodée a été reçue par la plate-forme, c'est-à-dire l'heure à laquelle le dernier paquet appartenant à cette trame a été reçu sur le réseau.rtpTimestamp
, de typeunsigned long
: code temporel RTP associé à cette image vidéo.
Cette liste présente un intérêt particulier : mediaTime
.
L'implémentation de Chromium utilise l'horloge audio comme source temporelle qui sauvegarde video.currentTime
, tandis que mediaTime
est directement renseigné par la presentationTimestamp
de l'image.
mediaTime
est l'option à utiliser si vous souhaitez identifier précisément les images de manière reproductible, y compris pour identifier exactement celles que vous avez manquées.
Si les choses vous semblent décalées...
La synchronisation verticale (ou simplement vsync) est une technologie graphique qui synchronise la fréquence d'images d'une vidéo et celle d'un écran.
Étant donné que requestVideoFrameCallback()
s'exécute sur le thread principal, mais que la composition vidéo s'effectue en arrière-plan sur le thread du compositeur, tout le travail de cette API est effectué au mieux et le navigateur n'offre aucune garantie stricte.
Il se peut que l'API soit en retard d'une synchronisation vsync par rapport au moment où une image vidéo est affichée.
Il faut une vsync pour que les modifications apportées à la page Web via l'API s'affichent à l'écran (identique à window.requestAnimationFrame()
). Ainsi, si vous mettez à jour le mediaTime
ou le numéro de frame sur votre page Web et que vous le comparez aux images vidéo numérotées, la vidéo finira par avoir l'air d'avoir une image en avance.
En réalité, le frame est prêt au niveau de vsync x, le rappel est déclenché et le frame est rendu à vsync x+1, et les modifications apportées au rappel sont affichées avec vsync x+2.
Vous pouvez vérifier si le rappel est une vsync tardive (et si le frame est déjà affiché à l'écran) en vérifiant si metadata.expectedDisplayTime
est à peu près now
ou vsync à venir.
S'il se situe environ cinq à dix microsecondes après now
, le frame est déjà affiché. Si expectedDisplayTime
a lieu environ 16 millisecondes dans le futur (en supposant que votre navigateur ou votre écran s'actualise à 60 Hz), vous êtes synchronisé avec le frame.
Démonstration
J'ai créé une petite démonstration sur Glitch qui montre comment les images sont dessinées sur un canevas avec exactement la fréquence d'images de la vidéo et où les métadonnées des images sont consigné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 effectuent depuis longtemps le traitement au niveau des frames, sans avoir accès aux frames eux-mêmes, 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.
Ce post a été examiné par Joe Medley et Kayce Basques.
Image héros de Denise Jans sur Unsplash.