Ekstensi Sumber Media

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

Media Source Extensions (MSE) adalah JavaScript API yang memungkinkan Anda membuat streaming untuk pemutaran dari segmen audio atau video. Meskipun tidak dibahas dalam artikel ini, pemahaman tentang MSE diperlukan jika Anda ingin menyematkan video di situs yang melakukan hal-hal seperti:

  • Streaming adaptif, yang merupakan cara lain untuk menyesuaikan dengan kemampuan perangkat dan kondisi jaringan
  • Penyambungan adaptif, seperti penyisipan iklan
  • Pergeseran waktu
  • Kontrol performa dan ukuran download
Aliran data MSE dasar
Gambar 1: Aliran data MSE dasar

Anda hampir bisa menganggap MSE sebagai jaringan. Seperti yang diilustrasikan dalam gambar, antara file yang didownload dan elemen media terdapat beberapa lapisan.

  • Elemen <audio> atau <video> untuk memutar media.
  • Instance MediaSource dengan SourceBuffer untuk memberi makan elemen media.
  • Panggilan fetch() atau XHR untuk mengambil data media dalam objek Response.
  • Panggilan ke Response.arrayBuffer() untuk memberi makan MediaSource.SourceBuffer.

Dalam praktiknya, rantai terlihat seperti ini:

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);
    });
}

Jika Anda dapat menyelesaikan masalah dari penjelasan sejauh ini, jangan ragu untuk berhenti membaca sekarang. Jika Anda ingin penjelasan yang lebih mendetail, lanjutkan membaca. Saya akan membahas rantai ini dengan membangun contoh MSE dasar. Setiap langkah build akan menambahkan kode ke langkah sebelumnya.

Catatan tentang kejelasan

Apakah artikel ini akan memberi tahu Anda semua hal yang perlu diketahui tentang memutar media di halaman web? Tidak, kode ini hanya dimaksudkan untuk membantu Anda memahami kode yang lebih rumit yang mungkin Anda temukan di tempat lain. Agar lebih jelas, dokumen ini menyederhanakan dan mengecualikan banyak hal. Kami yakin kita dapat melakukannya karena kami juga menyarankan penggunaan library seperti Shaka Player Google. Saya akan mencatat di mana saya sengaja menyederhanakan.

Beberapa hal yang tidak tercakup

Berikut adalah beberapa hal yang tidak akan saya bahas, tanpa urutan tertentu.

  • Kontrol pemutaran. Kita mendapatkannya secara gratis karena menggunakan elemen <audio> dan <video> HTML5.
  • Penanganan error.

Untuk digunakan di lingkungan produksi

Berikut beberapa hal yang saya rekomendasikan dalam penggunaan produksi API terkait MSE:

  • Sebelum melakukan panggilan pada API ini, tangani peristiwa error atau pengecualian API, lalu periksa HTMLMediaElement.readyState dan MediaSource.readyState. Nilai ini dapat berubah sebelum peristiwa terkait dikirim.
  • Pastikan panggilan appendBuffer() dan remove() sebelumnya masih dalam proses dengan memeriksa nilai boolean SourceBuffer.updating sebelum memperbarui mode, timestampOffset, appendWindowStart, appendWindowEnd SourceBuffer, atau memanggil appendBuffer() atau remove() di SourceBuffer.
  • Untuk semua instance SourceBuffer yang ditambahkan ke MediaSource, pastikan tidak ada nilai updating yang bernilai benar sebelum memanggil MediaSource.endOfStream() atau memperbarui MediaSource.duration.
  • Jika nilai MediaSource.readyState adalah ended, panggilan seperti appendBuffer() dan remove(), atau menetapkan SourceBuffer.mode atau SourceBuffer.timestampOffset akan menyebabkan nilai ini bertransisi ke open. Artinya, Anda harus bersiap untuk menangani beberapa peristiwa sourceopen.
  • Saat menangani peristiwa HTMLMediaElement error, konten MediaError.message dapat berguna untuk menentukan penyebab utama kegagalan, terutama untuk error yang sulit direproduksi di lingkungan pengujian.

Melampirkan instance MediaSource ke elemen media

Seperti banyak hal dalam pengembangan web saat ini, Anda memulai dengan deteksi fitur. Selanjutnya, dapatkan elemen media, baik elemen <audio> atau <video>. Terakhir, buat instance MediaSource. Media tersebut diubah menjadi URL dan diteruskan ke atribut sumber elemen media.

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.');
}
Atribut sumber sebagai blob
Gambar 1: Atribut sumber sebagai blob

Objek MediaSource dapat diteruskan ke atribut src mungkin tampak sedikit aneh. Biasanya berupa string, tetapi juga dapat berupa blob. Jika Anda memeriksa halaman dengan media tersemat dan memeriksa elemen medianya, Anda akan melihat maksud saya.

Apakah instance MediaSource sudah siap?

URL.createObjectURL() itu sendiri bersifat sinkron; tetapi, URL.createObjectURL() memproses lampiran secara asinkron. Hal ini menyebabkan sedikit penundaan sebelum Anda dapat melakukan apa pun dengan instance MediaSource. Untungnya, ada cara untuk mengujinya. Cara termudah adalah dengan properti MediaSource yang disebut readyState. Properti readyState menjelaskan hubungan antara instance MediaSource dan elemen media. Properti ini dapat memiliki salah satu nilai berikut:

  • closed - Instance MediaSource tidak dilampirkan ke elemen media.
  • open - Instance MediaSource dilampirkan ke elemen media dan siap untuk menerima data atau menerima data.
  • ended - Instance MediaSource dilampirkan ke elemen media dan semua datanya telah diteruskan ke elemen tersebut.

Mengkueri opsi ini secara langsung dapat berdampak negatif pada performa. Untungnya, MediaSource juga memicu peristiwa saat readyState berubah, khususnya sourceopen, sourceclosed, sourceended. Untuk contoh yang saya buat, saya akan menggunakan peristiwa sourceopen untuk memberi tahu saya kapan harus mengambil dan buffering video.

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>

Perhatikan bahwa saya juga memanggil revokeObjectURL(). Saya tahu ini terlalu dini, tetapi saya dapat melakukannya kapan saja setelah atribut src elemen media terhubung ke instance MediaSource. Memanggil metode ini tidak akan menghancurkan objek apa pun. Hal ini memang memungkinkan platform menangani pembersihan sampah pada waktu yang tepat, itulah sebabnya saya langsung memanggilnya.

Membuat SourceBuffer

Sekarang saatnya membuat SourceBuffer, yang merupakan objek yang benar-benar melakukan pekerjaan memindahkan data antara sumber media dan elemen media. SourceBuffer harus spesifik untuk jenis file media yang Anda muat.

Dalam praktiknya, Anda dapat melakukannya dengan memanggil addSourceBuffer() dengan nilai yang sesuai. Perhatikan bahwa dalam contoh di bawah ini, string jenis MIME berisi satu jenis mime dan dua codec. Ini adalah string mime untuk file video, tetapi menggunakan codec terpisah untuk bagian video dan audio file.

Versi 1 spesifikasi MSE memungkinkan agen pengguna membedakan apakah akan memerlukan jenis mime dan codec. Beberapa agen pengguna tidak memerlukannya, tetapi hanya mengizinkan jenis mime. Beberapa agen pengguna, misalnya Chrome, memerlukan codec untuk jenis mime yang tidak mendeskripsikan codec-nya sendiri. Daripada mencoba memilah semua hal ini, sebaiknya sertakan keduanya.

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>;
}

Mendapatkan file media

Jika melakukan penelusuran internet untuk contoh MSE, Anda akan menemukan banyak contoh yang mengambil file media menggunakan XHR. Agar lebih canggih, Saya akan menggunakan Fetch API dan Promise yang ditampilkannya. Jika Anda mencoba melakukannya di Safari, tindakan ini tidak akan berfungsi tanpa 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>;
}

Pemutar dengan kualitas produksi akan memiliki file yang sama dalam beberapa versi untuk mendukung browser yang berbeda. Fitur ini dapat menggunakan file terpisah untuk audio dan video agar audio dapat dipilih berdasarkan setelan bahasa.

Kode di dunia nyata juga akan memiliki beberapa salinan file media dengan resolusi yang berbeda sehingga dapat beradaptasi dengan berbagai kemampuan perangkat dan kondisi jaringan. Aplikasi semacam itu dapat memuat dan memutar video dalam potongan, baik menggunakan permintaan rentang atau segmen. Hal ini memungkinkan adaptasi dengan kondisi jaringan saat media diputar. Anda mungkin pernah mendengar istilah DASH atau HLS, yang merupakan dua metode untuk melakukannya. Diskusi lengkap tentang topik ini berada di luar cakupan pendahuluan.

Memproses objek respons

Kode terlihat hampir selesai, tetapi media tidak diputar. Kita perlu mendapatkan data media dari objek Response ke SourceBuffer.

Cara umum untuk meneruskan data dari objek respons ke instance MediaSource adalah dengan mendapatkan ArrayBuffer dari objek respons dan meneruskannya ke SourceBuffer. Mulai dengan memanggil response.arrayBuffer(), yang menampilkan promise ke buffer. Dalam kode saya, saya telah meneruskan promise ini ke klausa then() kedua tempat saya menambahkannya ke 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>
}

Memanggil endOfStream()

Setelah semua ArrayBuffers ditambahkan, dan tidak ada data media lebih lanjut yang diharapkan, panggil MediaSource.endOfStream(). Tindakan ini akan mengubah MediaSource.readyState menjadi ended dan memicu peristiwa 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);
    });
}

Versi akhir

Berikut adalah contoh kode lengkapnya. Semoga Anda telah mempelajari sesuatu tentang Ekstensi Media Sumber.

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);
    });
}

Masukan