Media Source Extensions (MSE) เป็น JavaScript API ที่ช่วยให้คุณสร้างสตรีมสำหรับการเล่นจากส่วนของเสียงหรือวิดีโอ แม้ว่าบทความนี้จะไม่ครอบคลุมถึง MSE แต่คุณจำเป็นต้องทำความเข้าใจ MSE หากต้องการฝังวิดีโอในเว็บไซต์ที่ทําสิ่งต่างๆ เช่น
- สตรีมมิงแบบปรับได้ ซึ่งเป็นอีกวิธีหนึ่งในการบอกว่าระบบจะปรับตามความสามารถของอุปกรณ์และสภาพเครือข่าย
- การต่อคลิปแบบปรับขนาดได้ เช่น การแทรกโฆษณา
- การเลื่อนเวลา
- การควบคุมประสิทธิภาพและขนาดการดาวน์โหลด
![โฟลว์ข้อมูล MSE พื้นฐาน](https://web.developers.google.cn/static/articles/media-mse-basics/image/basic-mse-data-flow-9f377a6841412.png?authuser=8&hl=th)
คุณอาจมอง 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>
- การจัดการข้อผิดพลาด
สำหรับใช้ในสภาพแวดล้อมที่ใช้งานจริง
ต่อไปนี้คือสิ่งที่เราขอแนะนําในการใช้งาน API ที่เกี่ยวข้องกับ MSE ในเวอร์ชันที่ใช้งานจริง
- ก่อนเรียกใช้ API เหล่านี้ ให้จัดการเหตุการณ์ข้อผิดพลาดหรือข้อยกเว้นของ API และตรวจสอบ
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.');
}
![แอตทริบิวต์แหล่งที่มาในรูปแบบบล็อก](https://web.developers.google.cn/static/articles/media-mse-basics/image/a-source-attribute-a-blo-8b1f76f582a22.png?authuser=8&hl=th)
การที่สามารถส่งออบเจ็กต์ MediaSource
ไปยังแอตทริบิวต์ src
อาจดูแปลกๆ อยู่บ้าง โดยปกติแล้วจะเป็นสตริง แต่อาจเป็น Blob ก็ได้
หากตรวจสอบหน้าเว็บที่มีสื่อที่ฝังและองค์ประกอบสื่อของหน้านั้น คุณจะเห็นสิ่งที่เราหมายถึง
อินสแตนซ์ 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 และตัวแปลงรหัส 2 รายการ สตริงนี้เป็นตัวสตริง mime สำหรับไฟล์วิดีโอ แต่ใช้ตัวแปลงรหัสแยกต่างหากสำหรับส่วนวิดีโอและเสียงของไฟล์
ข้อกำหนด MSE เวอร์ชัน 1 อนุญาตให้ User Agent กำหนดความต้องการที่แตกต่างกันได้ว่าจะกำหนดทั้งประเภท MIME และตัวแปลงรหัสหรือไม่ โดย User Agent บางรายการไม่กำหนด แต่อนุญาตให้ใช้เฉพาะประเภท MIME User Agent บางรายการ เช่น Chrome ต้องใช้ตัวแปลงรหัสสำหรับประเภท mime ที่ไม่ได้อธิบายตัวแปลงรหัสของตนเอง แทนที่จะพยายามจัดเรียงข้อมูลทั้งหมดนี้ คุณควรใส่ทั้ง 2 รายการ
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 API และ Promise ที่แสดงผลเพื่อเพิ่มความทันสมัย หากพยายามดำเนินการนี้ใน 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 ซึ่งเป็น 2 วิธีในการดำเนินการนี้ การอภิปรายหัวข้อนี้อย่างละเอียดอยู่นอกเหนือขอบเขตของบทนํานี้
ประมวลผลออบเจ็กต์การตอบกลับ
ดูเหมือนว่าโค้ดจะเกือบเสร็จแล้ว แต่สื่อไม่เล่น เราต้องรับข้อมูลสื่อจากออบเจ็กต์ Response
ไปยัง SourceBuffer
วิธีทั่วไปในการส่งข้อมูลจากออบเจ็กต์การตอบกลับไปยังอินสแตนซ์ MediaSource
คือการรับ ArrayBuffer
จากออบเจ็กต์การตอบกลับและส่งไปยัง SourceBuffer
เริ่มต้นด้วยการเรียกใช้ response.arrayBuffer()
ซึ่งจะแสดงผลเป็นสัญญากับบัฟเฟอร์ ในโค้ดของฉัน เราได้ส่ง Promise นี้ไปยังประโยค then()
ที่ 2 ซึ่งฉันจะเพิ่มต่อท้าย 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);
});
}