Media Source Extensions (MSE) เป็น JavaScript API ที่ช่วยให้คุณสร้างสตรีมสำหรับการเล่นจากส่วนของเสียงหรือวิดีโอ แม้ว่าจะไม่ได้กล่าวถึงในบทความนี้ แต่คุณจำเป็นต้องทำความเข้าใจ 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>
- การจัดการข้อผิดพลาด
สำหรับใช้ในสภาพแวดล้อมที่ใช้งานจริง
เราขอแนะนำการใช้งานจริงของ API ที่เกี่ยวข้องกับ MSE ดังต่อไปนี้
- ก่อนเรียกใช้ API เหล่านี้ ให้จัดการเหตุการณ์ข้อผิดพลาดหรือข้อยกเว้นของ API และตรวจสอบ
HTMLMediaElement.readyState
และMediaSource.readyState
ค่าเหล่านี้อาจเปลี่ยนแปลงได้ก่อนที่จะส่งเหตุการณ์ที่เกี่ยวข้อง - ตรวจสอบว่าการเรียกใช้
appendBuffer()
และremove()
ก่อนหน้าไม่ได้อยู่ระหว่างดำเนินการ โดยตรวจสอบค่าบูลีนSourceBuffer.updating
ก่อนที่จะอัปเดตmode
,timestampOffset
,appendWindowStart
,appendWindowEnd
หรือการเรียกappendBuffer()
หรือremove()
ในSourceBuffer
ของ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
อาจดูแปลกเล็กน้อย โดยปกติแล้วจะเป็นสตริง แต่อาจเป็น 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 ระบบจะใช้งานไม่ได้หากไม่มี 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 ซึ่งเป็น 2 วิธีในการดำเนินการนี้ การพูดคุยเกี่ยวกับหัวข้อนี้อย่างละเอียดอยู่นอกเหนือขอบเขตของบทนำนี้
ประมวลผลออบเจ็กต์การตอบกลับ
ดูเหมือนว่าโค้ดจะเกือบเสร็จแล้ว แต่สื่อไม่เล่น เราต้องรับข้อมูลสื่อจากออบเจ็กต์ 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);
});
}