إضافات مصدر الوسائط (MSE) هي واجهة برمجة تطبيقات JavaScript تتيح لك إنشاء أحداث بث لتشغيلها من مقاطع الصوت أو الفيديو. على الرغم من عدم تناولها في هذه المقالة، يجب فهم الخطأ التربيعي المتوسط إذا كنت تريد تضمين فيديوهات في موقعك الإلكتروني تؤدي أشياء مثل:
- البث التكيُّفي، وهو ما يعنيه التكيف مع إمكانات الجهاز وظروف الشبكة
- الربط التكيُّفي، مثل إدراج الإعلان
- تحويل الوقت
- التحكّم في الأداء وحجم التنزيل
يمكنك اعتبار الخطأ التربيعي المتوسط كسلسلة. كما هو موضح في الشكل، توجد عدة طبقات بين الملف الذي تم تنزيله وعناصر الوسائط.
- عنصر
<audio>
أو<video>
لتشغيل الوسائط - مثيل
MediaSource
معSourceBuffer
لإطعام عنصر الوسائط. - طلب
fetch()
أو XHR لاسترداد بيانات الوسائط في عنصرResponse
- مكالمة إلى
Response.arrayBuffer()
لخلاصةMediaSource.SourceBuffer
.
من الناحية العملية، تبدو السلسلة على النحو التالي:
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}
إذا كان بإمكانك فرز الأشياء من التفسيرات حتى الآن، فلا تتردد في التوقف عن القراءة الآن. للحصول على شرح أكثر تفصيلاً، يُرجى مواصلة القراءة. سأقوم عبر هذه السلسلة ببناء مثال الخطأ التربيعي المتوسط الأساسي. ستضيف كل خطوة من خطوات الإنشاء تعليمة برمجية إلى الخطوة السابقة.
ملاحظة حول الوضوح
هل ستخبرك هذه المقالة بكل ما تحتاج إلى معرفته حول تشغيل الوسائط على صفحة ويب؟ لا، الهدف منها فقط مساعدتك في فهم التعليمات البرمجية الأكثر تعقيدًا التي قد تجدها في مكان آخر. من أجل الوضوح، تعمل هذه الوثيقة على تبسيط أشياء كثيرة واستبعادها. ونعتقد أنه يمكننا الإفلات من هذا الخطأ لأننا نوصي أيضًا باستخدام مكتبة مثل Google Shaka Player. وسوف ألاحظ في كل المواضع التي أعمد فيها إلى التبسيط.
بعض العناصر التي لم يتم تناولها
في ما يلي بعض الأمور التي لن أتناولها، بدون ترتيب محدّد.
- عناصر التحكّم في التشغيل نحصل على هذه الرموز مجانًا بفضل استخدام عناصر HTML5
<audio>
و<video>
. - حدث خطأ أثناء المعالجة.
للاستخدام في بيئات الإنتاج
وفي ما يلي بعض النصائح التي أنصحك بها عند استخدام واجهات برمجة التطبيقات ذات الصلة بالخطأ التربيعي المتوسط (MSE):
- قبل طلب البيانات من واجهات برمجة التطبيقات هذه، يُرجى معالجة أي أحداث أخطاء أو استثناءات من واجهة برمجة التطبيقات
والتحقّق من
HTMLMediaElement.readyState
وMediaSource.readyState
. يمكن أن تتغير هذه القيم قبل تسليم الأحداث المرتبطة. - تأكَّد من أنّ طلبات
appendBuffer()
وremove()
السابقة ما زالت قيد التقدم من خلال التأكّد من القيمة المنطقيةSourceBuffer.updating
قبل تعديلmode
أوtimestampOffset
أوappendWindowStart
أوappendWindowEnd
فيSourceBuffer
أو من خلال الاتصال بـappendBuffer()
أوremove()
علىSourceBuffer
. - بالنسبة إلى جميع مثيلات
SourceBuffer
التي تمت إضافتها إلىMediaSource
، تأكَّد من عدم ضبط أي من قيمupdating
الخاصة بها قبل استدعاءMediaSource.endOfStream()
أو تحديثMediaSource.duration
. - إذا كانت القيمة
MediaSource.readyState
هيended
، ستؤدي الطلبات مثلappendBuffer()
وremove()
أو ضبطSourceBuffer.mode
أوSourceBuffer.timestampOffset
إلى نقل هذه القيمة إلىopen
. هذا يعني أنك ينبغي أن تكون مستعدًا للتعامل مع أحداثsourceopen
متعددة. - عند التعامل مع أحداث
HTMLMediaElement error
، يمكن أن يكون محتوىMediaError.message
مفيدًا في تحديد السبب الجذري للتعذُّر، خاصةً للأخطاء التي يصعب تكرارها في بيئات الاختبار.
إرفاق مثيل MediaSource بعنصر وسائط
كما هو الحال مع العديد من الأمور في مجال تطوير الويب هذه الأيام، فإنّك تبدأ باكتشاف الميزات. عليك بعد ذلك الحصول على عنصر وسائط، وهو العنصر <audio>
أو <video>
.
أخيرًا، عليك إنشاء مثيل لـ MediaSource
. ويتم تحويله إلى عنوان URL وتمريره إلى سمة مصدر عنصر الوسائط.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
// Is the MediaSource instance ready?
} else {
console.log('The Media Source Extensions API is not supported.');
}
قد يبدو أنّ الكائن MediaSource
يمكن تمريره إلى سمة src
غريبة بعض الشيء. عادةً ما تكون سلاسل، لكنها
يمكن أن تكون أيضًا فقاعات.
إذا فحصت صفحة تحتوي على وسائط مضمّنة وفحصت عنصر الوسائط الخاص بها،
سترى ما أعنيه.
هل مثيل MediaSource جاهز؟
يعتبر عنصر URL.createObjectURL()
في حد ذاته متزامنًا، ومع ذلك، فإنه يعالج المرفق على نحو غير متزامن. يؤدي ذلك إلى تأخير بسيط قبل أن تتمكّن من تنفيذ أي إجراء
في مثيل MediaSource
. ولحسن الحظ، هناك طرق لاختبار هذا الأمر.
إنّ أبسط طريقة هي استخدام السمة MediaSource
التي تُسمى readyState
. تصف السمة readyState
العلاقة بين مثيل MediaSource
وعنصر وسائط. يمكن أن يحتوي على إحدى القيم التالية:
closed
- المثيلMediaSource
غير مرتبط بعنصر وسائط.open
- يتم إرفاق المثيلMediaSource
بعنصر وسائط ويكون جاهزًا لاستقبال البيانات أو استقبال البيانات.ended
- يتم إرفاق المثيلMediaSource
بعنصر وسائط وتم تمرير جميع بياناته إلى ذلك العنصر.
يمكن أن يؤثر الاستعلام عن هذه الخيارات بشكل مباشر سلبًا في الأداء. ولحسن الحظ،
يتم أيضًا تنشيط الأحداث من خلال ميزة MediaSource
عند إجراء تغيير على السمة readyState
، وتحديدًا
sourceopen
وsourceclosed
وsourceended
. في المثال الذي أنشئه، سأستخدم
الحدث sourceopen
لإخباري بوقت جلب الفيديو وتخزينه مؤقتًا.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
<strong>mediaSource.addEventListener('sourceopen', sourceOpen);</strong>
} else {
console.log("The Media Source Extensions API is not supported.")
}
<strong>function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
// Create a SourceBuffer and get the media file.
}</strong>
لاحظت أنّني اتصلتُ أيضًا بـ revokeObjectURL()
. أعلم أن هذا يبدو مبكرًا،
ولكن يمكنني إجراء ذلك في أي وقت بعد ربط سمة src
لعنصر الوسائط
بنسخة MediaSource
. لا يؤدي استدعاء هذه الطريقة إلى تدمير أي
كائنات. تسمح هذه الميزة للمنصة بالتعامل مع عملية جمع النفايات في
الوقت المناسب، ولهذا أسميها على الفور.
إنشاء SourceBuffer
حان الوقت الآن لإنشاء الكائن SourceBuffer
، وهو الكائن الذي يؤدي فعليًا عملية نقل البيانات بين مصادر الوسائط وعناصر الوسائط. ويجب أن يكون SourceBuffer
خاصًا بنوع ملف الوسائط الذي تحمّله.
من الناحية العملية، يمكنك إجراء ذلك من خلال طلب القيمة addSourceBuffer()
بالقيمة المناسبة. لاحظ أنّه في المثال أدناه، تحتوي سلسلة نوع mime على نوع MIME وبرنامجَي ترميز. هذه سلسلة MIME لملف فيديو، ولكنها تستخدم برامج ترميز منفصلة لأجزاء الفيديو والصوت في الملف.
يسمح الإصدار 1 من مواصفات MSE لوكلاء المستخدم بالاختلاف في ما إذا كان عليهم طلب كل من نوع MIME وبرنامج ترميز. بعض برامج وكيل المستخدم لا تتطلب ذلك، لكنها تسمح باستخدام نوع MIME فقط. يطلب بعض برامج وكيل المستخدم، مثل Chrome، برنامجًا ترميزيًا لأنواع MIME لا تصف برامج الترميز الخاصة بها بنفسها. بدلاً من محاولة فرز كل هذا، من الأفضل تضمين كليهما فقط.
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
<strong>
var mime = 'video/webm; codecs="opus, vp09.00.10.08"'; // e.target refers to
the mediaSource instance. // Store it in a variable so it can be used in a
closure. var mediaSource = e.target; var sourceBuffer =
mediaSource.addSourceBuffer(mime); // Fetch and process the video.
</strong>;
}
الحصول على ملف الوسائط
إذا أجريت بحثًا على الإنترنت عن أمثلة الخطأ التربيعي المتوسط، فستجد الكثير الذي يسترد ملفات الوسائط باستخدام XHR. لأكون أكثر تطورًا، سأستخدم واجهة برمجة التطبيقات
الجلب
والوعد الذي تعرضه. إذا كنت تحاول إجراء ذلك في Safari، لن تعمل هذه الميزة بدون
رمز polyfill fetch()
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
<strong>
fetch(videoUrl) .then(function(response){' '}
{
// Process the response object.
}
);
</strong>;
}
سيكون لدى مشغّل جودة الإنتاج الملف نفسه في إصدارات متعددة ليتوافق مع متصفحات مختلفة. يمكنه استخدام ملفات منفصلة للصوت والفيديو للسماح باختيار الصوت بناءً على إعدادات اللغة.
قد يكون لرمز الواقع الفعلي نسخ متعددة من ملفات الوسائط بدرجات دقة مختلفة بحيث يمكن أن يتكيف مع إمكانات الجهاز وظروف الشبكة المختلفة. ويمكن لمثل هذا التطبيق تحميل مقاطع الفيديو وتشغيلها في أجزاء باستخدام طلبات النطاق أو المقاطع. ويتيح ذلك التكيف مع ظروف الشبكة أثناء تشغيل الوسائط. ربما سمعت مصطلحات DASH أو HLS، وهما طريقتان لتحقيق ذلك. المناقشة الكاملة لهذا الموضوع تخرج عن نطاق هذه المقدمة.
معالجة كائن الاستجابة
يبدو أنّ الرمز قد اكتمل تقريبًا، ولكن لا يتم تشغيل الوسائط. نحتاج إلى الحصول على بيانات
الوسائط من الكائن Response
إلى SourceBuffer
.
الطريقة المعتادة لتمرير البيانات من كائن الاستجابة إلى مثيل MediaSource
هي الحصول على ArrayBuffer
من كائن الاستجابة وتمريرها إلى SourceBuffer
. ابدأ بطلب البيانات من خلال response.arrayBuffer()
، والتي تعرض
وعدًا للمورّد الاحتياطي. في الرمز الخاص بي، نقلتُ هذا الوعد إلى بند then()
ثانٍ،
حيث ألحقه بـ SourceBuffer
.
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
<strong>return response.arrayBuffer();</strong>
})
<strong>.then(function(arrayBuffer) {
sourceBuffer.appendBuffer(arrayBuffer);
});</strong>
}
استدعاء endOfStream()
بعد أن يتم إلحاق جميع ArrayBuffers
، ولا يتوفّر المزيد من بيانات الوسائط، يمكنك طلب MediaSource.endOfStream()
. سيؤدي هذا الإجراء إلى تغيير MediaSource.readyState
إلى
ended
وتنشيط حدث "sourceended
".
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function(response) {
return response.arrayBuffer();
})
.then(function(arrayBuffer) {
<strong>sourceBuffer.addEventListener('updateend', function(e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});</strong>
sourceBuffer.appendBuffer(arrayBuffer);
});
}
النسخة النهائية
إليك مثال التعليمة البرمجية الكامل. آمل أن تكون قد تعلمت شيئًا عن إضافات مصادر الوسائط
var vidElement = document.querySelector('video');
if (window.MediaSource) {
var mediaSource = new MediaSource();
vidElement.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.log('The Media Source Extensions API is not supported.');
}
function sourceOpen(e) {
URL.revokeObjectURL(vidElement.src);
var mime = 'video/webm; codecs="opus, vp09.00.10.08"';
var mediaSource = e.target;
var sourceBuffer = mediaSource.addSourceBuffer(mime);
var videoUrl = 'droid.webm';
fetch(videoUrl)
.then(function (response) {
return response.arrayBuffer();
})
.then(function (arrayBuffer) {
sourceBuffer.addEventListener('updateend', function (e) {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
});
sourceBuffer.appendBuffer(arrayBuffer);
});
}