تنفيذ عمليات فعالة لكل إطار فيديو في الفيديو باستخدام الدالة requestVideoFrameCallback()

تعرَّف على كيفية استخدام requestVideoFrameCallback() للعمل بكفاءة أكبر مع الفيديوهات في المتصفّح.

تسمح طريقة HTMLVideoElement.requestVideoFrameCallback() لمؤلفي الويب بتسجيل دالة استدعاء يتم تنفيذها في خطوات المعالجة عند إرسال إطار فيديو جديد إلى أداة الدمج. ويسمح ذلك للمطوّرين بإجراء عمليات فعّالة لكل إطار فيديو على الفيديو، مثل معالجة الفيديو ورسمه على لوحة رسم أو تحليل الفيديو أو المزامنة مع مصادر الصوت الخارجية.

وستتم مزامنة عمليات مثل رسم إطار فيديو إلى لوحة رسم باستخدام 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!
}

دعم المتصفح

توافق المتصفّح

  • Chrome: 83
  • الحافة: 83.
  • ‫Firefox: 132
  • ‫Safari: 15.4

المصدر

حشو بوليستر

تتوفر 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