ส่วนขยายแหล่งที่มาของสื่อ

François Beaufort
François Beaufort
Joe Medley
Joe Medley

Media Source Extensions (MSE) เป็น JavaScript API ที่ช่วยให้คุณสร้างสตรีมสำหรับการเล่นจากส่วนของเสียงหรือวิดีโอ แม้ว่าจะไม่ได้กล่าวถึงในบทความนี้ แต่คุณจำเป็นต้องทำความเข้าใจ MSE หากต้องการฝังวิดีโอในเว็บไซต์ซึ่งทำสิ่งต่างๆ เช่น

  • สตรีมมิงแบบปรับได้ ซึ่งเป็นอีกวิธีหนึ่งในการบอกว่าระบบจะปรับตามความสามารถของอุปกรณ์และสภาพเครือข่าย
  • การเชื่อมต่อแบบปรับอัตโนมัติ เช่น การแทรกโฆษณา
  • การเลื่อนเวลา
  • การควบคุมประสิทธิภาพและขนาดการดาวน์โหลด
โฟลว์ข้อมูล MSE พื้นฐาน
รูปที่ 1: การรับส่งข้อมูล 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.');
}
แอตทริบิวต์แหล่งที่มาในรูปแบบบล็อก
รูปที่ 1: แอตทริบิวต์แหล่งที่มาในรูปแบบบล็อก

การส่งออบเจ็กต์ 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);
    });
}

ความคิดเห็น