Cómo acelerar la reproducción de contenido multimedia con la precarga activa de recursos
Un inicio de reproducción más rápido implica que más personas miran tu video o escuchan tu audio. Es un dato conocido. En este artículo, exploraré técnicas que puedes usar para acelerar la reproducción de audio y video mediante la precarga de recursos según tu caso de uso.
Describiré tres métodos de precarga de archivos multimedia, comenzando por los profesionales y desventajas.
Es genial... | Pero… | |
---|---|---|
Atributo de precarga de video | Fácil de usar para un archivo único alojado en un servidor web. | Es posible que los navegadores ignoren por completo este atributo. |
La recuperación de recursos comienza cuando el documento HTML se cargó por completo. analizar. | ||
Las extensiones de fuente de medios (MSE) ignoran el atributo preload en los elementos multimedia porque la app es responsable de
proporcionando medios a ECM.
|
||
Carga previa del vínculo |
Fuerza al navegador a realizar una solicitud de un recurso de video sin bloquear.
el evento onload del documento.
|
Las solicitudes de rango HTTP no son compatibles. |
Es compatible con ECM y segmentos de archivos. | Se debe usar solo para archivos multimedia pequeños (<5 MB) cuando se recuperan recursos completos. | |
Almacenamiento en búfer manual | Control total | El manejo de errores complejos es responsabilidad del sitio web. |
Atributo de precarga de video
Si la fuente del video es un archivo único alojado en un servidor web, te recomendamos
Usa el atributo preload
de video para sugerir al navegador una sugerencia sobre cómo
mucha información o contenido que se precarga. Esto significa que las extensiones de fuente de medios
(ECM) no es compatible con preload
.
La recuperación de recursos comenzará solo después de que el documento HTML inicial se haya
Se cargó y se analizó completamente (p. ej., se activó el evento DOMContentLoaded
).
mientras que el evento load
, que es muy diferente, se activa cuando el recurso
ya se recuperó.
Si estableces el atributo preload
en metadata
, esto indica que el usuario no está
se espera que necesiten el video, sino que recuperar los metadatos (dimensiones, pistas
lista, duración, etc.) es deseable. Ten en cuenta que, si comienzas en Chrome
64, el valor predeterminado para preload
es metadata
. (Era auto
anteriormente).
<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>
Establecer el atributo preload
en auto
indica que el navegador puede almacenar en caché
suficientes datos para completar la reproducción sin necesidad de detenerse
almacenamiento en búfer adicional.
<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>
Sin embargo, hay algunas advertencias. Como esto es solo una pista, es posible que el navegador
ignora el atributo preload
. Hasta el momento de la redacción, estas son algunas reglas
aplicadas en Chrome:
- Cuando el Ahorro de datos está habilitado, Chrome fuerza el valor
preload
anone
- En Android 4.3, Chrome fuerza el valor de
preload
anone
debido a un error de Android Error. - En una conexión móvil (2G, 3G y 4G), Chrome fuerza el valor de
preload
ametadata
Sugerencias
Si tu sitio web contiene muchos recursos de video en el mismo dominio,
se recomienda establecer el valor de preload
en metadata
o definir el poster
y configura preload
como none
. De esa manera, evitas tocar
la cantidad máxima de conexiones HTTP al mismo dominio (6 según el
especificación de HTTP 1.1) que puede detener la carga de recursos. Ten en cuenta que esto también podría
mejorar la velocidad de la página si los videos no son parte de la experiencia principal del usuario.
Carga previa del vínculo
Como se trata en otros artículos, la precarga de vínculos es una recuperación declarativa que
te permite forzar al navegador a realizar una solicitud de un recurso sin
bloqueando el evento load
y mientras se descarga la página. Recursos
cargados a través de <link rel="preload">
se almacenan localmente en el navegador y se
se mantienen inertes hasta que se haga una referencia explícita a ellas en el DOM, JavaScript
o CSS.
La carga previa es diferente de la carga previa, ya que se enfoca en la navegación y recupera recursos con prioridad según su tipo (secuencia de comandos, estilo, fuente, video, audio, etc.). Debe usarse para preparar la caché del navegador para sesiones.
Precargar video completo
Aquí te mostramos cómo precargar un video completo en tu sitio web para que cuando JavaScript solicita recuperar el contenido de video; se lee desde la caché como recurso puede haber sido almacenado en caché por el navegador. Si la solicitud de precarga aún no haya finalizado, se realizará una recuperación de red normal.
<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>
Como el recurso precargado será consumido por un elemento de video en
En el ejemplo, el valor del vínculo precargado as
es video
. Si se tratara de un audio
elemento, sería as="audio"
.
Precarga el primer segmento
En el siguiente ejemplo, se muestra cómo precargar el primer segmento de un video con <link
rel="preload">
y usarlo con extensiones de fuente de medios. Si desconocen
con la API de JavaScript de ECM, consulta Conceptos básicos de ECM.
Para simplificar, supongamos que todo el video se dividió en
archivos más pequeños, como 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>
Asistencia
Puedes detectar la compatibilidad de varios tipos de as
para <link rel=preload>
con el elemento
algunos fragmentos a continuación:
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');
}
Almacenamiento en búfer manual
Antes de analizar la API de Cache y los service workers, veamos
cómo almacenar manualmente un video en el búfer con ECM. En el siguiente ejemplo, se supone que tu sitio web
servidor compatible con HTTP Range
pero esto sería bastante similar con el caso de
segmentos. Ten en cuenta que algunas bibliotecas de middleware, como Google's Shaka
Player, JW Player y Video.js son
creado para manejar esto por ti.
<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>
Consideraciones
Como tienes el control de toda la experiencia de almacenamiento en búfer de contenido multimedia, ten en cuenta el nivel de batería del dispositivo, el "modo de ahorro de datos" las preferencias del usuario y la información de la red cuando pienses en la precarga.
Reconocimiento de batería
Ten en cuenta el nivel de batería de las los dispositivos antes de pensar sobre la precarga de un video. Esto conservará la duración de la batería cuando el nivel de es bajo.
Inhabilita la precarga o al menos precargar un video de menor resolución cuando dispositivo se está quedando sin batería.
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.
}
});
}
Detectar "Ahorro de datos"
Usa el encabezado de solicitud de sugerencia de cliente Save-Data
para realizar entregas rápidas y ligeras.
aplicaciones a los usuarios que han optado por el "ahorro de datos" en su
navegador. Al identificar este encabezado de la solicitud, tu aplicación puede personalizar y
brindar una experiencia del usuario optimizada
usuarios.
Consulta Cómo publicar aplicaciones rápidas y livianas con Save-Data para obtener más información.
Carga inteligente basada en la información de la red
Te recomendamos verificar navigator.connection.type
antes de la precarga. Cuándo
está configurado en cellular
, puedes evitar la precarga y avisarles a los usuarios que
es posible que su operador de red móvil esté
cargando por el ancho de banda y solo comience
reproducción automática de contenido previamente almacenado en caché
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
Consulta la muestra de información de red para saber cómo reaccionar ante la los cambios.
Almacena previamente en caché múltiples primeros segmentos
¿Qué sucede si quiero precargar especulativamente cierto contenido multimedia sin
saber qué medio de comunicación
el usuario elegirá finalmente? Si el usuario se encuentra en un
página web que contiene 10 videos,
probablemente, tengamos memoria suficiente
segmento a cada archivo, pero no deberíamos crear 10 <video>
y 10 objetos MediaSource
, y comenzarás a proporcionar esos datos.
El ejemplo de dos partes que aparece a continuación muestra cómo almacenar en caché por adelantado varios segmentos de
video con la potente y fácil de usar API de Cache. Ten en cuenta que algo similar
también se puede lograr con IndexedDB. Aún no usamos service workers
También se puede acceder a la API de Cache desde el objeto window
.
Recuperar y almacenar en caché
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;
});
});
}
Ten en cuenta que, si usara solicitudes Range
HTTP, tendría que volver a crearlas de forma manual
Un objeto Response
, ya que la API de Cache todavía no admite respuestas Range
. Sé
Ten en cuenta que llamar a networkResponse.arrayBuffer()
recupera todo el contenido
de la respuesta en la memoria del renderizador.
o en rangos pequeños.
Como referencia, modifiqué parte del ejemplo anterior para guardar el rango HTTP a la caché previa de 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;
});
Reproducir video
Cuando un usuario haga clic en un botón de reproducción, recuperaremos el primer segmento del video. disponible en la API de Cache para que la reproducción comience de inmediato (si está disponible). De lo contrario, simplemente los obtenemos de la red. Ten en cuenta que los navegadores y los usuarios pueden decidir borrar la Caché.
Como vimos antes, usamos ECM para ingresar el primer segmento del 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.
});
}
});
}
Crea respuestas de Range con un service worker
¿Qué sucede si recuperaste un archivo de video completo y lo guardaste en
la API de Cache? Cuando el navegador envía una solicitud Range
HTTP, seguramente no
todo el video en la memoria del procesador,
ya que la API de Cache no
Se admiten respuestas de Range
todavía.
Entonces, te mostraré cómo interceptar estas solicitudes y mostrar un Range
personalizado.
respuesta de 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;
}
}
Es importante destacar que usé response.blob()
para recrear la porción
ya que esto me permite manejar el archivo
response.arrayBuffer()
lleva todo el archivo a la memoria del procesador.
Se puede usar mi encabezado HTTP X-From-Cache
personalizado para saber si esta solicitud
provenga de la caché o de la red. La puede usar un jugador, como
ShakaPlayer para ignorar el tiempo de respuesta como indicador de
la velocidad de la red.
Consulta la app de Samples oficial y, en particular, su
archivo ranged-response.js para obtener una solución completa para controlar Range
solicitudes.