Cómo administrar el almacenamiento de videos en la Web

El video es un recurso difícil de administrar; la transmisión requiere mucho ancho de banda y el almacenamiento en caché no es sencillo. Estos problemas se agravan cuando los videos se reproducen en bucle, como en una pantalla de quiosco. Por ejemplo, si una empresa tiene cientos de dispositivos que reproducen 30 videos en repetición todo el día, todos los días, podría sobrecargar rápidamente su red. Si publicas los videos desde la caché en lugar de transmitirlos, solo incurrirás en el costo de descarga una vez, harás que las reproducciones posteriores sean más rápidas y permitirás que se reproduzcan sin conexión. Para ello, puedes aprovechar las capacidades de almacenamiento del navegador, de las cuales la API de Cache Storage y IndexedDB son las más adecuadas para almacenar archivos de video. Si bien ambas son buenas opciones, nos centraremos en la API de Cache Storage por su integración con la popular biblioteca de trabajadores de servicio Workbox.

Almacenamiento en caché de videos desde un service worker

Dado que descargar y almacenar en caché recursos grandes, como videos, puede ser una tarea particularmente intensiva en términos de tiempo y procesador, debes hacerlo en segundo plano fuera del subproceso principal. Los Service Workers son especialmente útiles para descargar tareas de almacenamiento en caché. Actúan como un proxy entre la página y la red, lo que permite interceptar solicitudes y aplicar lógica adicional a la respuesta de la red, por ejemplo, una estrategia de almacenamiento en caché.

Existen muchas estrategias de almacenamiento en caché diferentes, y cada una de ellas está diseñada para ayudar en diferentes casos de uso. Por ejemplo, para entregar un archivo desde una caché si está disponible o recurrir a la red si no lo está, puedes escribir el siguiente código.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

Administrar esto para diferentes tipos de recursos o URLs que requieren diferentes estrategias de almacenamiento en caché puede ser un proceso repetitivo y propenso a errores. Workbox proporciona un conjunto de herramientas, incluidos ayudantes de enrutamiento y estrategias de almacenamiento en caché, que te permiten escribir código de Service Worker de una manera más declarativa y reutilizable.

La estrategia anterior se denomina almacenamiento en caché primero. Para escribir lo mismo con Workbox, incluirías lo siguiente:

registerRoute(
  ({ request }) => request.destination === 'video',
  new CacheFirst()
);

Workbox proporciona recetas similares para otras estrategias de almacenamiento en caché y tareas comunes de service worker, incluida la integración con herramientas de compilación como Webpack y Rollup.

Una vez que configures Workbox, deberás elegir cuándo almacenar en caché tus videos. Aquí, hay dos enfoques: de forma anticipada cuando se carga la página o de forma diferida cuando se solicita el video.

Enfoque inmediato

El almacenamiento previo en caché es una técnica en la que los archivos se guardan en la caché durante la instalación del service worker, lo que los hace disponibles en cuanto se activa el service worker. Workbox puede configurar automáticamente el almacenamiento previo en caché para los archivos a los que puede acceder durante el proceso de compilación.

Puedes usar el siguiente código de Workbox en tu trabajador de servicio para almacenar en caché previamente los archivos:

import { addPlugins, precacheAndRoute } from 'workbox-precaching';
import { RangeRequestsPlugin } from 'workbox-range-requests';

addPlugins([new RangeRequestsPlugin()]);
precacheAndRoute(self.__WB_MANIFEST);
  • import(s): Carga las vinculaciones requeridas desde los módulos de Workbox correspondientes. Dado que los service workers aún no admiten ESModules de forma universal, tu service worker potenciado por Workbox deberá pasar por un bundler para que funcione en producción.
  • RangeRequestsPlugin: Permite que una respuesta almacenada en caché satisfaga una solicitud con un encabezado Range. Esto es necesario porque, por lo general, los navegadores usan un encabezado Range para el contenido multimedia.
  • addPlugins: Te permite agregar complementos de Workbox a cada solicitud de Workbox.
  • precacheAndRoute: Agrega entradas a la lista de caché previa y crea una ruta para controlar las solicitudes de recuperación correspondientes.
  • __WB_MANIFEST: Es un marcador de posición que la CLI de Workbox (o los complementos de herramientas de compilación) reemplaza por el manifiesto de almacenamiento previo en caché.

Pasa tu trabajador de servicio a la CLI de Workbox o a la herramienta de compilación que elijas, y configura cómo se debe generar tu precache. Un archivo workbox-config.js, como el siguiente, le indicará a la CLI cómo debe renderizar tu trabajador de servicio:

module.exports = {
  globDirectory: '.',
  globPatterns: ['**/*.{html,mp4}'],
  maximumFileSizeToCacheInBytes: 5000000,
  swSrc: 'sw.js',
  swDest: 'sw.js',
};
  • globDirectory: Es la carpeta raíz desde la que se comienza a buscar archivos de almacenamiento previo en caché.
  • globPatterns: Son los patrones de archivos ("globs") que se deben almacenar en la caché previa.
  • maximumFileSizeToCacheInBytes: Es un límite superior para el tamaño que puede tener un archivo para almacenarse previamente en la caché, en bytes.
  • swSrc: Es la ubicación del archivo que se usará para generar tu trabajador de servicio.
  • swDest: Es el destino del service worker generado (puede ser el mismo que el archivo fuente, pero asegúrate de que self.__WB_MANIFEST esté presente en cada ejecución).

Cuando se ejecuta el proceso de compilación, se genera una nueva versión del trabajador de servicio y self.__WB_MANIFEST se reemplaza por una lista de archivos, cada uno con un hash para indicar su revisión:

precacheAndRoute([
  {
    revision: '524ac4b453c83f76eb9caeec11854ca5',
    url: 'ny.mp4',
  },
]);

Cada vez que se ejecuta el proceso de compilación, esta lista se vuelve a escribir con el conjunto actual de archivos coincidentes y sus hashes de revisión actuales. Esto garantiza que, cada vez que se agregue, quite o cambie un archivo, el trabajador de servicio actualizará la caché en su próxima instalación.

Enfoque diferido

Cuando no tengas todos los videos disponibles en el momento de la compilación o solo quieras almacenarlos en caché cuando sean necesarios, debes adoptar un enfoque diferido. Este enfoque requiere que el almacenamiento en caché y la publicación se separen, ya que solo se recupera contenido parcial de la red durante la reproducción de video, por lo que no funcionará el almacenamiento en caché de archivos a medida que se transmiten.

Almacenamiento en caché de los archivos

Las cachés se pueden crear con Cache.open() y, luego, se pueden agregar archivos a la caché con Cache.add() o Cache.addAll(). Si tu app recibe una lista JSON de videos para almacenar en caché, se pueden agregar a una caché de video de la siguiente manera:

// Open video cache
const cache = await caches.open('video-cache');
// Fetch list of videos
const videos = await (await fetch('/video-list.json')).json();
// Add videos to cache
await cache.addAll(videos);

La ventaja de este enfoque es que puedes controlar el paso de almacenamiento en caché de forma independiente del ciclo de vida del service worker, incluso desde otros web workers. La desventaja es que la parte de administración del almacenamiento depende del desarrollador: debes escribir tu propio algoritmo para hacer un seguimiento de los cambios en los archivos, hacer un seguimiento de los archivos almacenados en caché actualmente en el navegador y administrar las actualizaciones de archivos para garantizar que solo se actualicen los archivos modificados.

Publicación de archivos de video almacenados en caché

Luego, se puede usar una estrategia de almacenamiento en caché en el tiempo de ejecución del trabajador de servicio, como almacenar en caché primero, para entregar los archivos de video almacenados en caché anteriormente:

import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { RangeRequestsPlugin } from 'workbox-range-requests';

registerRoute(
  ({ request }) => request.destination === 'video',
  new CacheFirst({
    cacheName: 'video-cache',
    plugins: [
      new CacheableResponsePlugin({
        statuses: [200],
      }),
      new RangeRequestsPlugin(),
    ],
  }),
);
  • import(s) carga las vinculaciones necesarias de los módulos de Workbox correspondientes.
  • registerRoute: Enruta solicitudes a funciones (estrategias de almacenamiento en caché y complementos) que proporcionan respuestas.
  • CacheFirst: Es una estrategia de almacenamiento en caché que satisface la solicitud desde la caché, si está disponible; de lo contrario, la recupera de la red y actualiza la caché.
  • CacheableResponsePlugin: Se usa para indicar qué encabezados deben estar presentes para que la respuesta se pueda almacenar en caché. Asegúrate de incluir solo estados 200 para las rutas que almacenan en caché el video para evitar que las respuestas de contenido parcial (206) se almacenen en caché a medida que se transmiten los videos.
  • RangeRequestsPlugin: Es un complemento que permite que una respuesta almacenada en caché satisfaga una solicitud con un encabezado Range. Esto es necesario porque, por lo general, los navegadores usan un encabezado Range para el contenido multimedia.

Optimizar la carga de videos es una tarea importante para las apps que realizan transmisiones intensivas. Si aprovechas la API de Cache Storage del navegador y Workbox, puedes hacer que esta tarea, que de otro modo sería difícil, sea manejable, lo que ahorra ancho de banda a los usuarios, reduce la carga del servidor, logra una reproducción de video más rápida y permite que los videos se ejecuten incluso sin conexión.