Découvrez comment accélérer la lecture de vos contenus multimédias en préchargeant activement les ressources.
<ph type="x-smartling-placeholder">
Un démarrage plus rapide signifie que davantage de personnes regardent votre vidéo ou écoutent votre audio. C'est un fait connu. Dans cet article, nous aborderons que vous pouvez utiliser pour accélérer la lecture de vos contenus audio et vidéo en les ressources selon votre cas d'utilisation.
Je vais décrire trois méthodes de préchargement des fichiers multimédias, en commençant par les pros et ses inconvénients.
C'est génial... | Mais… | |
---|---|---|
Attribut de préchargement de la vidéo | Simple à utiliser pour un fichier unique hébergé sur un serveur Web. | Les navigateurs peuvent ignorer complètement l'attribut. |
La récupération des ressources commence lorsque le document HTML a été entièrement chargé et analysées. | ||
Les extensions de source multimédia (MSE) ignorent l'attribut preload sur les éléments multimédias, car l'application est responsable
de la fourniture de médias à MSE.
|
||
Préchargement du lien |
Force le navigateur à demander une ressource vidéo sans blocage
l'événement onload du document.
|
Les requêtes HTTP Range ne sont pas compatibles. |
Compatible avec MSE et les segments de fichiers. | À utiliser uniquement pour les petits fichiers multimédias (< 5 Mo) lors de l'extraction de ressources complètes. | |
Mise en mémoire tampon manuelle | Contrôle complet | La gestion des erreurs complexes relève de la responsabilité du site Web. |
Attribut de préchargement de la vidéo
Si la source vidéo est un fichier unique hébergé sur un serveur Web, vous pouvez
utilisez l'attribut vidéo preload
pour indiquer au navigateur comment
trop d'informations ou de contenu à précharger Autrement dit, les extensions de source multimédia
(MSE) n'est pas compatible avec preload
.
La récupération des ressources ne commencera que lorsque le document HTML initial aura été
chargé et analysé (par exemple, l'événement DOMContentLoaded
s'est déclenché).
tandis que l'événement load
très différent est déclenché
a bien été récupérée.
Définir l'attribut preload
sur metadata
indique que l'utilisateur n'est pas
auraient besoin de la vidéo, mais que l'extraction de ses métadonnées (dimensions, piste
la liste, la durée, etc.) est souhaitable. Notez qu'à partir de Chrome
64, la valeur par défaut de preload
est metadata
. (Il était auto
précédemment).
<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>
Définir l'attribut preload
sur auto
indique que le navigateur peut mettre en cache
de données suffisantes pour que la lecture soit complète sans nécessiter d'arrêt pour
mise en mémoire tampon.
<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>
Il y a cependant quelques mises en garde. Comme ceci n'est qu'un indice, le navigateur peut avoir totalement
ignore l'attribut preload
. Au moment de la rédaction de ce document, voici quelques règles
appliquées dans Chrome:
- Lorsque l'économiseur de données est activé, Chrome force la valeur
preload
ànone
- Dans Android 4.3, Chrome force la valeur
preload
ànone
en raison d'une erreur Android Bug. - Avec une connexion au réseau mobile (2G, 3G ou 4G), Chrome force la valeur
preload
àmetadata
Conseils
Si votre site Web contient de nombreuses ressources vidéo sur le même domaine,
nous vous recommandons de définir la valeur preload
sur metadata
ou de définir l'poster
et définissez preload
sur none
. De cette façon, vous éviteriez
le nombre maximal de connexions HTTP vers le même domaine (six selon le
spécification HTTP 1.1), ce qui peut bloquer le chargement des ressources. Notez que cela peut aussi
améliorer la vitesse des pages si les vidéos ne font pas partie de l'expérience utilisateur principale.
Préchargement du lien
<ph type="x-smartling-placeholder">Comme décrit dans d'autres articles, le préchargement des liens est une extraction déclarative qui
vous permet de forcer le navigateur à envoyer une requête pour une ressource
bloquer l'événement load
et pendant le téléchargement de la page. Ressources
chargés via <link rel="preload">
sont stockés localement dans le navigateur et sont
efficacement inertes jusqu'à ce qu'elles soient explicitement référencées dans le DOM, JavaScript,
ou CSS.
Le préchargement est différent du préchargement en ce qu'il se concentre sur la navigation actuelle et extrait les ressources en fonction de leur type (script, style, police, vidéo, audio, etc.). Il doit servir à préchauffer le cache du navigateur pour l'état actuel sessions.
Précharger la vidéo complète
Voici comment précharger une vidéo complète sur votre site Web afin que, lorsque votre JavaScript demande l'extraction d'un contenu vidéo. Celui-ci est lu dans le cache en tant que ressource ont peut-être déjà été mis en cache par le navigateur. Si la demande de préchargement n'a pas été terminé, une récupération réseau régulière aura lieu.
<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>
Comme la ressource préchargée sera utilisée par un élément vidéo dans
Dans cet exemple, la valeur du lien de préchargement as
est video
. S'il s'agissait d'une piste audio
il s'agit de as="audio"
.
Précharger le premier segment
L'exemple ci-dessous montre comment précharger le premier segment d'une vidéo avec <link
rel="preload">
et comment l'utiliser avec les extensions de source multimédia. Si vous ne connaissez pas
avec l'API MSE JavaScript, consultez la page Principes de base de MSE.
Par souci de simplicité, supposons que l'intégralité de la vidéo a été divisée en
des fichiers plus petits comme file_1.webm
, file_2.webm
, file_3.webm
, etc.
<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>
Assistance
<ph type="x-smartling-placeholder">Vous pouvez détecter la prise en charge de différents types de as
pour <link rel=preload>
à l'aide de
extraits ci-dessous:
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');
}
Mise en mémoire tampon manuelle
Avant de nous pencher sur l'API Cache et les service workers, voyons
comment mettre une vidéo
en mémoire tampon manuellement avec MSE. L'exemple ci-dessous part du principe que votre site
serveur est compatible avec HTTP Range
mais ce serait assez similaire avec
segments. Notez que certaines bibliothèques de middleware telles que Shaka de Google
Player, JW Player et Video.js sont
conçue pour gérer cela pour vous.
<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>
Remarques
Comme vous contrôlez désormais l'intégralité de la mise en mémoire tampon du contenu multimédia, je vous recommande le niveau de batterie de l'appareil, le mode Économiseur de données, les préférences de l'utilisateur et des informations sur le réseau quand on parle de préchargement.
Détection de la batterie
Tenez compte du niveau de batterie des utilisateurs appareils avant de réfléchir sur le préchargement d'une vidéo. Cela permettra de préserver l'autonomie de la batterie lorsque le niveau de charge est faible.
Désactivez le préchargement ou au moins préchargez une vidéo de résolution inférieure lorsque la appareil est à court de batterie.
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.
}
});
}
Détecter "Data-Saver"
Utilisez l'en-tête de requête d'optimisation du client Save-Data
pour une diffusion rapide et légère.
applications auprès des utilisateurs ayant activé le "Économie de données" dans leur
navigateur. En identifiant cet en-tête de requête, votre application peut personnaliser et
offrir une expérience utilisateur optimisée pour les entreprises aux
utilisateurs.
Pour en savoir plus, consultez la page Proposer des applications rapides et légères avec Save-Data.
Chargement intelligent basé sur les informations réseau
Nous vous conseillons de vérifier navigator.connection.type
avant le préchargement. Quand ?
si elle est définie sur cellular
, vous pouvez empêcher le préchargement et indiquer aux utilisateurs
que leur opérateur de réseau mobile facture
pour la bande passante et ne fait que commencer
la lecture automatique du contenu précédemment mis en 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.
}
}
Consultez l'exemple "Informations sur le réseau" pour savoir comment réagir aux changent également.
Mettre en pré-cache plusieurs premiers segments
Comment effectuer un préchargement spéculatif de contenu multimédia
de savoir quel média l'utilisateur
va finalement choisir ? Si l'utilisateur se trouve sur un
contenant 10 vidéos, nous avons probablement assez de mémoire pour en récupérer une.
de chaque fichier d'analyse, mais nous ne devrions pas créer 10 <video>
et 10 objets MediaSource
, puis commencez à alimenter ces données.
L'exemple en deux parties ci-dessous montre comment mettre en
pré-cache plusieurs premiers segments de
vidéo à l'aide de l'API Cache, puissante et simple d'utilisation. Notez que quelque
chose de similaire
peut aussi être réalisé
avec IndexedDB. Nous n'utilisons pas encore de service workers,
l'API Cache est également accessible depuis l'objet window
.
Extraire et mettre en 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;
});
});
}
Notez que si je devais utiliser des requêtes HTTP Range
, je devrai recréer manuellement
Un objet Response
, car l'API Cache n'accepte pas encore les réponses Range
. Être
gardez à l'esprit que l'appel de networkResponse.arrayBuffer()
permet de récupérer l'intégralité du contenu
de la réponse en une seule fois dans la mémoire du moteur de rendu.
de petites plages.
Pour référence, j'ai modifié une partie de l'exemple ci-dessus pour enregistrer la plage HTTP les demandes de mise en cache vidéo.
...
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;
});
Lire la vidéo
Lorsqu'un utilisateur clique sur un bouton de lecture, nous récupérons le premier segment de la vidéo dans l'API Cache pour que la lecture démarre immédiatement si elle est disponible. Sinon, nous allons simplement le récupérer sur le réseau. N'oubliez pas que les navigateurs et les utilisateurs peuvent décider de vider le cache.
Comme nous l'avons vu précédemment, nous utilisons la MSE pour ajouter ce premier segment de la vidéo à la vidéo. .
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.
});
}
});
}
Créer des réponses Range avec un service worker
Que se passe-t-il si vous avez récupéré l'intégralité
d'un fichier vidéo et l'avez enregistré
l'API Cache ? Lorsque le navigateur envoie une requête HTTP Range
, vous n'avez absolument pas
car l'API Cache ne permet pas de stocker l'intégralité de la vidéo
sont encore compatibles avec les réponses Range
.
Voyons comment intercepter ces requêtes et renvoyer un Range
personnalisé.
d'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;
}
}
Il est important de noter que j'ai utilisé response.blob()
pour recréer ce segment
car cela me donne simplement un handle vers le fichier tout en
response.arrayBuffer()
ajoute le fichier entier dans la mémoire du moteur de rendu.
Je peux utiliser mon en-tête HTTP X-From-Cache
personnalisé pour savoir si cette requête
provenaient du cache ou du réseau. Il peut être utilisé par un lecteur
ShakaPlayer pour ignorer le temps de réponse en tant qu'indicateur
la vitesse du réseau.
Découvrez l'application Sample Media App officielle et en particulier ses
ranged-response.js pour obtenir une solution complète sur la gestion de Range
requêtes.