Scopri come utilizzare il pulsante requestVideoFrameCallback()
per lavorare in modo più efficiente con i video nel browser.
Il metodo HTMLVideoElement.requestVideoFrameCallback()
consente agli autori web di registrare un callback
che viene eseguito nei passaggi di rendering quando un nuovo frame video viene inviato al compositore.
In questo modo, gli sviluppatori possono eseguire operazioni efficienti sui singoli fotogrammi dei video, come l'elaborazione e la pittura su una tela, l'analisi video o la sincronizzazione con sorgenti audio esterne.
Differenza con requestAnimationFrame()
Operazioni come il disegno di un frame video su una tela utilizzando
drawImage()
effettuate tramite questa API verranno sincronizzate secondo il criterio del massimo impegno
con la frequenza dei fotogrammi del video riprodotto sullo schermo.
A differenza di
window.requestAnimationFrame()
,
che di solito viene attivato circa 60 volte al secondo,
requestVideoFrameCallback()
è vincolato alla frequenza fotogrammi effettiva del video, con un'importante
eccezione:
La frequenza effettiva con cui vengono eseguiti i callback è la frequenza più bassa tra quella del video e quella del browser. Ciò significa che un video a 25 fps riprodotto in un browser che esegue il rendering a 60 Hz attiva i callback a 25 Hz. Un video a 120 fps nello stesso browser a 60 Hz attiverebbe i callback a 60 Hz.
Cosa si nasconde dietro a un nome?
A causa della sua somiglianza con window.requestAnimationFrame()
, il metodo inizialmente
è stato proposto come video.requestAnimationFrame()
e poi rinominato in
requestVideoFrameCallback()
, dopo una lunga discussione.
Rilevamento di funzionalità
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Supporto browser
Polyfill
È disponibile un polyfill per il metodo requestVideoFrameCallback()
basato su Window.requestAnimationFrame()
e HTMLVideoElement.getVideoPlaybackQuality()
. Prima di utilizzarlo, tieni presente le
limitazioni indicate nel documento README
.
Utilizzo del metodo requestVideoFrameCallback()
Se hai già utilizzato il metodo requestAnimationFrame()
, il metodo requestVideoFrameCallback()
ti risulterà subito familiare.
Devi registrare un callback iniziale una volta e poi registrarlo di nuovo ogni volta che viene attivato.
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);
Nel callback, now
è un DOMHighResTimeStamp
e metadata
è un dizionario VideoFrameMetadata
con le seguenti proprietà:
presentationTime
, di tipoDOMHighResTimeStamp
: La data e l'ora in cui l'user agent ha inviato il frame per la composizione.expectedDisplayTime
, di tipoDOMHighResTimeStamp
: Il momento in cui l'user agent si aspetta che il frame sia visibile.width
, di tipounsigned long
: la larghezza del frame del video, in pixel medi.height
, di tipounsigned long
: l'altezza del frame del video, in pixel medi.mediaTime
, di tipodouble
: Il timestamp (PTS) della presentazione dei contenuti multimediali in secondi dell'inquadratura presentata (ad es. il timestamp nella sequenza temporalevideo.currentTime
).presentedFrames
, di tipounsigned long
: un conteggio del numero di frame inviati per la composizione. Consente ai client di determinare se sono stati persi frame tra le istanze diVideoFrameRequestCallback
.processingDuration
, di tipodouble
: la durata trascorsa in secondi dal momento dell'invio al decodificatore del pacchetto codificato con lo stesso timestamp di presentazione (PTS) di questo frame (ad es. lo stesso dimediaTime
) fino al momento in cui il frame decodificato era pronto per la presentazione.
Per le applicazioni WebRTC, potrebbero essere visualizzate proprietà aggiuntive:
captureTime
, di tipoDOMHighResTimeStamp
: per i fotogrammi video provenienti da una sorgente locale o remota, si tratta dell'ora in cui il fotogramma è stato acquisito dalla videocamera. Per un'origine remota, la data e l'ora di acquisizione vengono stimate utilizzando la sincronizzazione dell'orologio e i report del mittente RTCP per convertire i timestamp RTP in data e ora di acquisizione.receiveTime
, di tipoDOMHighResTimeStamp
: per i frame video provenienti da una sorgente remota, si tratta dell'ora in cui il frame codificato è stato ricevuto dalla piattaforma, ovvero l'ora in cui l'ultimo pacchetto appartenente a questo frame è stato ricevuto sulla rete.rtpTimestamp
, di tipounsigned long
: Il timestamp RTP associato a questo frame video.
Di particolare interesse in questo elenco è mediaTime
.
L'implementazione di Chromium utilizza l'orologio audio come origine temporale che supporta video.currentTime
, mentre mediaTime
viene compilato direttamente dal presentationTimestamp
del frame.
mediaTime
è ciò che devi utilizzare se vuoi identificare esattamente i frame in modo riproducibile, incluso identificare esattamente i frame mancanti.
Se le cose sembrano fuori di un fotogramma…
La sincronizzazione verticale (o semplicemente vsync) è una tecnologia grafica che sincronizza la frequenza fotogrammi di un video e la frequenza di aggiornamento di un monitor.
Poiché requestVideoFrameCallback()
viene eseguito nel thread principale, ma sotto il cofano il compositing video avviene nel thread del compositore,
tutto ciò che proviene da questa API è secondo il criterio del massimo impegno e il browser non offre garanzie rigorose.
È possibile che l'API sia in ritardo di un vsync rispetto al momento in cui viene visualizzato un frame video.
È necessario un vsync affinché le modifiche apportate alla pagina web tramite l'API vengano visualizzate sullo schermo (come per window.requestAnimationFrame()
).
Pertanto, se continui ad aggiornare mediaTime
o il numero di frame nella pagina web e lo confronti con i frame video numerati, alla fine il video sembrerà essere in anticipo di un frame.
In realtà, il frame è pronto a vsync x, il callback viene attivato e il frame viene visualizzato a vsync x+1,
e le modifiche apportate nel callback vengono visualizzate a vsync x+2.
Puoi verificare se il callback è in ritardo rispetto a vsync (e il frame è già stato visualizzato sullo schermo) controllando se metadata.expectedDisplayTime
è approssimativamente now
o un vsync in futuro.
Se è compreso tra circa cinque e dieci microsecondi di now
, il frame è già stato visualizzato;
se expectedDisplayTime
è approssimativamente sedici millisecondi nel futuro (supponendo che il browser/lo schermo si aggiorni a 60 Hz),
allora sei in sincronia con il frame.
Demo
Ho creato una piccola demo su Glitch che mostra come i fotogrammi vengono disegnati su una tela esattamente con la frequenza fotogrammi del video e dove vengono registrati i metadati dei fotogrammi a scopo di debug.
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);
Conclusioni
Le persone eseguono l'elaborazione a livello di frame da molto tempo, senza avere accesso ai frame effettivi,
solo in base a video.currentTime
.
Il metodo requestVideoFrameCallback()
migliora notevolmente questa soluzione alternativa.
Ringraziamenti
L'API requestVideoFrameCallback
è stata specificata e implementata da
Thomas Guilbert.
Questo post è stato esaminato da Joe Medley
e Kayce Basques.
Immagine hero di
Denise Jans su Unsplash.