تعرَّف على كيفية استخدام requestVideoFrameCallback() لزيادة كفاءة العمل مع الفيديوهات في المتصفّح.
تاريخ النشر: 8 كانون الثاني (يناير) 2023
تسمح طريقة HTMLVideoElement.requestVideoFrameCallback()
لمطوّري الويب بتسجيل دالة ردّ نداء
يتم تنفيذها في خطوات العرض عند إرسال إطار فيديو جديد إلى برنامج التجميع.
يتيح ذلك للمطوّرين إجراء عمليات فعّالة على مستوى كل إطار فيديو، مثل معالجة الفيديو والرسم على لوحة العرض أو تحليل الفيديو أو المزامنة مع مصادر الصوت الخارجية.
الفرق بينها وبين requestAnimationFrame()
تتم مزامنة العمليات التي يتم إجراؤها باستخدام واجهة برمجة التطبيقات هذه، مثل رسم إطار فيديو على لوحة باستخدام
drawImage()،
بأفضل جهد ممكن مع معدل عرض الفيديو الذي يتم تشغيله على الشاشة. يختلف ذلك عن
window.requestAnimationFrame()،
الذي يتم تنفيذه عادةً 60 مرة في الثانية.
يكون requestVideoFrameCallback() مرتبطًا بمعدّل عدد اللقطات الفعلي في الثانية للفيديو، مع استثناء مهم:
معدّل تنفيذ عمليات معاودة الاتصال الفعلي هو المعدّل الأقل بين معدّل الفيديو ومعدّل المتصفّح. وهذا يعني أنّ فيديو بمعدّل 25 لقطة في الثانية يتم تشغيله في متصفّح يعرض المحتوى بمعدّل 60 هرتز سيؤدي إلى تشغيل عمليات رد الاتصال بمعدّل 25 هرتز. سيتم تشغيل فيديو بمعدل 120 لقطة في الثانية في المتصفّح نفسه الذي يعمل بمعدل 60 هرتز، وسيتم إطلاق عمليات معاودة الاتصال بمعدل 60 هرتز.
رصد الميزات
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Polyfill
يتوفّر polyfill لطريقة requestVideoFrameCallback() استنادًا إلى Window.requestAnimationFrame() وHTMLVideoElement.getVideoPlaybackQuality(). قبل استخدام هذه الميزة، يُرجى التعرّف على
القيود المذكورة في README.
استخدام الطريقة
إذا كنت تستخدم طريقة 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);
في دالة رد الاتصال، 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() يتم تشغيله على سلسلة التعليمات الرئيسية، ولكن تتم عملية تركيب الفيديو على سلسلة تعليمات المكوّن، فإنّ كل ما تقدّمه واجهة برمجة التطبيقات هذه هو أفضل ما يمكن تقديمه، ولا يقدّم المتصفّح أي ضمانات صارمة.
من المحتمل أن يكون تأخّر واجهة برمجة التطبيقات بمقدار مزامنة عمودية واحدة مقارنةً بوقت عرض إطار الفيديو. يستغرق ظهور التغييرات التي يتم إجراؤها على صفحة الويب من خلال واجهة برمجة التطبيقات على الشاشة دورة مزامنة واحدة (مثل window.requestAnimationFrame()). لذلك، إذا واصلت تعديل mediaTime أو رقم الإطار على صفحة الويب وقارنت ذلك بإطارات الفيديو المرقمة، سيبدو الفيديو في النهاية وكأنّه يسبق إطارًا واحدًا.
ما يحدث فعلاً هو أنّ اللقطة تكون جاهزة عند vsync x، ويتم تشغيل معاودة الاتصال وعرض اللقطة عند vsync x+1، ويتم عرض التغييرات التي تم إجراؤها في معاودة الاتصال عند vsync x+2.
يمكنك التحقّق مما إذا كان رد الاتصال متأخرًا عن مزامنة الإطار العمودي (وتم عرض الإطار على الشاشة)
من خلال التحقّق مما إذا كان metadata.expectedDisplayTime يساوي تقريبًا now أو مزامنة إطار عمودي واحدة في المستقبل.
إذا كان الفرق بينهما يتراوح بين خمس وعشر ميكروثوانٍ، يعني ذلك أنّه تم عرض الإطار بالفعل، أما إذا كان الفرق بينهما يبلغ حوالي ست عشرة مللي ثانية (بافتراض أنّ المتصفّح يعيد تحميل الصفحة بمعدّل 60 هرتز)، يعني ذلك أنّك متزامن مع الإطار.nowexpectedDisplayTime
عرض توضيحي
لقد أنشأت عرضًا توضيحيًا صغيرًا يوضّح كيفية رسم اللقطات على لوحة عرض بمعدّل اللقطات في الثانية للفيديو نفسه، وكيفية تسجيل بيانات تعريف اللقطات لأغراض تصحيح الأخطاء.
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() هذا الحلّ بشكل كبير.
الإقرارات
تم تحديد مواصفات واجهة برمجة التطبيقات requestVideoFrameCallback وتنفيذها بواسطة
توماس غيلبرت.
راجع هذه المشاركة جو ميدلي وكايس باسكس.