wandres.dev
VÍDEO Y MEDIA · El recurso más pesado

Streaming adaptativo: HLS, DASH y cuándo no hace falta ninguno

Cómo funciona por dentro el bitrate adaptativo, qué diferencia realmente a HLS de DASH, qué necesita cada navegador para reproducirlos, y el umbral por debajo del cual un MP4 progresivo es mejor decisión.

⏱ 20 min

El streaming adaptativo resuelve un problema que un MP4 progresivo no puede resolver: el ancho de banda de un usuario cambia mientras mira el vídeo, y la calidad servida tiene que cambiar con él. La solución es trocear el vídeo en segmentos de pocos segundos, publicar varias versiones de cada segmento a distintas calidades, y dejar que el cliente decida en cada salto. Es una arquitectura excelente y bastante cara, y por eso la pregunta útil no es cómo montarla sino si tu caso la necesita.

🎯 Al terminar esta lección sabrás
  • Explicar el ciclo de decisión del algoritmo adaptativo y qué señales usa.
  • Distinguir HLS de DASH en lo que de verdad los separa, no en lo anecdótico.
  • Determinar qué necesita cada navegador para reproducir cada uno.
  • Aplicar el umbral por el que un MP4 progresivo con byte ranges basta.

Qué hay dentro de un flujo adaptativo

La idea es simple y las consecuencias no. En lugar de un fichero, publicas una escalera de representaciones: el mismo vídeo codificado a 360p con 400 kbps, a 720p con 1500 kbps, a 1080p con 3000 kbps, y así. Cada representación se corta en segmentos de duración fija, típicamente entre dos y seis segundos, alineados de forma que todos empiezan en un fotograma clave. Y publicas un manifiesto: un fichero de texto que enumera las representaciones, sus tasas de bits, sus resoluciones y las URL de sus segmentos.

El cliente descarga el manifiesto, elige una representación de partida, pide segmentos, y en cada segmento mide. Mide dos cosas: el ancho de banda efectivo —bytes del segmento divididos entre el tiempo que tardó— y el nivel del búfer, es decir, cuántos segundos de vídeo tiene ya descargados por delante de la posición de reproducción. Con esas dos señales decide si sube, baja o se queda.

Las heurísticas modernas dan más peso al búfer que al ancho de banda, porque el ancho de banda instantáneo es ruidoso y el búfer es el que de verdad predice un corte. La regla de fondo es que subir de calidad se hace despacio y bajar se hace deprisa: una subida agresiva que agote el búfer produce una parada de reproducción, y una parada es infinitamente peor que treinta segundos a 480p.

flowchart TB
M[Cliente descarga el manifiesto] --> S[Elegir representacion inicial]
S --> D[Pedir siguiente segmento]
D --> ME[Medir ancho de banda y nivel de bufer]
ME --> DE{Decidir}
DE -->|Bufer alto y ancho sobrado| U[Subir un escalon]
DE -->|Bufer estable| K[Mantener]
DE -->|Bufer cayendo| B[Bajar varios escalones]
U --> D
K --> D
B --> D
style ME fill:#89b4fa,color:#11111b
style U fill:#a6e3a1,color:#11111b
style K fill:#94e2d5,color:#11111b
style B fill:#f38ba8,color:#11111b

La duración del segmento es la palanca de diseño más importante y tiene un compromiso claro. Segmentos cortos —dos segundos— permiten reaccionar deprisa a un cambio de red y bajan la latencia de arranque, pero multiplican el número de peticiones y empeoran la eficiencia de compresión, porque cada segmento necesita su propio fotograma clave y los fotogramas clave son los caros. Segmentos largos —seis o diez segundos— comprimen mejor y generan menos peticiones, pero el cliente tarda más en corregir una mala decisión. Para vídeo bajo demanda, cuatro segundos es un punto razonable; para directo con baja latencia, se baja a dos o menos con segmentos parciales.

HLS y DASH: lo que de verdad los separa

Los dos hacen lo mismo. Las diferencias que importan son tres.

El manifiesto. HLS usa listas de reproducción en texto plano con extensión .m3u8, con una lista maestra que apunta a una lista por representación. DASH usa un XML llamado MPD, con una jerarquía de periodos, conjuntos de adaptación y representaciones. El MPD es más expresivo y más verboso; el m3u8 es más simple de generar y de depurar a mano.

El soporte nativo. Esta es la diferencia práctica decisiva. Safari reproduce HLS de forma nativa: le pones la URL del .m3u8 en el src de un <video> y funciona, sin JavaScript, incluido en iOS donde además es el único camino para reproducción a pantalla completa integrada con el sistema. Ningún navegador reproduce DASH de forma nativa, y ningún navegador que no sea Safari reproduce HLS de forma nativa. Todo lo demás pasa por Media Source Extensions, la API que permite a JavaScript alimentar el elemento <video> con trozos de vídeo que él mismo descarga.

El ecosistema de empaquetado. HLS empezó con contenedores de transporte MPEG-2, lo que obligaba a generar ficheros distintos de los de DASH. Desde que HLS admite MP4 fragmentado, los dos formatos pueden compartir exactamente los mismos segmentos y diferenciarse solo en el manifiesto. Eso es lo que hace el formato común de aplicación de medios, y es lo que debes usar si vas a servir los dos: un solo juego de segmentos, dos manifiestos. Duplicar el almacenamiento de vídeo por servir dos protocolos es un error caro y evitable.

La combinación pragmática en 2026 es servir HLS con MP4 fragmentado, nativo en Safari y con una biblioteca de MSE en el resto:

<video id="reproductor" controls playsinline poster="portada.avif" width="1280" height="720"></video>
const video = document.getElementById('reproductor');
const fuente = '/vod/clip/master.m3u8';

if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // Safari: soporte nativo, cero JavaScript adicional
  video.src = fuente;
} else {
  // Resto de navegadores: Media Source Extensions
  const { default: Hls } = await import('hls.js');
  if (Hls.isSupported()) {
    const hls = new Hls({ maxBufferLength: 30 });
    hls.loadSource(fuente);
    hls.attachMedia(video);
  } else {
    video.src = '/vod/clip/respaldo-720.mp4'; // MP4 progresivo como último recurso
  }
}

El import() dinámico no es decorativo: la biblioteca pesa del orden de 130 KB minificada, y cargarla en Safari, donde no hace falta, es tirar esos bytes. El patrón de cargar el reproductor solo cuando se necesita es el mismo que verás en el nivel de division por interacción.

La mayoría de los vídeos de una web no necesitan streaming adaptativo, y montarlo empeora las métricas

Hay un sesgo profesional muy extendido: el vídeo es un tema serio, luego merece la solución seria. En una página de producto con un clip de cuarenta segundos, montar HLS es una decisión que empeora casi todas las métricas que te importan.

Las cuentas. Un MP4 progresivo bien codificado con faststart empieza a reproducirse en cuanto llegan los primeros segundos de datos, con una sola petición y sin JavaScript. Un flujo HLS necesita descargar el manifiesto maestro, el manifiesto de la representación elegida, y el segmento de inicialización, antes del primer segmento de vídeo: son cuatro viajes de ida y vuelta encadenados antes del primer fotograma. Con una latencia móvil de 120 milisegundos eso es medio segundo de retraso puro, más los 130 KB de la biblioteca, más su parseo y ejecución, que en un móvil de gama media son otros 200 o 300 milisegundos de hilo principal. Si el vídeo es el elemento del LCP, acabas de meterle casi un segundo.

El umbral operativo que uso: si el vídeo dura menos de un minuto y pesa menos de 10 MB en su versión de mayor calidad, sirve MP4 progresivo. Y hazlo bien: con faststart, con el servidor respondiendo a peticiones de rango —el Accept-Ranges: bytes que permite al navegador pedir solo el trozo que necesita al saltar en la línea de tiempo— y con dos o tres calidades seleccionadas por media query si de verdad hay diferencia entre móvil y escritorio.

<video controls playsinline poster="portada.avif" width="1280" height="720" preload="metadata">
  <source src="clip-1080-av1.mp4" type='video/mp4; codecs="av01.0.05M.08"' media="(min-width: 900px)">
  <source src="clip-720-av1.mp4"  type='video/mp4; codecs="av01.0.05M.08"'>
  <source src="clip-1080-h264.mp4" type='video/mp4; codecs="avc1.640028"' media="(min-width: 900px)">
  <source src="clip-720-h264.mp4"  type='video/mp4; codecs="avc1.42E01E"'>
</video>

El atributo media en <source> es la pieza que casi nadie conoce y hace exactamente lo que parece: el navegador solo considera la fuente si la consulta se cumple. Es adaptación por resolución sin biblioteca, sin manifiesto y sin coste de JavaScript. No es adaptación dinámica —la decisión se toma una vez— pero para un clip de cuarenta segundos, la red no va a cambiar tanto.

Cuándo sí hace falta

Tres situaciones justifican la complejidad, y ninguna es «tenemos vídeo».

Contenido largo. A partir de cinco o diez minutos, la probabilidad de que la red del usuario cambie durante la reproducción se acerca a la certeza, sobre todo en móvil. Ahí el adaptativo es la diferencia entre una experiencia continua y tres paradas.

Catálogo con audiencia amplia y variable. Si sirves a usuarios en redes muy dispares, servir un único perfil obliga a elegir entre dejar borrosos a los buenos o cortar a los malos. El adaptativo elimina la elección.

Directo. No hay otra opción realista. La reproducción progresiva de un fichero que se está escribiendo no funciona.

Fuera de esas tres, un MP4 progresivo bien preparado gana en tiempo hasta el primer fotograma, en JavaScript enviado, en complejidad de infraestructura y en facilidad de cacheo en el borde, porque un fichero completo se cachea con una entrada y un flujo adaptativo genera cientos de objetos con su propia política.

Un último detalle de coherencia: si acabas montando HLS, comprueba que la primera representación que el cliente elige no es la más alta. Muchos empaquetadores ordenan el manifiesto de mayor a menor calidad y algunos clientes arrancan por la primera de la lista, lo que produce un arranque lento y una bajada inmediata. Ordena de menor a mayor, o fija explícitamente el nivel inicial en la configuración del reproductor.

⚔️ Reto práctico

Toma un clip de tres minutos y prepáralo de las dos formas: un MP4 progresivo con faststart y un flujo HLS con segmentos de cuatro segundos y tres representaciones. Mide en ambos, con la red estrangulada a 3G rápido, el tiempo desde la navegación hasta el primer fotograma pintado y el número de peticiones antes de ese fotograma. Después estrangula a 3G lento a mitad de reproducción y anota qué hace cada uno.