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

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

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

الفرق مع requestAnimationFrame()

في إطار جهودنا لتحقيق أفضل هدف، ستتم مزامنة عمليات مثل رسم إطار فيديو على لوحة رسم باستخدام drawImage() التي يتم إجراؤها من خلال واجهة برمجة التطبيقات هذه، وذلك مع عدد اللقطات في الثانية للفيديو الذي يتم تشغيله على الشاشة. على عكس window.requestAnimationFrame()، الذي يتم تنشيطه عادةً حوالي 60 مرة في الثانية، يرتبط requestVideoFrameCallback() بعدد اللقطات الفعلي في الثانية للفيديو، مع استثناء مهم:

ويكون المعدّل الفعلي لتنفيذ عمليات معاودة الاتصال هو المعدّل الأقل بين معدّل الفيديو وسعر المتصفّح. وهذا يعني أنّ الفيديو الذي يتم تشغيله بسرعة 25 لقطة في الثانية في متصفّح يبثّ حرارته 60 هرتز يؤدي إلى تنشيط معاودة الاتصال بمقدار 25 هرتز. ومن خلال فيديو بدقة 120 لقطة في الثانية في المتصفّح نفسه بنطاق 60 هرتز، يتم تنشيط عمليات معاودة الاتصال بمعدل 60 هرتز.

ماذا يجب أن يتضمّن الاسم؟

وبسبب التشابه بين هذه الطريقة وwindow.requestAnimationFrame()، تم في البداية اقتراح طريقةvideo.requestAnimationFrame() باسم video.requestAnimationFrame() وإعادة تسميتها إلى requestVideoFrameCallback()، وقد تم الاتفاق على هذه الطريقة بعد مناقشة مطوّلة.

رصد الميزات

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
  // The API is supported!
}

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

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

  • 83
  • 83
  • x
  • 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 واحد حتى تظهر التغييرات التي يتم إجراؤها على صفحة الويب من خلال واجهة برمجة التطبيقات على الشاشة (مثل window.requestAnimationFrame()). لذلك، إذا واصلت تعديل mediaTime أو رقم الإطار على صفحة الويب ومقارنة ذلك مقارنةً بإطارات الفيديو المُرقَّمة، سيبدو الفيديو في النهاية كما لو كان أمامك إطار واحد.

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