Servicio OTT

Entrega flujos mediante protocolos basados en HTTP — HLS (sobre MPEG-TS), MPEG-DASH y Low-Latency HLS (sobre CMAF — MP4 fragmentado, desde la versión 1.13), así como MPEG-TS over HTTP. Se admiten HTTPS (SSL) y HTTP/3 (QUIC). La entrega se activa en la pestaña OTT de los ajustes de Stream.

Las URL de conexión tienen el formato:

host y port se configuran en los ajustes de http server.

streamID del stream. No confundir con el número de orden en la lista de streams. El ID se muestra en la parte superior de la página de estadísticas del stream y en la columna ID de la lista de streams; se establece al crear el stream y nunca cambia.

De forma análoga para HLS, DASH y Low-Latency HLS (los dos últimos — solo en OTT/HLS/LL-HLS/LL-Dash, véase más abajo):

En la página de estadísticas del stream se muestran las URLs de los protocolos conectados (como plantilla) y su estado actual. El acceso no autorizado está prohibido; los clientes deben estar declarados en Peers.

Para HLS y DASH la URL admite parámetros adicionales (opcionales):

[URL]?a=1&s=40&m=40&v=5&h3=1

  • a: 1 — ruta absoluta en la playlist, 0 — ruta relativa (por defecto).

  • s: duración de la playlist dinámica (s), 40 s por defecto.

  • m: duración mínima de la playlist dinámica (s), 40 s por defecto. Tamaño máximo de la playlist dinámica 60 s. Si el tamaño actual del búfer de chunks es menor que el tamaño mínimo solicitado en la petición, se devuelve un error 404. Así HLS arranca con un búfer de chunks lleno en el servidor.

  • v: la versión del protocolo HLS emitida en la lista de reproducción. Por defecto, el valor depende del modo HLS (véase más abajo): OTT/HLS y OTT/HLS/LL-HLS/LL-Dash6, Peering/HLS3. Un valor explícito en la URL anula el valor por defecto. Cambiar la versión puede ser necesario para la compatibilidad con un cliente HLS concreto.

  • h3: opt-in a HTTP/3 (QUIC) para esta sesión OTT. Hace que el servidor emita una cabecera Alt-Svc en la respuesta, tras lo cual un reproductor / navegador compatible cambia a QUIC. Véase la sección HTTP/3 (QUIC) más abajo.

Para compatibilidad con algunos clientes HLS se puede añadir el nombre de archivo index.m3u8 a la URL, p. ej. http://host:port/hls/stream/login/password/index.m3u8.

El modo de entrega se establece con el ajuste de stream OTT HLS (pestaña OTT): Peering/HLS, OTT/HLS o OTT/HLS/LL-HLS/LL-Dash.

Peering/HLS — un modo con división simple en segmentos (chunks). Recomendado para el peering (distribución) de flujos. Se entrega solo HLS sobre MPEG-TS (/hls). Por defecto, la lista de reproducción se sirve como EXT-X-VERSION:3 para compatibilidad con los clientes peer.

OTT/HLS — un modo con una división de segmentos optimizada para un inicio rápido de los reproductores en la difusión OTT. En este modo la carga de CPU es mayor; se recomienda para la difusión. Se entrega HLS sobre MPEG-TS (/hls). Por defecto, la lista de reproducción se sirve como EXT-X-VERSION:6 con EXT-X-INDEPENDENT-SEGMENTS y el atributo CHARACTERISTICS en EXT-X-MEDIA TYPE=SUBTITLES (Apple HLS, hls.js, Safari, dash.js/Shaka). Si un cliente concreto necesita un valor anterior, establézcalo mediante el parámetro de consulta ?v= (véase la lista de parámetros más arriba).

OTT/HLS/LL-HLS/LL-Dash — un modo de entrega sobre CMAF (MP4 fragmentado, fMP4; desde la versión 1.13). El stream genera segmentos fMP4 (.m4s + un init.mp4 común, mimeType="video/mp4"), sobre los que se entregan:

  • MPEG-DASH en /dash — ahora sobre CMAF, y no sobre MPEG-TS;

  • Low-Latency HLS en /llhls (véase Low-Latency HLS más abajo);

  • con el ajuste de stream Enable TS Chunk activado (por defecto true) — adicionalmente HLS legacy sobre MPEG-TS (/hls), como en OTT/HLS; con false se entregan solo segmentos fMP4, lo que ahorra disco y CPU.

La lista de reproducción HLS en OTT/HLS/LL-HLS/LL-Dash también es EXT-X-VERSION:6 por defecto.

Nota

MPEG-DASH y Low-Latency HLS solo están disponibles en OTT/HLS/LL-HLS/LL-Dash. En OTT/HLS y Peering/HLS se entrega exclusivamente HLS sobre MPEG-TS.

Se puede habilitar SSL (HTTPS) en el servidor HTTP desde sus ajustes.

Chunk Min Interval y Chunk Max Interval

En modo OTT se analiza el flujo en busca de PAT/PMT/SPS/PPS/IFrame y los chunks se recortan según el criterio de arranque rápido de los reproductores. El análisis comienza en min interval y, si por alguna razón los datos no se encuentran, el chunk se recorta forzosamente en max interval.

GOP-aligned segments

En OTT/HLS, el segmentador alinea los límites de los chunks con los puntos de acceso aleatorio y distingue entre IDR y un I-frame ordinario. Si la fuente emite con closed-GOP (con IDR), cada segmento TS .ts comienza garantizadamente con SPS / PPS / IDR (para HEVC — también VPS) — un verdadero punto de entrada desde el cual el reproductor abre el flujo. Si la fuente es open-GOP / sin IDR, el límite es el I-frame más cercano (el comportamiento anterior). El segmentador recorta la «cola» del GOP anterior — los slices P / B — de la ventana entre el PAT inicial y el primer SPS del chunk. Esto:

  • elimina el cuadro negro inicial al comienzo de la sesión VOD (?t= / ?epg=) en hls.js, Safari y VLC: el decodificador MSE recibe el conjunto correcto de unidades NAL de cabecera ya en el primer segmento y arranca de inmediato;

  • alinea la salida con HLS RFC 8216 §3 («Each Media Segment MUST contain a SPS and a PPS that decode its first Access Unit»);

  • hace correcta la declaración EXT-X-INDEPENDENT-SEGMENTS en una playlist EXT-X-VERSION:6.

Se controla con el ajuste por flujo gop-aligned-segment (por defecto true). Se aplica solo cuando HLS = OTT/HLS: en Peering/HLS y para los flujos MPTS el comportamiento no cambia. El PSI (PAT / PMT) y el audio se conservan íntegramente; el único efecto secundario es un salto de un cuadro del continuity_counter en el PID de vídeo en la frontera entre dos chunks contiguos, lo que constituye un decode boundary legítimo para HLS / DASH MSE.

Al alinear con los puntos de entrada, la duración real del chunk puede redondearse hacia arriba más allá de Chunk Min Interval. Por ello, en la lista de reproducción live el valor EXT-X-TARGETDURATION refleja la duración de segmento real máxima (sección 4.3.3.1 de RFC 8216) en lugar del mínimo configurado. Esto mantiene el manifiesto dentro del estándar: hls.js y los reproductores compatibles no acortan el intervalo de actualización de la lista de reproducción ni emiten bufferStalledError falsos.

Low-Latency HLS (/llhls)

En OTT/HLS/LL-HLS/LL-Dash, además de /dash está disponible un endpoint Low-Latency HLS aparte — entrega de baja latencia sobre los mismos segmentos fMP4 de CMAF:

La lista de reproducción de medios se divide en segmentos parciales (parts, directivas #EXT-X-PART): el reproductor inicia la reproducción sin esperar a que el segmento completo esté listo. Se aplican la recarga bloqueante de la lista (el servidor retiene la solicitud hasta que el siguiente part esté listo) y la sugerencia de precarga #EXT-X-PRELOAD-HINT.

La duración objetivo de un part se establece con el ajuste de stream Part Target Duration (ms; por defecto 500, rango 100–5000). El valor se aplica al vuelo, sin reiniciar el flujo, y debe ser menor que Chunk Min Interval.

HLS / DASH / LL-HLS Adaptive Multistream

HLS Adaptive Multistream se admite desde la versión 1.10, DASH Adaptive Multistream desde la versión 1.12 (desde la versión 1.13 — sobre CMAF), y Low-Latency HLS Adaptive desde la versión 1.13.

Para los flujos adaptativos se configura una lista de reproducción aparte. Para ello:

  • Activar la entrega OTT en los flujos que formarán parte de la lista adaptativa: OTT/HLS — para HLS adaptativo sobre MPEG-TS; OTT/HLS/LL-HLS/LL-Dash — para DASH / Low-Latency HLS adaptativos sobre CMAF.

  • En el menú principal aparecerá una sección de flujos adaptativos. Allí hay que añadir un flujo e indicar todos los flujos que deben incluirse en esta lista de reproducción.

  • Los flujos pueden tener establecido un parámetro de bitrate. Por defecto es 0 — el bitrate se toma del valor medido; de lo contrario, puede establecerse de forma explícita.

Para playlists adaptativas la URL es distinta:

A los peers (clientes) se les pueden imponer restricciones de acceso a los flujos adaptativos, igual que a los normales. El permiso para un flujo adaptativo incluye el permiso para todos los flujos que lo componen.

HTTP/3 (QUIC)

Desde la versión 1.13.1.438, Perfect Streamer incorpora un servidor HTTP/3 integrado para la entrega OTT de HLS, MPEG-DASH y Low-Latency HLS sobre QUIC (RFC 9000 + RFC 9114). La pila es ngtcp2 (QUIC v1) + nghttp3 (HTTP/3 frame layer); la infraestructura TLS reutiliza el mismo certificado que el listener HTTPS.

QUIC sirve únicamente rutas OTT:

  • / — redirección raíz,

  • /hls/..., /dash/... y /llhls/... — master playlists / MPD,

  • /h<sessID>/... — URL por sesión (media playlists, segmentos, VTT),

  • /http/<stream>/... — MPEG-TS bruto sobre HTTP.

Low-Latency HLS y DASH se entregan sobre QUIC de forma incremental (chunked): las parts se envían al cliente a medida que están listas, sin esperar al segmento completo.

Las rutas administrativas (/data, /config, /xmltv, /db/, /login, /logout, /restart) devuelven 404 sobre QUIC — la API de administración permanece en HTTPS / HTTP-TCP. Se trata de una restricción deliberada para reducir la superficie de ataque del listener QUIC.

Activación

Los ajustes de QUIC se encuentran en la sección Configuration / HTTP server (nodo /config/http-server):

  • HTTP/3 Enable (http3-enable) — indicador global que activa el listener QUIC. Valor por defecto: off. Al activarlo se abre un socket UDP en http3-port; al desactivarlo se cierra.

  • HTTP/3 Port (http3-port) — puerto UDP del listener QUIC. Valor por defecto: 43984. QUIC opera sobre UDP y HTTPS sobre TCP; los puertos pueden coincidir o diferir, sin conflicto entre ellos.

  • HTTP/3 0-RTT Enable (http3-zero-rtt-enable) — permite el handshake 0-RTT (RFC 9001 §4.6.1) en conexiones reanudadas del mismo cliente. Reduce la latencia de inicio; debe considerarse el riesgo de reproducción para solicitudes no idempotentes (es seguro para cargas OTT de sólo lectura). Valor por defecto: on.

Los cambios de configuración se aplican en caliente, sin reiniciar el servicio.

El certificado y la clave se toman del listener HTTPS — no existe una configuración de certificado independiente para HTTP/3. Si HTTPS no está configurado (ssl-enable=false), activar HTTP/3 no surte efecto — el handshake no se completará.

El parámetro ?h3 — opt-in por sesión

Un navegador no se conecta directamente a un endpoint HTTP/3 — primero accede por HTTPS/TCP, recibe en la respuesta la cabecera Alt-Svc: h3=":<port>"; ma=86400 (RFC 7838), y sólo las solicitudes posteriores a ese origin se conmutan a QUIC.

En Perfect Streamer, la emisión de Alt-Svc es opt-in por sesión OTT. El servidor no anuncia QUIC a todos los clientes indistintamente: el cliente debe solicitar HTTP/3 de forma explícita mediante el parámetro de consulta ?h3 en la URL master HLS / DASH.

Valor en la URL

Valor opt-in

Alt-Svc en la respuesta

parámetro ausente

off (default)

no

?h3 (sin valor)

on

?h3=1, ?h3=on, ?h3=yes, ?h3=true

on

?h3=0, ?h3=off, ?h3=no, ?h3=false

explicit off

no (como si estuviera ausente)

Una vez que el cliente ha obtenido la URL de sesión /h<sess>/..., el indicador wantH3 queda almacenado en la sesión — todas las actualizaciones posteriores de media playlist, los GET de segmentos y los GET de fragmentos VTT bajo esa URL de sesión reciben también Alt-Svc, aunque ?h3 no figure en la propia solicitud. Sin opt-in en el master no se instaura comportamiento sticky.

Filtrado de Alt-Svc:

  1. El listener QUIC está activado globalmente (http3-enable=true); de lo contrario el anuncio se suprime aunque se especifique ?h3.

  2. La solicitud no llegó por QUIC — en solicitudes que ya se sirven sobre H3, Alt-Svc carece de sentido y no se emite.

  3. Por sesión o por URL wantH3=true (véase la tabla anterior).

  4. Los servidores de administración y EPG nunca emiten Alt-Svc — no disponen de listener QUIC.

Este comportamiento es conforme con la RFC 7838 §3 — el propio servidor decide en qué respuestas emite Alt-Svc.

Escenario de conmutación a QUIC en el navegador

Flujo típico (Chrome / Firefox / Safari):

  1. El reproductor abre la URL master con opt-in explícito:

    https://stream.example.com:41982/hls/test1/login/password/index.m3u8?h3=1
    
  2. La primera solicitud va por HTTPS/TCP. En la respuesta el servidor emite Alt-Svc: h3=":43984"; ma=86400.

  3. El navegador almacena la asociación stream.example.com:41982 h3=":43984". En Chrome puede verse en la página chrome://net-internals/#alt-svc; en Firefox — about:networking#http3.

  4. En las solicitudes posteriores al mismo origin (actualización de playlist, GET de segmentos, VTT) el navegador abre una conexión QUIC en udp/43984 y continúa sobre HTTP/3. En DevTools → Network la columna Protocol de esas solicitudes pasa a h3.

Si la conexión QUIC no puede establecerse (puerto UDP bloqueado por el cortafuegos, certificado no confiable en el sistema), el navegador permanece de forma transparente en HTTPS/TCP — funcionalmente el flujo continúa reproduciéndose sin interrupción.

Contabilización de clientes y supervisión

La dirección IP real de un cliente QUIC en la contabilización de pares activos (/data/http-clients, límites de conexiones concurrentes, ACL por IP) es la dirección de pair real de la conexión QUIC, no la de loopback del puente backend interno.

El atributo ott-type en /data/http-clients es compuesto, con el formato <PROTO>/<scheme>:

  • PROTO — protocolo OTT: HLS, DASH o HTTP.

  • scheme — el transporte de red real: http, https o quic.

Ejemplos: HLS/quic, DASH/https, HTTP/http. La interfaz de administración muestra tanto el protocolo OTT como el transporte de red de cada cliente en una sola columna.

El tiempo de espera de una sesión OTT es de 60 segundos, independientemente del transporte. Cuando el cliente cambia entre transportes, el esquema solo se actualiza hacia arriba según la cadena de prioridad httphttpsquic. Un retroceso paralelo a una conexión menos segura no sobrescribe el tipo actual — en la contabilización permanece el transporte más seguro observado.

Compatibilidad de reproductores

HTTP/3 para OTT está soportado por:

  • iOS AVPlayer / Safari — de forma nativa, mediante Alt-Svc; con 0-RTT.

  • Chrome, Firefox, Edge, Brave — mediante Alt-Svc; HLS se reproduce con hls.js y DASH con dash.js / Shaka; el tráfico del reproductor hereda HTTP/3 de la fetch API del navegador.

  • Android ExoPlayer — mediante el motor QUIC Cronet (no es el transporte predeterminado; requiere configuración en el cliente).

VLC (libVLC) no soporta HTTP/3 — en estos clientes el flujo sigue funcionando sobre HTTPS/TCP, sin el beneficio de QUIC.

La ventaja real de QUIC frente a HTTPS/TCP se aprecia sobre todo en redes móviles / Wi-Fi / redes con pérdidas:

  • ausencia de head-of-line blocking en la capa TCP — una pérdida en un flujo HTTP no retrasa a los demás;

  • handshake 0-RTT en conexiones reanudadas del mismo cliente;

  • tasa de bits ABR más estable con pérdidas de paquetes del 1 al 5 %.

En redes gestionadas / Wi-Fi corporativo, la ventaja suele ser modesta — del 5 al 10 % en la latencia de inicio y prácticamente nula en régimen estacionario. La carga de CPU del servidor es mayor con QUIC que con el TCP del kernel — es el coste de una pila TLS + transporte en espacio de usuario.

Modelo de caché para OTT HLS y DASH

El servidor genera respuestas en tres categorías, que difieren en la vida útil del contenido y en su idoneidad para cacheo en nodos intermedios (reverse proxy, CDN, caché del cliente).

1. Modelo de caché

1.1. Recursos y cabeceras HTTP

Recurso

URL

Content-Type

Cache-Control

Segmento TS (HLS)

/h<sess>/<keyHex>.ts, /h<sess>/sub/<pid>/<keyHex>.vtt

video/mp2t

public, max-age=60, immutable

Segmento fMP4 (DASH / LL-HLS)

/h<sess>/init.mp4, /h<sess>/<key|N>.m4s

video/mp4

public, max-age=60, immutable

DASH MPD

/h<sess>/index.mpd

application/dash+xml; charset=utf-8

public, max-age=1

HLS master

/hls/<stream>/<login>/<pass>/index.m3u8

application/vnd.apple.mpegurl

public, max-age=1

HLS media

/h<sess>/index.m3u8, /h<sess>/sub/<pid>/index.m3u8

application/vnd.apple.mpegurl

public, max-age=1

302 Redirect

/dash/<stream>/<login>/<pass>/index.mpd

no-cache, no-store

Raw TS

/http/<stream>/<login>/<pass>

video/mp2t

no establecido; no se cachea

1.2. Características de los segmentos

El identificador hexadecimal del segmento en la URL (<keyHex> en las rutas /h<sess>/<keyHex>.ts) se calcula como un CRC64 sobre el tiempo de inicio del segmento y el ID del flujo, y es globalmente único. La URL del segmento direcciona contenido inmutable — ante solicitudes repetidas a la misma URL se devuelve un flujo de bytes idéntico (mientras el segmento permanezca dentro de la ventana deslizante).

La directiva immutable suprime la revalidación condicional del cliente (If-None-Match, If-Modified-Since). El valor max-age=60 es compatible con un timeShiftBufferDepth=40s típico.

Los segmentos fMP4 de CMAF (.m4s) y el init.mp4 común para DASH / Low-Latency HLS se direccionan de forma análoga y se almacenan en caché según el mismo modelo (immutable, max-age=60). Los segmentos parciales (parts) de LL-HLS son solicitudes byte-range dentro del mismo .m4s, por lo que no forman una entrada de caché aparte.

1.3. Características de los manifest

max-age=1 limita el límite superior de obsolescencia del contenido en caché a un segundo. Junto con proxy_cache_lock on (nginx), los picos de solicitudes al manifiesto se coalescen en una única solicitud al origen por segundo.

1.4. Variabilidad del contenido

Con absPath=0 (valor por defecto, sin el parámetro de URL a) los manifiestos HLS media y DASH MPD no incluyen el identificador de sesión en el cuerpo. El contenido del manifiesto es idéntico entre sesiones que pertenecen a la misma combinación (stream, param). Esto permite que la caché del reverse-proxy reutilice una única entrada entre sesiones cuando la clave de caché está normalizada.

Con absPath=1 (parámetro de URL a=1) el cuerpo del manifiesto contiene URL absolutas que incluyen el esquema, el host y el identificador de sesión. El contenido pasa a ser específico de la sesión; la reutilización de caché entre sesiones no está disponible.

2. Comportamiento de clientes

Cliente

URL de actualización del manifest

Impacto en el número de sesiones

VLC 3.x HLS

/h<sess>/index.m3u8

Una sesión por reproducción

VLC 3.x DASH

/dash/<stream>/.../index.mpd

Se gestiona mediante session reuse (ver 3.3)

ffmpeg 5.x HLS

/h<sess>/index.m3u8

Una sesión por reproducción

ffmpeg 5.x DASH

/dash/<stream>/.../index.mpd (bucle de repetición)

Se gestiona mediante session reuse (ver 3.3)

dash.js, hls.js

/h<sess>/... a través de <Location> / URL de sesión

Una sesión por reproducción

3. Mecanismos especializados

3.1. Redirect HTTP 302 para DASH

Una solicitud de la forma /dash/<stream>/<login>/<pass>/index.mpd devuelve la respuesta 302 Found con la cabecera Location: /h<sess>/index.mpd. El cuerpo de la respuesta es vacío. La autenticación y la asignación de la sesión ocurren durante el procesamiento de la redirección.

Los clientes que admiten caché de redirecciones acceden directamente a la URL de la sesión en las solicitudes posteriores. Los clientes que no la admiten repiten la solicitud de redirección. El coste del reprocesamiento de la redirección se limita a la verificación de autenticación y a las operaciones de session reuse.

3.2. Session reuse para DASH

Al procesar una solicitud /dash/.../index.mpd del mismo login al mismo flujo (con el mismo indicador adaptive), el servidor encuentra una sesión DASH ya existente y devuelve su identificador de nuevo. No se crea una nueva sesión; no se consume un slot del límite de conexiones simultáneas.

Solo se aplica a DASH. Para HLS no se necesita un mecanismo de reuse separado: los clientes HLS actualizan la media playlist mediante la URL de sesión y no crean una nueva sesión en cada actualización.

3.3. Reutilización de segmentos entre sesiones

La ruta /h<sess>/<keyHex>.ts no depende de <sess> al resolver <keyHex> en contenido: <keyHex> identifica de forma globalmente única un segmento TS dentro de un flujo. Nginx con una clave de caché normalizada (que recorta el prefijo /h<sess>/) sirve cada solicitud del mismo <keyHex> desde una única entrada de caché, sin importar qué clientes la hayan emitido.

Lo mismo vale para los segmentos fMP4 de CMAF (.m4s) e init.mp4: su contenido es inmutable y se direcciona dentro del stream, por lo que la normalización de la cache key produce la misma deduplicación entre sesiones en la caché.

4. Parámetros de la petición

Parámetro

Valor por defecto

Impacto

a

0

1 — URL absolutas en los manifest; 0 — relativas

s

40

timeShiftBufferDepth en segundos

m

40

Longitud mínima de ventana para emitir el manifest

v

6 para OTT/HLS y OTT/HLS/LL-HLS/LL-Dash, 3 para Peering/HLS

#EXT-X-VERSION en HLS (ignorado por DASH); un valor explícito en la URL prevalece sobre el valor por defecto

h3

ausente (off)

opt-in a HTTP/3 (QUIC) — hace que el servidor emita Alt-Svc en la respuesta. Valores reconocidos: presence ⇒ on, 1/on/yes/true ⇒ on, 0/off/no/false ⇒ explicit off. Sticky en la URL de sesión /h<sess>/.... Véase la sección HTTP/3 (QUIC).

Cambiar un parámetro mediante query string actualiza los valores guardados en la sesión en su próxima reapertura.

5. Características de carga

La carga sobre el origen escala con el número de streams distintos vistos simultáneamente. El aumento del número de clientes que ven el mismo stream no incrementa el número de solicitudes al origen cuando hay un caché reverse-proxy con clave de caché normalizada.

Escenario

Frecuencia de peticiones origin (ref.)

1 cliente por flujo X

MPD: 0.4 req/s, segment: 0.2 req/s

N clientes en un mismo flujo X (caché activada)

MPD: 1 req/s, segment: 0.2 req/s

N clientes ffmpeg en modo de reproducción sobre un mismo flujo

MPD: 1 req/s (con proxy_cache_lock)

N clientes en N flujos distintos

MPD: 0.4·N req/s, segment: 0.2·N req/s

6. Nginx como reverse proxy con caché

6.1. Configuración base

proxy_cache_path /var/cache/nginx/pss_segments
    levels=1:2 keys_zone=pss_segments:100m
    max_size=20g inactive=30m use_temp_path=off;

proxy_cache_path /var/cache/nginx/pss_manifests
    levels=1:2 keys_zone=pss_manifests:10m
    max_size=256m inactive=5m use_temp_path=off;

upstream pss_backend {
    server 127.0.0.1:41972;
    keepalive 64;
}

map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "stream:$tail";
    default                                     $uri;
}

server {
    listen 80;
    server_name stream.example.com;

    location ~* "^/h[0-9a-f]{16}(/[0-9]+)?/([0-9a-f]+\.(ts|m4s)|init\.mp4)$" {
        proxy_cache               pss_segments;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 60s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_use_stale     updating error timeout;
        proxy_cache_revalidate    on;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_buffering           on;
    }

    location ~* "(^/h[0-9a-f]{16}(/[0-9]+)?/index\.(m3u8|mpd)$|^/(hls|dash)/.*\.(m3u8|mpd)$)" {
        proxy_cache               pss_manifests;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 1s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_lock_timeout  2s;
        proxy_cache_use_stale     updating;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
    }

    location / {
        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_set_header          X-Forwarded-Proto $scheme;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_buffering           off;
        proxy_read_timeout        3600s;
    }
}

6.2. Función de las directivas

Directiva

Propósito

proxy_cache_lock on

Serializa las peticiones upstream cuando hay cache miss simultáneos para la misma clave

proxy_cache_use_stale updating

Devuelve la copia obsoleta a peticiones paralelas durante la actualización de la caché

proxy_cache_revalidate on

Usa If-Modified-Since en cache miss con copia guardada

proxy_cache_valid 404 403 0s

Prohíbe la caché de errores de autorización y 404

keepalive 64 en upstream

Mantiene un pool de conexiones persistentes al origin

proxy_buffering on

Para los segmentos; activa el bufferizado de la respuesta en nginx

proxy_buffering off

Para la sección /; desactiva el buffering (raw streaming)

6.3. Cálculo de max_size del caché de segmentos

Valor orientativo: bitrate × timeShiftBufferDepth × distinct_streams × 2

Ejemplo: 10 flujos × 8 Mbps × 40 s × 2 ≈ 800 MB. Se recomienda un margen 10× para absorber la variabilidad del bitrate.

6.4. Terminación TLS

El servidor Perfect Streamer acepta conexiones en los puertos HTTP y HTTPS. Con terminación TLS en nginx, el upstream usa el puerto HTTP. El reenvío de las cabeceras X-Forwarded-Proto y X-Forwarded-Host es obligatorio para la formación correcta de URL absolutas cuando absPath=1.

server {
    listen 443 ssl http2;
    server_name stream.example.com;

    ssl_certificate           /etc/letsencrypt/live/stream.example.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/stream.example.com/privkey.pem;
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_session_cache         shared:SSL:10m;
    ssl_session_timeout       1d;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location ... {
        proxy_pass                http://pss_backend;
        proxy_set_header          X-Forwarded-Proto https;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_set_header          Host              $host;
        # + caching directives from 6.1
    }
}

server {
    listen 80;
    server_name stream.example.com;
    return 301 https://$host$request_uri;
}

Para HTTPS entre nginx y origin se usan proxy_ssl_verify y proxy_ssl_trusted_certificate. Para conexiones loopback el cifrado es redundante.

6.5. Multi-host

Si un mismo proceso nginx atiende varios server_name, se añade $host a la cache key para aislar el contenido:

map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "$host:stream:$tail";
    default                                     "$host:$uri";
}

El tamaño de keys_zone se calcula como 8000 claves/MB. Para instalaciones multi-host con miles de flujos se recomienda keys_zone=...:300m o superior.

7. Caché del cliente

Cache-Control: immutable lo respetan los navegadores Chrome/Firefox/Safari. El caché del cliente devuelve el segmento sin solicitud condicional al volver a acceder (incluido seek hacia atrás dentro del búfer del reproductor).

Los Service Workers pueden aplicar una estrategia cache-first basada en el contenido de Cache-Control. Los reproductores DASH (dash.js, Shaka) usan MSE a través de SourceBuffer; un segmento colocado en el búfer permanece disponible sin nueva solicitud HTTP hasta que sale de la ventana.

Para solicitudes entre dominios, la cabecera Access-Control-Allow-Origin: * permite el caché en shared caches sin Vary: Origin. Cambiar el valor de ACAO a un Origin específico requiere Vary: Origin, lo que reduce la eficiencia del shared cache.

8. Distribución mediante CDN

Perfect Streamer es compatible con CDNs en modo pull-from-origin (Cloudflare, Akamai, Fastly, BunnyCDN, Amazon CloudFront).

Origin shield. Se recomienda colocar uno o varios nodos shield entre el edge del CDN y el origin para reducir la frecuencia de peticiones al origin cuando los clientes están distribuidos globalmente.

Purge. Los segmentos content-addressed no necesitan purge. Si cambian los metadatos del stream (códec, resolución), los manifiestos se actualizan dentro de max-age=1 sin purge explícito.

Cache warming. Ante un aumento previsto de carga en un stream, se puede precalentar la CDN desde varios puntos geográficos antes del inicio de la emisión.

Distribución geográfica. Los segmentos (max-age=60) son adecuados para caché distribuido geográficamente. Los manifiestos (max-age=1) toleran hasta un segundo de retardo de entrega — aceptable para live no low-latency.

9. Monitorización

9.1. X-Cache-Status

Añadir add_header X-Cache-Status $upstream_cache_status; en cada location con caché. Valores:

Valor

Descripción

HIT

Respuesta desde caché

MISS

No estaba en caché; obtenido del origin y almacenado

EXPIRED

Caducado, actualizado

UPDATING

Copia stale devuelta a una solicitud paralela durante la actualización

STALE

use_stale devolvió la copia caducada (origin no disponible)

REVALIDATED

Origin devolvió 304 Not Modified

BYPASS

Se activó proxy_cache_bypass

9.2. Formato de access-log

log_format pss_cache '$remote_addr $status $request_method "$request" '
                     '$body_bytes_sent rt=$request_time ut=$upstream_response_time '
                     'cache=$upstream_cache_status key=$pss_cache_key';

server {
    access_log /var/log/nginx/pss.log pss_cache;
}

9.3. Métricas

El módulo nginx-vts exporta métricas por zona en formato Prometheus:

GET /status/format/prometheus

Umbrales recomendados para alertas:

Métrica

Umbral

Causa posible

Segment HIT rate

< 90 % en 5 minutos

Normalización de cache key rota; max_size pequeño

Manifest MISS rate

> 50 % en 1 minuto

proxy_cache_lock no serializa las peticiones

Upstream response time p95

> 500 ms en 1 minuto

Sobrecarga del origin

Cache zone fill

> 90 % en 10 minutos

Aproximándose a max_size; se prevé desalojo LRU

10. Diagnóstico

Síntoma

Causa probable

Solución

Tasa HIT de segmento baja

Vary: Origin con alta variabilidad de Origin; normalización rota en map

Comprobar las cabeceras y la regex en la directiva map

404 en segmentos tras salir de la ventana

404 cacheado para un segmento que salió de la ventana deslizante

Añadir proxy_cache_valid 404 0s en la location segments

Retraso del inicio de reproducción 2–5 s

proxy_cache_lock_timeout supera la latencia objetivo

Reducir a 1–2 s; activar proxy_cache_use_stale updating

El manifest no se actualiza

proxy_cache_valid sobrescribe max-age

Establecer explícitamente proxy_cache_valid 200 1s

Aumento de TIME_WAIT en upstream

Falta keepalive en el bloque upstream

Añadir keepalive 64, proxy_http_version 1.1, proxy_set_header Connection ""

403 en /dash/.../<segment>.m4s desde ffmpeg

El cliente resuelve URLs relativas frente a la URL previa al redirect

El servidor emite <BaseURL>/h<sess>/</BaseURL> (ruta absoluta); compatible en la build actual

Lags, rebuffering frecuente en clientes remotos

Throughput TCP efectivo bajo debido a slow start e idle restart con RTT grandes (300 ms y superiores)

Ajuste de la pila de red de Linux en el origin: véase 10.1

10.1. Ajuste TCP del origin para clientes high-RTT

El problema se manifiesta en clientes con un RTT grande hasta el origin (por ejemplo 300 ms o superior) cuando el bitrate del flujo se acerca a la capacidad del canal. Síntomas en el reproductor (VLC, ffmpeg, dash.js) — rebuffering frecuente, warnings del tipo ES_OUT_SET_PCR called too late (aumento de pts_delay), buffer deadlock prevented, interrupciones del flujo. En el servidor, mientras tanto, el cliente parece normal, no hay errores, y el throughput en /data/stream/... se corresponde con el flujo de entrada.

Causa:

  • TCP slow start. Cada nueva conexión TCP comienza con un congestion window de aproximadamente 14 KB y lo aumenta a lo largo de varios RTT. Con un RTT de 300 ms, alcanzar la ventana completa lleva 2-3 segundos. Durante ese tiempo, un segmento HLS/DASH de 5 s de duración (4-6 MB) se descarga apreciablemente más lento que en tiempo real.

  • TCP idle restart. Entre solicitudes de segmentos, un cliente HLS en pull-model hace una pausa de 4-5 s. Por defecto, tras tal pausa el kernel de Linux restablece el congestion window de la conexión al initial cwnd (comportamiento net.ipv4.tcp_slow_start_after_idle=1). Como resultado, en el siguiente GET la conexión keep-alive reanuda la transmisión desde slow start — incluso en una sesión ya calentada.

Una agravante adicional — el congestion control CUBIC por defecto se comporta mal con RTT largos y pérdidas de paquetes en tramos intermedios de la red.

Solución — dos parámetros sysctl en el origin:

# Keep congestion window across idle pauses inside keep-alive sessions.
sysctl -w net.ipv4.tcp_slow_start_after_idle=0

# Use BBR instead of CUBIC: better behaviour on long-RTT paths
# with mild packet loss; paces sending instead of bursting.
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr

Para una aplicación persistente:

cat > /etc/sysctl.d/99-pss-net.conf <<EOF
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_congestion_control    = bbr
EOF
sysctl --system

El efecto principal lo proporciona el primer parámetro (tcp_slow_start_after_idle=0). Elimina directamente el slow start repetido entre solicitudes de segmentos dentro de una misma conexión keep-alive. El segundo (BBR) aporta robustez adicional y se aplica a todas las nuevas conexiones.

El ajuste no requiere reiniciar Perfect Streamer y se aplica a todas las nuevas conexiones TCP inmediatamente después de sysctl -w. Las conexiones existentes conservan el congestion control con el que fueron establecidas.

11. Seguridad

11.1. Session URL

Una URL en el formato /h<sess>/... cumple la función de token de sesión — no requiere reautenticación. La duración está acotada por el idle timeout (valor 30 s). En caso de inactividad, la sesión es eliminada por la tarea cleaner.

Requisitos:

  • HTTPS en todas las rutas OTT (/hls/, /dash/, /h<sess>/) en producción

  • El Session ID en la cabecera Location del 302 no se cachea (no-cache, no-store)

11.2. Rate limiting

limit_req_zone $binary_remote_addr zone=dash_top:10m  rate=5r/s;
limit_req_zone $binary_remote_addr zone=hls_top:10m   rate=5r/s;
limit_req_zone $binary_remote_addr zone=llhls_top:10m rate=5r/s;

server {
    location /dash/ {
        limit_req zone=dash_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /hls/ {
        limit_req zone=hls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /llhls/ {
        limit_req zone=llhls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
}

Las URLs de sesión (/h<sess>/) no requieren rate limiting — su procesamiento es barato y las respuestas se cachean.

11.3. Caché de respuestas de error

proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;

Prohíbe la caché de redirecciones (sess único en Location) y de respuestas con errores de autorización o recurso inexistente.

11.4. Restricción del acceso de red al origin

El puerto 41972 (41982 para HTTPS) debe estar cerrado al tráfico externo. Configuraciones admitidas:

  1. Bindear Perfect Streamer a 127.0.0.1 (con nginx local)

  2. Regla de firewall:

iptables -A INPUT -p tcp --dport 41972 ! -s 10.0.0.0/8 -j DROP

12. Integración con middleware

12.1. Modelo prefix-login

Perfect Streamer permite delegar la identificación de usuarios a middleware/sistema de facturación mediante el mecanismo prefix-login. El conector externo al sistema de facturación no se incluye en la versión actual.

Configuración del usuario embedded:

{
  "id": 9,
  "login": "sub",
  "password": "xxx",
  "is-prefix": true,
  "max-conn-http-hls": 1,
  "accept-stream": [ ... ]
}

Con is-prefix: true el servidor acepta URLs cuyo login tiene la forma <prefix><billing_user_id>:

/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8

12.2. Formato de estadísticas

<clients>
  <client login-id="-1974387287" login="sub" match-login="sub42"
          sess-id="11331..." ott-type="dash" stream-id="10000" .../>
  <client login-id="-2147031294" login="sub" match-login="sub43"
          sess-id="11132..." ott-type="dash" stream-id="10000" .../>
</clients>

El campo login-id contiene el hash del login URL. login es el valor configurado. match-login es el login URL usado por el cliente.

12.3. Limitaciones del prefix-login

  • Contraseña compartida. Todos los suscriptores del pool prefix usan un único valor de contraseña. Su compromiso otorga acceso a cualquier <prefix><string>.

  • Granularidad de ACL. accept-stream se aplica a todo el pool prefix; no hay ACL por suscriptor sin facturación externa.

  • Rotación de contraseña. El cambio de contraseña desconecta a todos los suscriptores activos. Una sustitución progresiva requiere el uso temporal de dos prefix-logins.

13. Subtítulos WebVTT

La fuente de subtítulos es DVB Teletext / DVB Subtitling del MPEG-TS de entrada. Las pistas de subtítulos Teletext deben estar presentes en las secciones Media Information o Original Media Information. La sección Analyzer también permite verificar que los paquetes de los PID correspondientes están activos.

Para OTT HLS/DASH debe activarse el modo OTT (en Peering/HLS los subtítulos WebVTT no están disponibles). En la sección Output # OTT el contador de chunks OTT WebVTT buffer chunk count debe ser distinto de cero.

Para diagnosticar los subtítulos, activar Analyze y Trace en el stream. Al iniciar el flujo, el log del stream debe mostrar:

Start Teletext subtitle decoder
[ttxsubdec] ttx: pid=331 magazine=8 page=0x88 lang=***

A continuación, el log registra el texto decodificado de los subtítulos.

13.1. URL de los segmentos VTT

Esquema

URL

Contenido

HLS master

/hls/.../index.m3u8

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",...,URI="/h<sess>/sub/<pid>/index.m3u8"

HLS subtitle playlist

/h<sess>/sub/<pid>/index.m3u8

lista <keyHex>.vtt con #EXTINF

Segmento VTT HLS

/h<sess>/sub/<pid>/<keyHex>.vtt

VTT con X-TIMESTAMP-MAP estilo HLS

DASH MPD AdaptationSet

en index.mpd

contentType="text" mimeType="text/vtt" + <SegmentTemplate media="$Number$.vtt">

Segmento VTT DASH

/h<sess>/sub/<pid>/<seq>.vtt

VTT con X-TIMESTAMP-MAP estilo DASH

<keyHex> es un CRC64 hex de 16 caracteres a partir del tiempo de inicio del segmento, el ID del flujo y el PID de la pista de subtítulos. <seq> es el número decimal secuencial de un chunk del flujo de subtítulos (la numeración de subtítulos no está relacionada con la de los chunks TS).