Rozszerzenia źródeł multimediów (MSE) to interfejs API JavaScript, który umożliwia tworzenie strumieni do odtwarzania z segmentów audio lub wideo. Chociaż nie zostało to opisane w tym artykule, zrozumienie MSE jest niezbędne, jeśli chcesz umieszczać w swojej witrynie filmy, które umożliwiają:
- Adaptacyjne przesyłanie strumieniowe, które oznacza dostosowywanie się do możliwości urządzenia i warunków sieci.
- Łączenie adaptacyjne, np. wstawianie reklam
- Przesunięcie w czasie
- Kontrola wydajności i rozmiaru pobierania
MSE można porównać do łańcucha. Jak pokazano na ilustracji, między pobranym plikiem a elementami multimedialnymi znajduje się kilka warstw.
- Element
<audio>
lub<video>
służący do odtwarzania multimediów. - Wystąpienie
MediaSource
z atrybutemSourceBuffer
do przesyłania elementu multimedialnego. - Wywołanie
fetch()
lub XHR służące do pobierania danych multimedialnych w obiekcieResponse
. - Wywołanie metody
Response.arrayBuffer()
w celu pliku danychMediaSource.SourceBuffer
.
W praktyce łańcuch wygląda tak:
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);
});
}
Jeśli potrafisz rozwiązać ten problem, nie musisz już czytać. Jeśli potrzebujesz bardziej szczegółowych wyjaśnień, czytaj dalej. Przejdę do tego łańcucha, tworząc podstawowy przykład MSE. Każdy krok kompilacji spowoduje dodanie kodu do poprzedniego kroku.
Uwaga dotycząca przejrzystości
Czy ten artykuł zawiera wszystko, co musisz wiedzieć o odtwarzaniu multimediów na stronie internetowej? Nie. Ma na celu pomoc w zrozumieniu bardziej złożonego kodu, który można znaleźć w innym miejscu. Aby rozwiać wątpliwości: ten dokument upraszcza wiele aspektów i je wyklucza. Naszym zdaniem możemy temu zapobiec, ponieważ zalecamy korzystanie z biblioteki takiej jak Google Shaka Player. Będę zaznaczać, gdzie celowo je upraszczam.
Kilka kwestii
Oto kilka spraw, których nie będę uwzględniać, w kolejności ustalonej.
- Sterowanie odtwarzaniem. Są one udostępniane bezpłatnie, ponieważ użyto elementów
<audio>
HTML5 i<video>
. - Obsługa błędów –
Do użytku w środowiskach produkcyjnych
Oto kilka rzeczy, które polecam w środowisku produkcyjnym interfejsów API związanych z MSE:
- Przed wywołaniem tych interfejsów API zajmij się wszelkimi zdarzeniami błędów lub wyjątkami od interfejsów API i sprawdź
HTMLMediaElement.readyState
orazMediaSource.readyState
. Te wartości mogą się zmienić przed dostarczeniem powiązanych zdarzeń. - Aby upewnić się, że poprzednie wywołania
appendBuffer()
iremove()
nie są nadal wykonywane, sprawdź wartość logicznąSourceBuffer.updating
przed zaktualizowaniem parametrówmode
,timestampOffset
,appendWindowStart
lubappendWindowEnd
SourceBuffer
albo wywołaniu funkcjiappendBuffer()
lubremove()
w interfejsieSourceBuffer
. - Przed wywołaniem metody
MediaSource.endOfStream()
lub zaktualizowaniem metodyMediaSource.duration
w przypadku wszystkich instancjiSourceBuffer
dodanych doMediaSource
upewnij się, że żadna z ich wartościupdating
nie jest spełniony. - Jeśli
MediaSource.readyState
ma wartośćended
, wywołania takie jakappendBuffer()
lubremove()
albo ustawienieSourceBuffer.mode
lubSourceBuffer.timestampOffset
spowoduje zmianę tej wartości naopen
. Oznacza to, że musisz przygotować się na obsługę wielu zdarzeńsourceopen
. - W przypadku obsługi zdarzeń
HTMLMediaElement error
zawartośćMediaError.message
może być przydatna do określania głównej przyczyny błędu, szczególnie w przypadku błędów, które trudno jest odtworzyć w środowiskach testowych.
Dołączanie wystąpienia MediaSource do elementu multimedialnego
Podobnie jak w przypadku tworzenia stron internetowych, zaczynamy od wykrywania funkcji. Następnie pobierz element multimedialny, <audio>
lub <video>
.
Na koniec utwórz instancję MediaSource
. Jest on przekształcany w adres URL
i przekazywany do atrybutu źródła elementu multimedialnego.
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.');
}
To, że obiekt MediaSource
można przekazać do atrybutu src
, może wydawać się nieco niepasujący. Zwykle są to ciągi tekstowe, ale mogą być też blobami.
Jeśli przyjrzysz się stronie z osadzonymi elementami multimedialnymi i przyjrzysz się jej elementom multimedialnym,
zrozumiesz, o co mi chodzi.
Czy instancja MediaSource jest gotowa?
Komponent URL.createObjectURL()
jest synchroniczny, ale przetwarza załącznik asynchronicznie. Powoduje to niewielkie opóźnienie w wykonaniu jakichkolwiek czynności z instancją MediaSource
. Na szczęście można to sprawdzić na kilka sposobów.
Najprostszym sposobem jest użycie właściwości MediaSource
o nazwie readyState
. Właściwość readyState
opisuje relację między instancją MediaSource
a elementem multimedialnym. Może mieć jedną z tych wartości:
closed
– wystąpienieMediaSource
nie jest podłączone do elementu multimedialnego.open
– instancjaMediaSource
jest dołączona do elementu multimedialnego i jest gotowa do odbierania danych lub je odbiera.ended
– instancjaMediaSource
jest dołączona do elementu multimedialnego i wszystkie jej dane zostały przekazane do tego elementu.
Bezpośrednie zapytania dotyczące tych opcji mogą negatywnie wpłynąć na wydajność. Na szczęście MediaSource
uruchamia też zdarzenia po zmianie parametru readyState
, w szczególności sourceopen
, sourceclosed
, sourceended
. W tym przykładzie użyję zdarzenia sourceopen
, aby określić, kiedy pobrać i buforować film.
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>
Zwróć uwagę, że dzwonię też do: revokeObjectURL()
. Wiem, że to przedwcześnie, ale mogę to zrobić w każdej chwili, gdy atrybut src
elementu multimedialnego zostanie połączony z wystąpieniem MediaSource
. Wywołanie tej metody nie powoduje zniszczenia żadnych obiektów. Umożliwia platformie obsługę procesu usuwania odpadów w odpowiednim czasie, dlatego od razu to robię.
Tworzenie obiektu SourceBuffer
Pora utworzyć SourceBuffer
, czyli obiekt, który tak naprawdę rozdziela dane między źródłami multimediów i elementami multimedialnymi. Wartość SourceBuffer
musi odpowiadać typowi wczytywanego pliku multimedialnego.
W praktyce możesz to zrobić, wywołując addSourceBuffer()
z odpowiednią wartością. Zwróć uwagę, że w przykładzie poniżej ciąg znaków typu MIME zawiera typ MIME i 2 kodeki. Jest to ciąg MIME pliku wideo, ale dla fragmentów wideo i audio do tych plików wykorzystywane są osobne kodeki.
Wersja 1 specyfikacji MSE pozwala klientom użytkownika różnicować, czy wymagają zarówno typu MIME, jak i kodeka. Niektóre klienty użytkownika nie wymagają, ale zezwalają na stosowanie tylko typu MIME. Na przykład niektóre klienty użytkownika (Chrome) wymagają kodeka w przypadku typów MIME, które nie opisują swoich kodeków samodzielnie. Zamiast segregować wszystko od siebie, lepiej uwzględnić oba te elementy.
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>;
}
Pobierz plik multimedialny
Po wyszukaniu w internecie przykładów MSE znajdziesz sporo plików multimedialnych za pomocą XHR. Aby to zrobić, użyję interfejsu Fetch API i zwracanego przez niego Promise. Jeśli spróbujesz zrobić to w Safari, nie zadziała bez użycia 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>;
}
Odtwarzacz jakości produkcyjnej może zawierać ten sam plik w wielu wersjach dla różnych przeglądarek. Może użyć osobnych plików audio i wideo, aby umożliwić wybieranie dźwięku na podstawie ustawień języka.
Prawdziwy kod mógłby też mieć wiele kopii plików multimedialnych o różnej rozdzielczości, aby mógł dostosowywać się do różnych możliwości urządzenia i warunków sieci. Taka aplikacja może wczytywać i odtwarzać filmy fragmentami, korzystając z żądań zakresu lub segmentów. Dzięki temu można dostosować się do warunków sieciowych podczas odtwarzania multimediów. Być może znasz terminy DASH lub HLS, które to dwie metody. Pełny zakres dyskusji na ten temat wykracza poza zakres tego wprowadzenia.
Przetwarzanie obiektu odpowiedzi
Kod wygląda na prawie gotowy, ale multimedia się nie odtwarzają. Musimy pobrać dane mediów z obiektu Response
do SourceBuffer
.
Typowym sposobem na przekazanie danych z obiektu odpowiedzi do instancji MediaSource
jest pobranie obiektu ArrayBuffer
z obiektu odpowiedzi i przekazanie go do SourceBuffer
. Zacznij od wywołania metody response.arrayBuffer()
, która zwraca obietnicę do bufora. W kodzie przekazuję tę obietnicę do drugiej klauzuli then()
, gdzie dołączam ją do sekcji 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>
}
Wywołanie metody endOfStream()
Po dołączeniu wszystkich elementów ArrayBuffers
i nie powinny się już pojawiać dane dotyczące multimediów, wywołaj metodę MediaSource.endOfStream()
. Spowoduje to zmianę MediaSource.readyState
na ended
i wywoła zdarzenie 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);
});
}
Ostateczna wersja
Oto pełny przykład kodu. Mam nadzieję, że wiesz już coś o rozszerzeniach źródła multimediów.
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);
});
}