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