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 เพื่อให้ล้ำสมัยมากขึ้น
ผมจะใช้ API การดึงข้อมูล
และฟังก์ชัน Promise ซึ่งให้ผลลัพธ์ หากคุณพยายามทำเช่นนี้ใน Safari การทำเช่นนั้นจะไม่ทำงานหากไม่มีโพลีฟิลล์ 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()
ซึ่งจะแสดงผลบัฟเฟอร์ ในโค้ด ฉันได้ส่งผ่านคำสัญญานี้ไปยังวรรคที่ 2
ของ 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);
});
}