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
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
denganSourceBuffer
untuk memberi makan elemen media. - Panggilan
fetch()
atau XHR untuk mengambil data media dalam objekResponse
. - Panggilan ke
Response.arrayBuffer()
untuk memberi makanMediaSource.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
danMediaSource.readyState
. Nilai ini dapat berubah sebelum peristiwa terkait dikirim. - Pastikan panggilan
appendBuffer()
danremove()
sebelumnya masih dalam proses dengan memeriksa nilai booleanSourceBuffer.updating
sebelum memperbaruimode
,timestampOffset
,appendWindowStart
,appendWindowEnd
SourceBuffer
, atau memanggilappendBuffer()
atauremove()
diSourceBuffer
. - Untuk semua instance
SourceBuffer
yang ditambahkan keMediaSource
, pastikan tidak ada nilaiupdating
yang bernilai benar sebelum memanggilMediaSource.endOfStream()
atau memperbaruiMediaSource.duration
. - Jika nilai
MediaSource.readyState
adalahended
, panggilan sepertiappendBuffer()
danremove()
, atau menetapkanSourceBuffer.mode
atauSourceBuffer.timestampOffset
akan menyebabkan nilai ini bertransisi keopen
. Artinya, Anda harus bersiap untuk menangani beberapa peristiwasourceopen
. - Saat menangani peristiwa
HTMLMediaElement error
, kontenMediaError.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.');
}
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
- InstanceMediaSource
tidak dilampirkan ke elemen media.open
- InstanceMediaSource
dilampirkan ke elemen media dan siap untuk menerima data atau menerima data.ended
- InstanceMediaSource
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);
});
}