بیاموزید که چگونه از requestVideoFrameCallback()
برای کار موثرتر با ویدیوهای موجود در مرورگر استفاده کنید.
متد HTMLVideoElement.requestVideoFrameCallback()
به نویسندگان وب اجازه می دهد تا زمانی که یک فریم ویدیویی جدید به کامپوزیتور ارسال می شود، یک فراخوانی را ثبت کنند که در مراحل رندر اجرا می شود. این به توسعهدهندگان اجازه میدهد تا عملیات کارآمدی را برای هر فریم ویدیو روی ویدیو انجام دهند، مانند پردازش ویدیو و نقاشی روی بوم، تجزیه و تحلیل ویدیو، یا همگامسازی با منابع صوتی خارجی.
تفاوت با requestAnimationFrame()
عملیاتی مانند کشیدن یک فریم ویدیو بر روی بوم با استفاده از drawImage()
ساخته شده از طریق این API به عنوان بهترین تلاش با نرخ فریم ویدیویی که روی صفحه پخش می شود همگام می شود. متفاوت از window.requestAnimationFrame()
، که معمولاً حدود 60 بار در ثانیه شلیک می شود ، requestVideoFrameCallback()
به نرخ فریم واقعی ویدیو محدود می شود - با یک استثنا مهم:
نرخ موثری که با آن تماسهای برگشتی اجرا میشوند، نرخ کمتری بین نرخ ویدیو و نرخ مرورگر است. این به این معنی است که یک ویدیو با سرعت 25 فریم در ثانیه در مرورگری که با فرکانس 60 هرتز پخش میشود، با فرکانس 25 هرتز پاسخ تماس میگیرد. یک ویدیوی 120 فریم بر ثانیه در همان مرورگر 60 هرتزی، پاسخ تماسها را با فرکانس 60 هرتز انجام میدهد.
در یک نام چیست؟
به دلیل شباهت آن با window.requestAnimationFrame()
، این روش در ابتدا به عنوان video.requestAnimationFrame()
پیشنهاد شد و به requestVideoFrameCallback()
تغییر نام داد که پس از یک بحث طولانی با آن موافقت شد.
تشخیص ویژگی
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
پشتیبانی از مرورگر
پلی پر
یک polyfill برای متد requestVideoFrameCallback()
بر اساس Window.requestAnimationFrame()
و HTMLVideoElement.getVideoPlaybackQuality()
موجود است. قبل از استفاده از این، از محدودیت های ذکر شده در README
آگاه باشید.
با استفاده از متد () requestVideoFrameCallback
اگر تا به حال از متد requestAnimationFrame()
استفاده کرده باشید، بلافاصله با متد requestVideoFrameCallback()
آشنا خواهید شد. شما یک بار تماس اولیه را ثبت می کنید، و سپس هر زمان که پاسخ تماس شروع شد دوباره ثبت نام می کنید.
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);
در callback، now
یک DOMHighResTimeStamp
است و metadata
یک فرهنگ لغت VideoFrameMetadata
با ویژگی های زیر است:
-
presentationTime
، از نوعDOMHighResTimeStamp
: زمانی که عامل کاربر فریم را برای ترکیب ارسال کرد. -
expectedDisplayTime
، از نوعDOMHighResTimeStamp
: زمانی که عامل کاربر انتظار دارد فریم قابل مشاهده باشد. -
width
, از نوعunsigned long
: عرض فریم ویدیو بر حسب پیکسل رسانه. -
height
، از نوعunsigned long
: ارتفاع فریم ویدیو، بر حسب پیکسل رسانه. -
mediaTime
، از نوعdouble
: مهر زمانی ارائه رسانه (PTS) در ثانیه از فریم ارائه شده (به عنوان مثال، مهر زمانی آن در جدول زمانیvideo.currentTime
). -
presentedFrames
، از نوعunsigned long
: شمارش تعداد فریم های ارسال شده برای ترکیب. به کلاینتها اجازه میدهد تعیین کنند که آیا فریمها بین نمونههایVideoFrameRequestCallback
از دست رفته است یا خیر. -
processingDuration
از نوعdouble
: مدت زمان سپری شده بر حسب ثانیه از ارسال بسته رمزگذاری شده با همان مهر زمان ارائه (PTS) مانند این فریم (مثلاً همانmediaTime
) به رمزگشا تا زمانی که فریم رمزگشایی شده برای ارائه آماده شود.
برای برنامه های WebRTC، ممکن است ویژگی های اضافی ظاهر شود:
-
captureTime
، از نوعDOMHighResTimeStamp
: برای فریم های ویدیویی که از منبع محلی یا راه دور می آیند، این زمانی است که در آن قاب توسط دوربین گرفته شده است. برای یک منبع راه دور، زمان ضبط با استفاده از همگامسازی ساعت و گزارشهای فرستنده RTCP برای تبدیل مهرهای زمانی RTP به زمان تصویربرداری تخمین زده میشود. -
receiveTime
از نوعDOMHighResTimeStamp
: برای فریم های ویدئویی که از منبع راه دور می آیند، این زمانی است که فریم کدگذاری شده توسط پلتفرم دریافت می شود، یعنی زمانی که آخرین بسته متعلق به این فریم از طریق شبکه دریافت می شود. -
rtpTimestamp
، از نوعunsigned long
: مهر زمانی RTP مرتبط با این فریم ویدیویی.
مورد توجه ویژه در این لیست mediaTime
است. پیادهسازی Chromium از ساعت صوتی بهعنوان منبع زمانی استفاده میکند که از video.currentTime
پشتیبانی میکند، در حالی که mediaTime
مستقیماً با presentationTimestamp
قاب پر میشود. اگر میخواهید دقیقاً فریمها را به روشی تکرارپذیر شناسایی کنید، از جمله شناسایی دقیق فریمهایی که از دست دادهاید، باید از mediaTime
استفاده کنید.
اگر همه چیز یک فریم به نظر می رسد…
همگام سازی عمودی (یا فقط vsync)، یک فناوری گرافیکی است که نرخ فریم یک ویدیو و نرخ تازه سازی یک مانیتور را همگام می کند. از آنجایی که requestVideoFrameCallback()
روی رشته اصلی اجرا میشود، اما در زیر پوشش، ترکیب ویدیو در رشته ترکیبکننده اتفاق میافتد، همه چیز از این API بهترین تلاش است و مرورگر هیچ ضمانت دقیقی ارائه نمیدهد. چیزی که ممکن است اتفاق بیفتد این است که API می تواند نسبت به زمانی که یک فریم ویدیو رندر می شود یک vsync کند. یک vsync طول می کشد تا تغییرات ایجاد شده در صفحه وب از طریق API روی صفحه نمایش داده شود (همانند window.requestAnimationFrame()
). بنابراین اگر mediaTime
یا شماره فریم را در صفحه وب خود به روز کنید و آن را با فریم های ویدیویی شماره گذاری شده مقایسه کنید، در نهایت ویدیو به نظر می رسد که یک فریم جلوتر است.
چیزی که واقعاً اتفاق می افتد این است که فریم در vsync x آماده است، callback فعال می شود و فریم در vsync x+1 رندر می شود و تغییرات ایجاد شده در callback در vsync x+2 رندر می شود. با بررسی اینکه آیا metadata.expectedDisplayTime
تقریباً now
است یا یک vsync در آینده، میتوانید بررسی کنید که آیا تماس برگشتی با تأخیر vsync است (و فریم قبلاً روی صفحه نمایش داده شده است). اگر در حدود پنج تا ده میکروثانیه از now
باشد، فریم قبلاً ارائه شده است. اگر expectedDisplayTime
در آینده تقریباً شانزده میلی ثانیه باشد (با فرض اینکه مرورگر/صفحه نمایش شما با فرکانس 60 هرتز به روز شود)، آنگاه با فریم همگام هستید.
نسخه ی نمایشی
من یک نسخه نمایشی کوچک در Glitch ایجاد کردهام که نشان میدهد چگونه فریمها روی بوم دقیقاً با نرخ فریم ویدیو ترسیم میشوند و متادیتای فریم در کجا برای اهداف اشکالزدایی ثبت میشود.
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);
نتیجه گیری
افراد برای مدت طولانی پردازش در سطح فریم را انجام دادهاند—بدون دسترسی به فریمهای واقعی، فقط بر اساس video.currentTime
. متد requestVideoFrameCallback()
تا حد زیادی این راه حل را بهبود می بخشد.
قدردانی
API requestVideoFrameCallback
توسط Thomas Guilbert مشخص و اجرا شد. این پست توسط Joe Medley و Kayce Basques بازبینی شده است. تصویر قهرمان توسط Denise Jans در Unsplash.