Media Source Extensions (MSE) هي واجهة برمجة تطبيقات JavaScript تتيح لك إنشاء أحداث بث لتشغيل مقاطع من الصوت أو الفيديو. على الرغم من أنّ هذه المقالة لا تتناول MSE، إلا أنّه يجب فهمه إذا كنت تريد تضمين فيديوهات في موقعك الإلكتروني تؤدي إجراءات مثل:
- ميزة "البث التكيُّفي"، وهي طريقة أخرى للتعبير عن التكيّف مع إمكانات الجهاز وظروف الشبكة
- الربط التكيُّفي، مثل إدراج الإعلانات
- تغيير الوقت
- التحكّم في الأداء وحجم التنزيل
يمكنك اعتبار MSE سلسلة تقريبًا. كما هو موضّح في الشكل، هناك عدة طبقات بين الملف الذي تم تنزيله وعناصر الوسائط.
- عنصر
<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);
});
}
إذا تمكّنت من معرفة ما عليك فعله من خلال الاطّلاع على الشرح، يمكنك التوقف عن قراءة هذه المقالة الآن. إذا كنت تريد شرحًا أكثر تفصيلاً، يُرجى مواصلة القراءة. سأشرح هذه السلسلة من خلال إنشاء مثال أساسي على MSE. ستُضيف كل خطوة من خطوات الإنشاء رمزًا إلى الخطوة السابقة.
ملاحظة حول الوضوح
هل تخبرك هذه المقالة بكل ما تحتاج إلى معرفته حول تشغيل الوسائط على صفحة ويب؟ لا، فالهدف منها هو مساعدتك فقط في فهم الرموز البرمجية الأكثر تعقيدًا التي قد تعثر عليها في مكان آخر. من أجل الوضوح، يبسط هذا المستند أشياء عديدة ويستبعدها. نعتقد أنّه يمكننا تنفيذ ذلك لأنّنا ننصح أيضًا باستخدام مكتبة مثل Shaka Player من Google. سألاحظ طوال الوقت الذي أقوم فيه بالتبسيط عمدًا.
بعض الأمور التي لا يشملها هذا الترخيص
هنا، بدون ترتيب معين، بعض الأشياء التي لن أتناولها.
- عناصر التحكّم في التشغيل ونحن نحصل عليها مجانًا بحكم استخدام عنصرَي HTML5
<audio>
و<video>
. - معالجة الأخطاء
للاستخدام في بيئات الإنتاج
في ما يلي بعض النقاط التي أنصحك باستخدامها عند استخدام واجهات برمجة التطبيقات المتعلّقة بالخطأ التربيعي المتوسط:
- قبل إجراء طلبات البيانات من واجهات برمجة التطبيقات هذه، عليك معالجة أي أحداث أخطاء أو
استثناءات لواجهات برمجة التطبيقات، والتحقّق من
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 من مواصفات الخطأ التربيعي المتوسط لوكلاء المستخدم بالاختلاف في ما إذا كان يجب طلب كل من نوع 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);
});
}