Pelajari cara menggunakan requestVideoFrameCallback() untuk bekerja lebih efisien dengan video di browser.
Dipublikasikan: 8 Januari 2023
Metode HTMLVideoElement.requestVideoFrameCallback()
memungkinkan penulis web mendaftarkan callback
yang berjalan dalam langkah-langkah rendering saat frame video baru dikirim ke compositor.
Hal ini memungkinkan developer melakukan operasi per frame video yang efisien pada video, seperti pemrosesan dan penggambaran video ke kanvas, analisis video, atau sinkronisasi dengan sumber audio eksternal.
Perbedaan dengan requestAnimationFrame()
Operasi yang dilakukan dengan API ini, seperti menggambar frame video ke kanvas dengan
drawImage(),
disinkronkan sebaik mungkin dengan kecepatan frame video yang diputar di
layar. Hal ini berbeda dengan
window.requestAnimationFrame(),
yang biasanya diaktifkan sekitar 60 kali per detik.
requestVideoFrameCallback() terikat pada kecepatan frame video sebenarnya—dengan
pengecualian penting:
Kecepatan efektif saat callback dijalankan adalah kecepatan yang lebih rendah antara kecepatan video dan kecepatan browser. Artinya, video 25 fps yang diputar di browser yang melakukan penggambaran pada 60 Hz akan memicu callback pada 25 Hz. Video 120 fps di browser 60 Hz yang sama akan memicu callback pada 60 Hz.
Deteksi fitur
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Polyfill
Polyfill untuk metode requestVideoFrameCallback()
berdasarkan
Window.requestAnimationFrame()
dan
HTMLVideoElement.getVideoPlaybackQuality()
tersedia. Sebelum menggunakannya, perhatikan
batasan yang disebutkan dalam README.
Gunakan metode
Jika Anda menggunakan metode requestAnimationFrame(), Anda akan mengenali metode requestVideoFrameCallback(). Daftarkan callback awal satu kali, lalu daftarkan ulang setiap kali callback dipicu.
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);
Dalam callback, now adalah
DOMHighResTimeStamp
dan metadata adalah kamus VideoFrameMetadata
dengan properti berikut:
presentationTime, berjenisDOMHighResTimeStamp: Waktu saat agen pengguna mengirimkan frame untuk komposisi.expectedDisplayTime, berjenisDOMHighResTimeStamp: Waktu saat agen pengguna mengharapkan frame terlihat.width, dari jenisunsigned long: Lebar frame video, dalam piksel media.height, berjenisunsigned long: Tinggi frame video, dalam piksel media.mediaTime, berjenisdouble: Stempel waktu presentasi media (PTS) dalam detik frame yang ditampilkan (seperti stempel waktunya di linimasavideo.currentTime).presentedFrames, berjenisunsigned long: Jumlah frame yang dikirimkan untuk komposisi. Memungkinkan klien menentukan apakah frame terlewat di antara instanceVideoFrameRequestCallback.processingDuration, berjenisdouble: Durasi yang berlalu dalam detik sejak pengiriman paket yang dienkode dengan stempel waktu presentasi (PTS) yang sama dengan frame ini (misalnya, sama denganmediaTime) ke dekoder hingga frame yang didekode siap untuk presentasi.
Untuk aplikasi WebRTC, properti tambahan dapat muncul:
captureTime, berjenisDOMHighResTimeStamp: Untuk frame video yang berasal dari sumber lokal atau jarak jauh, ini adalah waktu saat frame diambil oleh kamera. Untuk sumber jarak jauh, waktu pengambilan diperkirakan menggunakan sinkronisasi jam dan laporan pengirim RTCP untuk mengonversi stempel waktu RTP ke waktu pengambilan.receiveTime, berjenisDOMHighResTimeStamp: Untuk frame video yang berasal dari sumber jarak jauh, ini adalah waktu frame yang dienkode diterima oleh platform, yaitu waktu saat paket terakhir yang termasuk dalam frame ini diterima melalui jaringan.rtpTimestamp, berjenisunsigned long: Stempel waktu RTP yang terkait dengan frame video ini.
Yang menarik dalam daftar ini adalah mediaTime.
Implementasi Chromium menggunakan clock audio sebagai sumber waktu yang mendukung video.currentTime,
sedangkan mediaTime diisi langsung oleh presentationTimestamp frame.
mediaTime adalah yang harus Anda gunakan jika ingin mengidentifikasi frame secara persis dengan cara yang dapat direproduksi,
termasuk untuk mengidentifikasi frame mana yang terlewat.
Jika tampak ada perbedaan satu frame
Sinkronisasi vertikal (atau hanya vsync), adalah teknologi grafis yang menyinkronkan kecepatan frame video dan kecepatan refresh monitor.
Karena requestVideoFrameCallback() berjalan di thread utama, tetapi, di balik
layar, penggabungan video terjadi di thread compositor,
semua dari API ini adalah upaya terbaik, dan browser tidak menawarkan jaminan
ketat apa pun.
Mungkin API terlambat satu sinkronisasi vertikal (vsync) dibandingkan saat frame video dirender. Perubahan yang dilakukan pada halaman web melalui API memerlukan satu sinkronisasi vertikal agar muncul di layar (sama seperti window.requestAnimationFrame()).
Jadi, jika Anda terus memperbarui mediaTime atau nomor frame di halaman web dan membandingkannya dengan frame video bernomor, pada akhirnya video akan terlihat seperti satu frame lebih awal.
Yang sebenarnya terjadi adalah frame siap pada vsync x, callback dipicu dan frame dirender pada vsync x+1, dan perubahan yang dilakukan dalam callback dirender pada vsync x+2.
Anda dapat memeriksa apakah callback terlambat vsync (dan frame sudah dirender di layar)
dengan memeriksa apakah metadata.expectedDisplayTime kira-kira now atau satu vsync di masa mendatang.
Jika berada dalam waktu sekitar lima hingga sepuluh mikrodetik dari now, frame sudah dirender;
jika expectedDisplayTime kira-kira enam belas milidetik di masa mendatang
(dengan asumsi browser Anda melakukan refresh pada 60 Hz), maka Anda sudah sinkron dengan frame.
Demo
Saya telah membuat demo kecil yang menunjukkan cara menggambar frame di kanvas dengan kecepatan frame video yang tepat dan tempat metadata frame dicatat untuk tujuan proses 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);
Kesimpulan
Orang telah melakukan pemrosesan tingkat frame sejak lama—tanpa memiliki akses ke frame sebenarnya, hanya berdasarkan video.currentTime.
Metode requestVideoFrameCallback() sangat meningkatkan kualitas solusi ini.
Ucapan terima kasih
requestVideoFrameCallback API ditentukan dan diimplementasikan oleh
Thomas Guilbert.
Postingan ini ditinjau oleh Joe Medley
dan Kayce Basques.