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 dans les é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 telles que le dessin d'un frame vidéo sur un canevas à l'aide de drawImage()
effectuées via cette API seront synchronisées dans la mesure du possible avec la fréquence d'images de la vidéo lue à l'écran.
Contrairement à window.requestAnimationFrame()
, qui se déclenche généralement environ 60 fois par seconde, requestVideoFrameCallback()
est lié au débit d'images vidéo réel, à une exception importante :
La fréquence effective à laquelle les rappels sont exécutés correspond à la fréquence la plus faible entre celle de la vidéo et celle du navigateur. Cela signifie qu'une vidéo 25 FPS lue dans un navigateur qui peint à 60 Hz déclenche 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 similitude avec window.requestAnimationFrame()
, la méthode a d'abord été proposée sous le nom video.requestAnimationFrame()
et a été rebaptisée requestVideoFrameCallback()
, ce qui a été convenu après une discussion approfondie.
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 la section README
.
À l'aide de la méthode requestVideoFrameCallback()
Si vous avez déjà utilisé la méthode requestAnimationFrame()
, vous vous sentirez immédiatement à l'aise avec la méthode requestVideoFrameCallback()
.
Vous enregistrez un rappel initial une fois, puis le réenregistrez chaque fois qu'il 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 envoyé le frame pour la composition.expectedDisplayTime
, de typeDOMHighResTimeStamp
: heure à laquelle l'agent utilisateur s'attend à ce que le frame soit visible.width
, de typeunsigned long
: largeur du frame vidéo, en pixels multimédias.height
, de typeunsigned long
: hauteur du frame vidéo, en pixels multimédias.mediaTime
, de typedouble
: code temporel de présentation multimédia (PTS) en secondes de la trame présentée (par exemple, son code temporel sur la chronologievideo.currentTime
).presentedFrames
, de typeunsigned long
: nombre de frames envoyés pour la composition. Permet aux clients de déterminer si des images ont été manquées 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 que lemediaTime
) au décodeur jusqu'à ce que le frame décodé soit 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, le temps de capture est estimé à l'aide de la synchronisation d'horloge et de rapports sur les expéditeurs RTCP afin de convertir les horodatages RTP en temps 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é à ce frame vidéo.
Cette liste présente un intérêt particulier : mediaTime
.
L'implémentation de Chromium utilise l'horloge audio comme source temporelle de video.currentTime
, tandis que mediaTime
est directement renseigné par la presentationTimestamp
du frame.
Vous devez utiliser mediaTime
si vous souhaitez identifier exactement les images de manière reproductible, y compris pour identifier exactement les images que vous avez manquées.
Si les éléments semblent décalés d'un cadre…
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 en arrière-plan, la composition vidéo s'effectue sur le thread du compositeur, tout ce qui provient de cette API est un effort, et le navigateur n'offre aucune garantie stricte.
Il se peut que l'API soit en retard d'une synchronisation verticale par rapport au moment où un frame vidéo est affiché.
Un vsync est nécessaire 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 à le comparer aux frames vidéo numérotés, la vidéo semblera être en avance d'un frame.
En réalité, le frame est prêt à l'étape vsync x, le rappel est déclenché et le frame est affiché à l'étape vsync x+1, et les modifications apportées dans le rappel sont affichées à l'étape vsync x+2.
Vous pouvez vérifier si le rappel est en retard vsync (et si l'image est déjà affichée à l'écran) en vérifiant si metadata.expectedDisplayTime
est à peu près now
ou vsync à l'avenir.
Si elle se situe entre cinq et dix microsecondes après now
, le frame est déjà affiché. Si expectedDisplayTime
est à environ seize millisecondes dans le futur (en supposant que votre navigateur/écran s'actualise à 60 Hz), vous êtes synchronisé avec le frame.
Démo
J'ai créé une petite démonstration sur Glitch qui montre comment les images sont dessinées sur un canevas avec la fréquence d'images exacte 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 un traitement au niveau des frames, sans avoir accès aux frames réels, uniquement sur la base 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 par Denise Jans sur Unsplash.