تعرَّف على كيفية استخدام requestVideoFrameCallback()
للعمل بكفاءة أكبر مع الفيديوهات في المتصفّح.
تسمح طريقة HTMLVideoElement.requestVideoFrameCallback()
لمؤلفي الويب بتسجيل دالة استدعاء
يتم تنفيذها في خطوات المعالجة عند إرسال إطار فيديو جديد إلى أداة الدمج.
يتيح ذلك للمطوّرين تنفيذ عمليات فعّالة على كل إطار من الفيديو،
مثل معالجة الفيديو ورسمه على لوحة أو تحليل الفيديو
أو مزامنته مع مصادر صوتية خارجية.
الفرق مع requestAnimationFrame()
إنّ العمليات، مثل رسم إطار فيديو على لوحة باستخدام
drawImage()
التي يتم إجراؤها من خلال واجهة برمجة التطبيقات هذه، ستتم مزامنتها على أفضل وجه
مع معدل عرض اللقطات للفيديو الذي يتم تشغيله على الشاشة.
على عكس العنصر
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
يتوفّر عنصر 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);
في دالة ردّ الاتصال، 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()
يتم تشغيله على سلسلة المهام الرئيسية، ولكن يتم دمج الفيديو في سلسلة مهام المُركّب،
يتم تنفيذ كل الإجراءات من واجهة برمجة التطبيقات هذه بأفضل ما يمكن، ولا يقدّم المتصفّح أي ضمانات صارمة.
قد يكون السبب أنّ واجهة برمجة التطبيقات متأخرة عن vsync بمقدار دورة واحدة مقارنةً بوقت عرض إطار الفيديو.
يستغرق ظهور التغييرات التي يتم إجراؤها على صفحة الويب من خلال واجهة برمجة التطبيقات على الشاشة دورة واحدة من vsync (مثل window.requestAnimationFrame()
).
لذلك، إذا واصلت تعديل mediaTime
أو رقم اللقطة على صفحة الويب وقارنت ذلك
بإطارات الفيديو المرقّمة، سيبدو الفيديو في النهاية وكأنه يعرض لقطة واحدة متقدّمة.
ما يحدث فعلاً هو أنّ اللقطة تكون جاهزة عند vsync x، ويتم تشغيل دالة الاستدعاء وعرض اللقطة عند vsync x+1،
ويتم عرض التغييرات التي تم إجراؤها في دالة الاستدعاء عند vsync x+2.
يمكنك التحقّق مما إذا كان المرجع المُعاد الاتصال به هو تأخُّر في vsync (وقد تم عرض اللقطة على الشاشة)
من خلال التحقّق مما إذا كان metadata.expectedDisplayTime
هو now
تقريبًا أو 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()
هذا الحلّ البديل بشكل كبير.
الشكر والتقدير
تم تحديد واجهة برمجة التطبيقات requestVideoFrameCallback
وتنفيذها من قِبل
توماس غويلبيرت.
تمت مراجعة هذه المشاركة من قِبل جو ميدلي
وكايسي باسكيز.
الصورة الرئيسية من أعمال
دينيس جانز على Unsplash