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 من مواصفات 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>;
}
الحصول على ملف الوسائط
إذا أجريت بحثًا على الإنترنت عن أمثلة على MSE، ستجد الكثير من الأمثلة التي تسترجع
ملفات الوسائط باستخدام XHR. لتكون أكثر حداثة،
سأستخدم واجهة برمجة التطبيقات Fetch
والوعد الذي
تعرضه. إذا كنت تحاول إجراء ذلك في Safari، لن يعمل بدون استخدام مكتبة
fetch()
polyfill.
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);
});
}