Ekstensi Sumber Media

Prancis Beaufort
François Beaufort
Joe Medley
Joe Medley

Media Source Extensions (MSE) adalah JavaScript API yang memungkinkan Anda mem-build streaming untuk diputar dari segmen audio atau video. Meskipun tidak dibahas dalam artikel ini, Anda perlu memahami MSE jika ingin menyematkan video di situs yang melakukan hal-hal seperti:

  • Streaming adaptif, yang merupakan cara lain untuk mengatakan beradaptasi 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 dapat menganggap MSE sebagai sebuah rantai. Seperti yang ditunjukkan dalam gambar, antara file yang didownload dan elemen media terdapat beberapa lapisan.

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

Dalam praktiknya, rantai tersebut akan 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 sudah bisa memahami penjelasan sejauh ini, jangan ragu untuk berhenti membaca sekarang. Jika Anda ingin mendapat penjelasan lebih rinci, silakan terus membaca. Saya akan membahas rantai ini dengan membuat contoh dasar pengukuran MSE. Setiap langkah build akan menambahkan kode ke langkah sebelumnya.

Catatan tentang kejelasan

Apakah artikel ini akan menjelaskan semua yang perlu Anda ketahui tentang memutar media di halaman web? Tidak, 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. Menurut kami, hal ini tidak akan berlaku karena kami juga merekomendasikan penggunaan library seperti Shka Player Google. Saya akan mencatat di mana saya sengaja menyederhanakannya.

Beberapa hal yang tidak dibahas

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

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

Untuk digunakan di lingkungan produksi

Berikut beberapa hal yang kami 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 ditayangkan.
  • Pastikan panggilan appendBuffer() dan remove() sebelumnya tidak masih berlangsung dengan memeriksa nilai boolean SourceBuffer.updating sebelum memperbarui mode, timestampOffset, appendWindowStart, appendWindowEnd SourceBuffer, atau memanggil appendBuffer() atau remove() pada SourceBuffer.
  • Untuk semua instance SourceBuffer yang ditambahkan ke MediaSource Anda, pastikan tidak ada nilai updating-nya yang benar sebelum memanggil MediaSource.endOfStream() atau mengupdate MediaSource.duration.
  • Jika nilai MediaSource.readyState adalah ended, panggilan seperti appendBuffer() dan remove(), atau menetapkan SourceBuffer.mode atau SourceBuffer.timestampOffset akan menyebabkan nilai ini ditransisikan ke open. Ini berarti Anda harus siap untuk menangani beberapa peristiwa sourceopen.
  • Saat menangani peristiwa HTMLMediaElement error, isi MediaError.message dapat berguna untuk menentukan akar penyebab 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. Data 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

Bahwa objek MediaSource dapat diteruskan ke atribut src mungkin tampak agak aneh. Kumpulan ini biasanya berupa string, tetapi bisa juga berupa blob. Saat memeriksa halaman dengan media tersemat dan memeriksa elemen medianya, Anda akan melihat yang dimaksud.

Apakah instance MediaSource sudah siap?

URL.createObjectURL() itu sendiri sinkron; tetapi akan memproses lampiran secara asinkron. Hal ini akan menyebabkan sedikit keterlambatan sebelum Anda dapat melakukan apa pun dengan instance MediaSource. Untungnya, ada cara untuk mengujinya. Cara paling mudah 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 menerima data atau sedang menerima data.
  • ended - Instance MediaSource dilampirkan ke elemen media dan semua datanya telah diteruskan ke elemen tersebut.

Membuat kueri terhadap opsi ini secara langsung dapat berdampak negatif pada performa. Untungnya, MediaSource juga mengaktifkan peristiwa saat readyState berubah, khususnya sourceopen, sourceclosed, sourceended. Pada contoh yang saya buat, saya akan menggunakan peristiwa sourceopen untuk memberi tahu kapan harus mengambil dan melakukan 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 hal ini sepertinya 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 memungkinkan platform menangani pembersihan sampah memori pada waktu yang tepat, itulah sebabnya saya memanggilnya segera.

Membuat SourceBuffer

Sekarang saatnya membuat SourceBuffer, yang merupakan objek yang benar-benar melakukan tugas beralih 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() bersama nilai yang sesuai. Perhatikan bahwa dalam contoh di bawah, string jenis mime berisi jenis mime dan dua codec. Ini adalah string mime untuk file video, tetapi menggunakan codec terpisah untuk bagian video dan audio dari file.

Versi 1 spesifikasi MSE memungkinkan agen pengguna membedakan apakah akan memerlukan jenis mime dan codec. Beberapa agen pengguna tidak memerlukan jenis MIME, tetapi hanya mengizinkan jenis mime. Beberapa agen pengguna, misalnya Chrome, memerlukan codec untuk jenis mime yang tidak mendeskripsikan codec-nya secara mandiri. Daripada mencoba memilah-milah semua ini, lebih baik menyertakan 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 Anda melakukan penelusuran internet untuk contoh MSE, Anda akan menemukan banyak file yang mengambil file media menggunakan XHR. Agar lebih canggih, saya akan menggunakan Fetch API dan Promise yang ditampilkannya. Jika Anda mencoba melakukannya di Safari, permintaan ini tidak akan dapat dilakukan 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 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 dunia nyata juga akan memiliki beberapa salinan file media pada resolusi yang berbeda sehingga dapat beradaptasi dengan kemampuan perangkat dan kondisi jaringan yang berbeda. Aplikasi tersebut dapat memuat dan memutar video dalam potongan, baik menggunakan permintaan rentang atau segmen. Hal ini memungkinkan adaptasi terhadap 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 pengantar ini.

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. Mulailah dengan memanggil response.arrayBuffer(), yang menampilkan promise ke buffer. Dalam kode, 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 mengaktifkan 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 final

Berikut contoh kode lengkapnya. Saya harap Anda telah belajar sesuatu tentang Media Source Extensions.

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