Come accelerare la riproduzione di contenuti multimediali precaricando attivamente le risorse.
Una riproduzione più rapida si traduce in un numero maggiore di persone che guardano il tuo video o ascoltano il tuo audio. È un fatto risaputo. In questo articolo, esplorerai tecniche che puoi utilizzare per accelerare la riproduzione audio e video di precaricamento delle risorse a seconda del caso d'uso.
descriverò tre metodi per precaricare i file multimediali, a partire dai loro professionisti e contro.
È magnifico... | Ma… | |
---|---|---|
Attributo di precaricamento video | Semplice da utilizzare per un file univoco ospitato su un server web. | I browser potrebbero ignorare completamente l'attributo. |
Il recupero delle risorse inizia quando il documento HTML è stato completamente caricato analizzato. | ||
Media Source Extensions (MSE) ignora l'attributo preload negli elementi multimediali perché l'app è responsabile
la fornitura di contenuti multimediali all'MSE.
|
||
Precaricamento link |
Forza il browser a effettuare una richiesta per una risorsa video senza bloccare
l'evento onload del documento.
|
Le richieste di intervallo HTTP non sono compatibili. |
Compatibile con MSE e segmenti di file. | Da utilizzare solo per file multimediali di piccole dimensioni (< 5 MB) quando recuperi le risorse complete. | |
Buffering manuale | Pieno controllo | La gestione degli errori complessi è responsabilità del sito web. |
Attributo di precaricamento video
Se l'origine video è un file univoco ospitato su un server web, potresti voler
Utilizza l'attributo video preload
per fornire al browser un suggerimento su come
informazioni o contenuti da precaricare. Ciò significa che Media Source Extensions
(MSE) non è compatibile con preload
.
Il recupero della risorsa verrà avviato solo quando il documento HTML iniziale è stato
completamente caricati e analizzati (ad es. è stato attivato l'evento DOMContentLoaded
)
mentre un evento load
molto diverso
verrà attivato quando la risorsa
è stato effettivamente recuperato.
Se l'attributo preload
è impostato su metadata
, l'utente non è
dovrebbe richiedere il video, ma questo recupero dei metadati (dimensioni,
elenco, durata e così via) è desiderabile. Tieni presente che da Chrome
64, il valore predefinito per preload
è metadata
. (La durata era auto
in precedenza).
<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>
Se l'attributo preload
è impostato su auto
, il browser potrebbe memorizzare nella cache
sia possibile ottenere una quantità sufficiente di dati per completare la riproduzione senza dover interrompere
un ulteriore buffering.
<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>
Ci sono però alcune avvertenze. Poiché questo è solo un suggerimento, il browser potrebbe
ignora l'attributo preload
. Al momento della stesura di questo documento, ecco alcune regole
applicati in Chrome:
- Quando l'opzione Risparmio dati è attiva, Chrome forza il valore
preload
sunone
. - In Android 4.3, Chrome forza il valore
preload
anone
a causa di un comando Android Bug. - Su una rete cellulare (2G, 3G e 4G), Chrome forza il valore
preload
ametadata
.
Suggerimenti
Se il tuo sito web contiene molte risorse video sullo stesso dominio,
ti consigliamo di impostare il valore preload
su metadata
o di definire il poster
e imposta preload
su none
. In questo modo, eviteresti di colpire
il numero massimo di connessioni HTTP allo stesso dominio (6 in base al
HTTP 1.1) che può bloccare il caricamento delle risorse. Tieni presente che anche in questo caso
migliorare la velocità delle pagine se i video non rientrano nell'esperienza utente di base.
Precaricamento link
Come trattato in altri articoli, il precaricamento dei link è un recupero dichiarativo che
consente di forzare il browser a effettuare una richiesta per una risorsa senza
blocca l'evento load
e durante il download della pagina. Risorse
caricati tramite <link rel="preload">
vengono memorizzati localmente nel browser e sono
inerziano in modo efficace finché non viene fatto riferimento
esplicitamente in DOM, JavaScript
o CSS.
Il precaricamento è diverso dal precaricamento perché si concentra sulla navigazione corrente recupera le risorse con priorità in base al tipo (script, stile, carattere video, audio ecc.). Deve essere utilizzata per riscaldare la cache del browser sessioni.
Precarica il video completo
Ecco come precaricare un video completo sul tuo sito web in modo che, quando JavaScript chiede il recupero dei contenuti video, che vengono letti dalla cache come risorsa potrebbero essere già state memorizzate nella cache dal browser. Se la richiesta di precaricamento non è stata è terminato, verrà eseguito un normale recupero della rete.
<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>
Poiché la risorsa precaricata verrà utilizzata da un elemento video in
Nell'esempio, il valore del link di precaricamento as
è video
. Se fosse un audio
, sarà as="audio"
.
Precarica il primo segmento
L'esempio seguente mostra come precaricare il primo segmento di un video con <link
rel="preload">
e utilizzarlo con Media Source Extensions. Se non hai familiarità
con l'API MSE JavaScript, consulta le nozioni di base su MSE.
Per semplicità, supponiamo che l'intero video sia stato suddiviso in
file più piccoli come file_1.webm
, file_2.webm
, file_3.webm
e così via.
<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>
Assistenza
Puoi rilevare il supporto di vari tipi di as
per <link rel=preload>
con il
di seguito:
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');
}
Buffering manuale
Prima di approfondire l'API Cache e i service worker, vediamo
come eseguire manualmente il buffering di un video con MSE. Nell'esempio seguente si presuppone che il tuo sito web
supporta HTTP Range
richieste, ma sarebbe abbastanza simile con le richieste
segmenti. Tieni presente che alcune librerie middleware come Shaka di Google
Player, JW Player e Video.js sono
è progettato per occuparsene.
<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>
Considerazioni
Dato che ora hai il controllo dell'intera esperienza di buffering dei contenuti multimediali, ti consiglio di tieni in considerazione il livello della batteria del dispositivo, la "modalità Risparmio dati" preferenze utente e le informazioni di rete quando si pensa al precaricamento.
Consapevolezza della batteria
Considera il livello della batteria degli utenti dispositivi prima di pensare sul precaricamento di un video. Questo consente di preservare la durata della batteria quando il livello di alimentazione è basso.
Disattiva o almeno precarica un video a risoluzione più bassa quando la batteria del dispositivo si sta esaurendo.
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.
}
});
}
Rileva "Risparmio dati"
Utilizza l'intestazione della richiesta di suggerimento del client Save-Data
per una distribuzione rapida e chiara
applicazioni agli utenti che hanno attivato il "risparmio di dati" nei suoi
del browser. Se identifichi questa intestazione della richiesta, la tua applicazione può personalizzare
offrire un'esperienza utente ottimizzata a limiti di costi e prestazioni
utenti.
Per saperne di più, consulta la sezione Distribuzione rapida e leggera di applicazioni con risparmio di dati.
Caricamento intelligente basato sulle informazioni di rete
Ti consigliamo di controllare navigator.connection.type
prima del precaricamento. Quando
è impostato su cellular
, potresti impedire il precaricamento e consigliare agli utenti che
l'operatore di rete mobile potrebbe addebitare la larghezza di banda e iniziare
la riproduzione automatica di contenuti memorizzati in precedenza nella cache.
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
Guarda l'esempio di informazioni di rete per scoprire come reagire alla rete modifiche.
Prememorizzare nella cache più primi segmenti
E se volessi precaricare in modo speculativo alcuni contenuti multimediali senza
sapere quali contenuti multimediali sceglierà l'utente? Se l'utente si trova su un
pagina web contenente 10 video, probabilmente abbiamo memoria sufficiente per recuperarne uno
del segmento di pubblico di ogni dominio, ma di sicuro non dovremmo creare 10 <video>
nascosti
e 10 MediaSource
oggetti e iniziare a inserire questi dati.
L'esempio in due parti riportato di seguito mostra come prememorizzare nella cache più primi segmenti di
utilizzando l'API Cache, potente e facile da usare. Tieni presente che qualcosa di simile
possono essere ottenuti anche
con IndexedDB. Non utilizziamo ancora i service worker come
l'API Cache è accessibile anche dall'oggetto window
.
Recupera e memorizza nella cache
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;
});
});
}
Tieni presente che se utilizzassi le richieste Range
HTTP, dovrei ricreare manualmente
un oggetto Response
poiché l'API Cache non supporta ancora le risposte Range
. Essere
ricordando che la chiamata a networkResponse.arrayBuffer()
consente di recuperare l'intero contenuto
della risposta nella memoria del renderer; per questo conviene usare
piccoli intervalli.
Come riferimento, ho modificato parte dell'esempio precedente per salvare l'intervallo HTTP richieste alla preregistrazione del video.
...
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;
});
Riproduci video
Quando un utente fa clic su un pulsante di riproduzione, recuperiamo il primo segmento del video disponibile nell'API Cache in modo che la riproduzione inizi immediatamente, se disponibile. Altrimenti, lo recuperiamo semplicemente dalla rete. Ricorda che i browser e gli utenti possono decidere di svuotare la cache.
Come abbiamo visto in precedenza, utilizziamo la tecnologia MSE per indirizzare il primo segmento del video al video. .
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.
});
}
});
}
Creare risposte a intervalli con un service worker
Che cosa succede se hai recuperato un intero file video e lo hai salvato
l'API Cache? Quando il browser invia una richiesta Range
HTTP,
vuoi portare l'intero video nella memoria del renderer perché l'API Cache non
supporta Range
risposte ancora.
Vediamo quindi come intercettare queste richieste e restituire un'istruzione Range
personalizzata
una risposta da un 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;
}
}
È importante notare che ho utilizzato response.blob()
per ricreare questa sezione
come risposta, poiché mi consente di gestire il file mentre
response.arrayBuffer()
inserisce l'intero file nella memoria del renderer.
La mia intestazione HTTP X-From-Cache
personalizzata può essere utilizzata per sapere se questa richiesta
provengono dalla cache o dalla rete. Può essere utilizzato da un player come
ShakaPlayer per ignorare il tempo di risposta come indicatore di
la velocità della rete.
Dai un'occhiata all'app multimediale di esempio ufficiale, in particolare alle sue
ranged-response.js per una soluzione completa su come gestire Range
richieste.