Jak przyspieszyć odtwarzanie multimediów przez aktywne wstępne wczytywanie zasobów.
Szybsze rozpoczynanie odtwarzania oznacza, że więcej osób ogląda Twój film lub słucha audio. To znany fakt. W tym artykule omówię Za pomocą tych technik możesz przyspieszyć odtwarzanie dźwięku i wideo, aktywnie wstępne ładowanie zasobów w zależności od przypadku użycia.
Omówię 3 metody wstępnego wczytywania plików multimedialnych, zaczynając od profesjonalistów i wady.
Wspaniale... | Ale... | |
---|---|---|
Atrybut wstępnego wczytywania filmu | Prosta w użyciu w przypadku unikalnego pliku hostowanego na serwerze WWW. | Przeglądarki mogą całkowicie zignorować ten atrybut. |
Pobieranie zasobów rozpoczyna się po całkowitym wczytaniu dokumentu HTML przeanalizowano. | ||
Rozszerzenia Media Source Extensions (MSE) ignorują atrybut preload w elementach multimedialnych, ponieważ to aplikacja jest odpowiedzialna za
dostarczania multimediów do MSE.
|
||
Wstępne wczytywanie linków |
Wymusza na przeglądarce wysłanie żądania dotyczącego zasobu wideo bez blokowania
zdarzenie onload dokumentu.
|
Żądania zakresu HTTP są niezgodne. |
Zgodny z MSE i segmentami plików. | Powinna być używana tylko w przypadku małych plików multimedialnych (poniżej 5 MB) podczas pobierania pełnych zasobów. | |
Buforowanie ręczne | Pełna kontrola | Za obsługę złożonych błędów odpowiada właściciel witryny. |
Atrybut wstępnego wczytywania filmu
Jeśli źródłem wideo jest unikalny plik przechowywany na serwerze WWW, warto
użyj atrybutu wideo preload
, aby przekazać przeglądarce wskazówkę, jak to zrobić.
dużo informacji lub treści do wstępnego wczytania. Oznacza to, że rozszerzenia źródeł multimediów
(MSE) jest niezgodny z tabelą preload
.
Pobieranie zasobów rozpocznie się dopiero wtedy, gdy początkowy dokument HTML zostanie
w pełni wczytane i przeanalizowane (np. uruchomienie zdarzenia DOMContentLoaded
)
podczas gdy bardzo różne zdarzenie load
będzie wywoływane, gdy zasób
został pobrany.
Jeśli atrybut preload
ma wartość metadata
, oznacza to, że użytkownik nie
powinien potrzebować wideo, ale pobieranie jego metadanych (wymiary, śledzenie
lista, czas trwania itd.) jest pożądane. Pamiętaj, że od Chrome
64, wartość domyślna preload
to metadata
. (Było auto
wcześniej).
<video id="video" preload="metadata" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
});
</script>
Ustawienie atrybutu preload
na auto
oznacza, że przeglądarka może buforować dane.
dostatecznie dużo danych, by odtwarzanie całkowite było możliwe bez konieczności zatrzymywania
dalsze buforowanie.
<video id="video" preload="auto" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
});
</script>
Jest jednak kilka zastrzeżeń. To tylko wskazówka, że przeglądarka może całkowicie
zignoruj atrybut preload
. W momencie pisania tych zasad możemy zapoznać się z tymi zasadami
zastosowane w Chrome:
- Gdy włączone jest Oszczędzanie danych, Chrome wymusza wartość
preload
nanone
- W Androidzie 4.3 Chrome wymusza wartość
preload
nanone
ze względu na Androida Błąd. - W przypadku połączenia komórkowego (2G, 3G i 4G) Chrome wymusza wartość
preload
nametadata
Wskazówki
Jeśli witryna zawiera wiele zasobów wideo w tej samej domenie,
zalecamy ustawienie wartości preload
na metadata
lub zdefiniowanie poster
i ustaw preload
na none
. Dzięki temu unikniesz klikania
maksymalną liczbę połączeń HTTP z tą samą domeną (6 zgodnie z
specyfikacji HTTP 1.1), które mogą powodować opóźnienia ładowania zasobów. Pamiętaj, że może to też
popraw szybkość stron, jeśli filmy nie są główną częścią usługi.
Wstępne wczytywanie linków
Jak omówiliśmy w innych artykułach, wstępne wczytywanie linków to deklaratywne pobieranie,
pozwala wymusić na przeglądarce wysłanie żądania do zasobu bez
blokowanie zdarzenia load
i podczas pobierania strony. Zasoby
wczytywane przez <link rel="preload">
są przechowywane lokalnie w przeglądarce i są
działają bezwzględnie, dopóki nie zostaną wyraźnie przywoływane w DOM, JavaScript
lub CSS.
Wstępne wczytywanie różni się od pobierania z wyprzedzeniem, ponieważ koncentruje się na bieżącej nawigacji pobiera zasoby o priorytecie na podstawie ich typu (skrypt, styl, czcionka wideo, audio itp.). Powinien być używany do ogrzania pamięci podręcznej przeglądarki na potrzeby bieżącego sesji.
Wstępnie wczytuj cały film
Oto jak wczytać wstępnie pełny film na stronie, JavaScript prosi o pobranie treści wideo. Jest on odczytywany z pamięci podręcznej jako zasób mogły już zostać zapisane w pamięci podręcznej przeglądarki. Jeśli żądanie wstępnego wczytywania nie zostało nie zostało zakończone, odbywa się zwykłe pobieranie sieciowe.
<link rel="preload" as="video" href="https://cdn.com/small-file.mp4">
<video id="video" controls></video>
<script>
// Later on, after some condition has been met, set video source to the
// preloaded video URL.
video.src = 'https://cdn.com/small-file.mp4';
video.play().then(() => {
// If preloaded video URL was already cached, playback started immediately.
});
</script>
Wstępnie załadowany zasób zostanie wykorzystany przez element wideo w
W tym przykładzie wartość linku wstępnego wczytywania as
to video
. Jeśli to dźwięk
element, będzie to as="audio"
.
Wstępnie wczytuj pierwszy segment
Poniższy przykład pokazuje, jak wstępnie wczytać pierwszy segment filmu za pomocą parametru <link
rel="preload">
i używać go z rozszerzeniami źródła multimediów. Jeśli nie znasz usługi,
przy użyciu interfejsu MSE JavaScript API, zapoznaj się z podstawowymi informacjami o MSE.
Dla uproszczenia załóżmy, że cały film został podzielony na
mniejsze pliki, takie jak file_1.webm
, file_2.webm
, file_3.webm
itp.
<link rel="preload" as="fetch" href="https://cdn.com/file_1.webm">
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// If video is preloaded already, fetch will return immediately a response
// from the browser cache (memory cache). Otherwise, it will perform a
// regular network fetch.
fetch('https://cdn.com/file_1.webm')
.then(response => response.arrayBuffer())
.then(data => {
// Append the data into the new sourceBuffer.
sourceBuffer.appendBuffer(data);
// TODO: Fetch file_2.webm when user starts playing video.
})
.catch(error => {
// TODO: Show "Video is not available" message to user.
});
}
</script>
Pomoc
Możesz wykryć obsługę różnych typów as
w witrynie <link rel=preload>
za pomocą
poniżej:
function preloadFullVideoSupported() {
const link = document.createElement('link');
link.as = 'video';
return (link.as === 'video');
}
function preloadFirstSegmentSupported() {
const link = document.createElement('link');
link.as = 'fetch';
return (link.as === 'fetch');
}
Buforowanie ręczne
Zanim przejdziemy do omówienia interfejsu Cache API i mechanizmów Service Worker
jak ręcznie buforować film za pomocą MSE. W przykładzie poniżej zakładamy, że Twoja witryna
serwer obsługuje HTTP Range
ale działa to podobnie w przypadku plików.
segmentów niestandardowych. Pamiętaj, że niektóre biblioteki oprogramowania pośredniczącego, takie jak Google Shaka
Player, JW Player i Video.js
stworzonych po to, aby sprostać tym potrzebom.
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// Fetch beginning of the video by setting the Range HTTP request header.
fetch('file.webm', { headers: { range: 'bytes=0-567139' } })
.then(response => response.arrayBuffer())
.then(data => {
sourceBuffer.appendBuffer(data);
sourceBuffer.addEventListener('updateend', updateEnd, { once: true });
});
}
function updateEnd() {
// Video is now ready to play!
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
// Fetch the next segment of video when user starts playing the video.
video.addEventListener('playing', fetchNextSegment, { once: true });
}
function fetchNextSegment() {
fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })
.then(response => response.arrayBuffer())
.then(data => {
const sourceBuffer = mediaSource.sourceBuffers[0];
sourceBuffer.appendBuffer(data);
// TODO: Fetch further segment and append it.
});
}
</script>
Uwagi
Masz teraz kontrolę nad całym procesem buforowania multimediów, więc zalecamy sprawdzić poziom baterii w urządzeniu, tryb oszczędzania danych preferencji użytkownika informacje o sieci podczas wstępnego wczytywania.
Znajomość baterii
Weź pod uwagę poziom naładowania baterii urządzeń, zanim pomyślą na temat wstępnego wczytywania filmu. Pozwoli to wydłużyć czas pracy na baterii, gdy poziom naładowania baterii jest niski.
Wyłącz wstępne wczytywanie lub przynajmniej wczytuj filmy w niższej rozdzielczości, gdy w urządzeniu kończy się bateria.
if ('getBattery' in navigator) {
navigator.getBattery()
.then(battery => {
// If battery is charging or battery level is high enough
if (battery.charging || battery.level > 0.15) {
// TODO: Preload the first segment of a video.
}
});
}
Wykrywanie funkcji „Oszczędzanie danych”
Użyj nagłówka żądania wskazówek dla klienta Save-Data
, aby przyspieszyć i łatwiej realizować działania
użytkownikom, którzy wyrazili zgodę na „oszczędność danych” w swoim trybie
przeglądarki. Dzięki zidentyfikowaniu tego nagłówka żądania aplikacja może dostosowywać i
są zoptymalizowane pod kątem wrażeń użytkowników
z ograniczonym kosztem i wydajnością
użytkowników.
Więcej informacji znajdziesz w artykule o przesyłaniu szybkich i lekkich aplikacji z opcją oszczędzania danych.
Inteligentne ładowanie na podstawie informacji o sieci
Warto sprawdzić navigator.connection.type
przed rozpoczęciem wstępnego wczytywania. Kiedy
ma wartość cellular
, możesz zapobiec wstępnemu ładowaniu i zalecić użytkownikom
jego operator sieci komórkowej może pobierać opłaty za przepustowość i dopiero wtedy
automatycznego odtwarzania treści zapisanych w pamięci podręcznej.
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
Zapoznaj się z przykładem informacji o sieci, aby dowiedzieć się, jak zareagować na sieć jego zmiany.
Wstępnie zapisuj wiele pierwszych segmentów w pamięci podręcznej
A co, jeśli chcę spekulować wcześniej ładować treści multimedialne bez
wiedząc, na jaki media użytkownik ostatecznie wybierze? Jeśli użytkownik korzysta z
która zawiera 10 filmów,
prawdopodobnie mamy wystarczająco dużo pamięci, aby pobrać jeden
segmentacji z każdego pliku, ale na pewno nie powinniśmy tworzyć 10 ukrytych <video>
oraz 10 obiektów MediaSource
i zacznij dodawać te dane.
W dwuczęściowym przykładzie poniżej pokazujemy, jak wstępnie zapisać wiele pierwszych segmentów w pamięci podręcznej
zaawansowany i łatwy w obsłudze interfejs Cache API. Zauważ, że coś podobnego
którą można osiągnąć za pomocą IndexedDB. Nie używamy jeszcze mechanizmów Service Worker,
interfejs Cache API jest dostępny również z poziomu obiektu window
.
Pobierz i buforuj
const videoFileUrls = [
'bat_video_file_1.webm',
'cow_video_file_1.webm',
'dog_video_file_1.webm',
'fox_video_file_1.webm',
];
// Let's create a video pre-cache and store all first segments of videos inside.
window.caches.open('video-pre-cache')
.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));
function fetchAndCache(videoFileUrl, cache) {
// Check first if video is in the cache.
return cache.match(videoFileUrl)
.then(cacheResponse => {
// Let's return cached response if video is already in the cache.
if (cacheResponse) {
return cacheResponse;
}
// Otherwise, fetch the video from the network.
return fetch(videoFileUrl)
.then(networkResponse => {
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, networkResponse.clone());
return networkResponse;
});
});
}
Gdybym używać żądań HTTP Range
, trzeba było ręcznie utworzyć ponownie
obiekt Response
, ponieważ interfejs Cache API nie obsługuje jeszcze odpowiedzi Range
. Bądź
pamiętaj, że wywołanie metody networkResponse.arrayBuffer()
powoduje pobranie całej treści
odpowiedzi możesz wysłać do pamięci mechanizmu renderowania, dlatego warto użyć funkcji
małe zakresy.
Część powyższego przykładu została zmodyfikowana, aby zapisać zakres HTTP. żądania do pamięci podręcznej filmu.
...
return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })
.then(networkResponse => networkResponse.arrayBuffer())
.then(data => {
const response = new Response(data);
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, response.clone());
return response;
});
Odtwórz film
Gdy użytkownik kliknie przycisk odtwarzania, zostanie pobrany pierwszy segment filmu. dostępne w interfejsie Cache API, dzięki czemu odtwarzanie rozpocznie się natychmiast, jeśli będzie to możliwe. W przeciwnym razie po prostu pobierzemy go z sieci. Pamiętaj, że przeglądarki a użytkownicy mogą wyczyścić pamięć podręczną.
Jak widzieliśmy wcześniej, używamy MSE, by przekazać ten pierwszy segment filmu do filmu. .
function onPlayButtonClick(videoFileUrl) {
video.load(); // Used to be able to play video later.
window.caches.open('video-pre-cache')
.then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.
.then(response => response.arrayBuffer())
.then(data => {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
sourceBuffer.appendBuffer(data);
video.play().then(() => {
// TODO: Fetch the rest of the video when user starts playing video.
});
}
});
}
Tworzenie odpowiedzi zakresu za pomocą skryptu service worker
A co, jeśli pobierzesz cały plik wideo i zapiszesz go
interfejs API Cache? Gdy przeglądarka wysyła żądanie HTTP Range
, na pewno nie
chcesz umieścić w pamięci mechanizmu renderowania cały film, ponieważ
obsługują jeszcze Range
odpowiedzi.
Pokażę więc, jak przechwycić te żądania i zwrócić dostosowane Range
ze skryptu service worker.
addEventListener('fetch', event => {
event.respondWith(loadFromCacheOrFetch(event.request));
});
function loadFromCacheOrFetch(request) {
// Search through all available caches for this request.
return caches.match(request)
.then(response => {
// Fetch from network if it's not already in the cache.
if (!response) {
return fetch(request);
// Note that we may want to add the response to the cache and return
// network response in parallel as well.
}
// Browser sends a HTTP Range request. Let's provide one reconstructed
// manually from the cache.
if (request.headers.has('range')) {
return response.blob()
.then(data => {
// Get start position from Range request header.
const pos = Number(/^bytes\=(\d+)\-/g.exec(request.headers.get('range'))[1]);
const options = {
status: 206,
statusText: 'Partial Content',
headers: response.headers
}
const slicedResponse = new Response(data.slice(pos), options);
slicedResponse.setHeaders('Content-Range': 'bytes ' + pos + '-' +
(data.size - 1) + '/' + data.size);
slicedResponse.setHeaders('X-From-Cache': 'true');
return slicedResponse;
});
}
return response;
}
}
Należy zauważyć, że do odtworzenia tego wycinka służy mi usługa response.blob()
ponieważ daje mi to po prostu nick do pliku,
response.arrayBuffer()
umieszcza cały plik w pamięci mechanizmu renderowania.
Mój niestandardowy nagłówek HTTP X-From-Cache
może być używany do sprawdzania, czy to żądanie
z pamięci podręcznej lub z sieci. Może go używać gracz, taki jak
ShakaPlayer, aby zignorować czas odpowiedzi jako wskaźnik
oraz ich prędkość sieci.
Przyjrzyj się oficjalnej aplikacji Sample Media, a w szczególności
pliku ranged-response.js, aby uzyskać kompletne rozwiązanie do obsługi protokołu Range
.
żądań.