Servicio OTT

Emite flujos por protocolos basados en HTTP — HLS, MPEG-DASH (desde la versión 1.12) y MPEG-TS over HTTP. Soporta HTTPS (SSL). La emisión se activa en la pestaña OTT de los ajustes 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 similar para HLS y DASH:

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 la URL admite parámetros adicionales (opcionales):

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

  • a: 1 — rutas absolutas en la playlist (por defecto), 0 — rutas relativas.

  • 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 mínimo solicitado, se devolverá el error 404. Esto se hace para que HLS arranque desde un búfer de chunks ya lleno en el servidor.

  • v: versión del protocolo HLS indicada en la playlist. Por defecto 5; algunos clientes HLS pueden requerir cambiarla.

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.

Hay 2 modos de funcionamiento del servidor HLS — Peer mode y OTT mode.

Peer mode — modo con segmentación (chunks) simple. Recomendado para peering / distribución de flujos.

OTT mode — modo con una segmentación optimizada para emisión OTT y arranque rápido de los reproductores. La carga de CPU es mayor; recomendado para emisión.

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 PAT/PMT/SPS/PPS/IFrame y los chunks se cortan según el criterio de inicio rápido de los reproductores. El análisis comienza en min interval; si por alguna razón los datos no se encuentran, el chunk se corta forzosamente en max interval.

HLS Adaptive Multistream

Desde la versión 1.10 se soporta HLS Adaptive Multistream y desde la 1.12 DASH Adaptive Multistream

Para los flujos adaptativos se configura una playlist HLS dedicada. Para ello:

  • En los flujos que se incluirán en la playlist adaptativa, activar HLS con OTT Mode.

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

  • A los flujos se les puede asignar un parámetro de bitrate. Por defecto 0 significa que se usa el valor medido; en caso contrario se puede fijar explícitamente.

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.

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

/h<sess>/<keyID>.ts, /h<sess>/<subID>/<keyID>.ts

video/mp2t

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>/<subID>/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 TS

El identificador keyID se forma como CRC64(startTime || streamID) y es globalmente único. La URL de un segmento direcciona contenido inmutable — ante solicitudes repetidas de 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.

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 (por defecto, sin el parámetro de URL «a»), los manifiestos HLS media y DASH MPD no contienen el identificador de sesión en el cuerpo. El contenido del manifiesto es idéntico entre sesiones pertenecientes a una misma combinación (stream, param). Esto permite que el caché del reverse-proxy reutilice una entrada entre sesiones con normalización de la clave de caché.

Con absPath=1 (parámetro de URL «a=1»), el cuerpo del manifiesto contiene URL absolutas que incluyen esquema, host e identificador de sesión. El contenido se vuelve específico de la sesión — la reutilización del caché entre sesiones deja de ser posible.

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

En el procesamiento de la solicitud /dash/.../index.mpd ejecutada bajo login-id L para stream-id S y la flag adaptive=A, si en _ottClientList ya existe una sesión DASH con los mismos (L, S, A), se devuelve su sessID. No se crea una nueva sesión y no se consume ningún slot maxConn.

Solo se aplica a DASH. HLS no necesita un mecanismo de reuse separado: los clientes HLS actualizan la media playlist mediante la URL de sesión y no disparan applyNewOTTSess en cada refresh.

3.3. Reutilización de segmentos entre sesiones

La ruta /h<sess>/<keyID>.ts es independiente de sess al resolver keyID en contenido: keyID identifica de forma unívoca el segmento dentro de las ChunkList registradas (ver _ottStreamList). Nginx con clave de caché normalizada (eliminando el prefijo /h<sess>/) sirve todas las solicitudes del mismo keyID desde una única entrada de 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

3

#EXT-X-VERSION en HLS (DASH lo ignora)

Cambiar el parámetro mediante query string actualiza los valores guardados en la sesión al próximo applyNewOTTSess.

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$" {
        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;
        # + директивы кеширования из 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>.ts 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

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;

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;
    }
}

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 subscribers del pool prefix usan una sola contraseña. Su compromiso da 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 desconecta a todos los subscribers activos. Para una sustitución progresiva se requieren dos prefix-logins temporalmente.

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 Peer mode 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 el hex de 16 caracteres de CRC64(startTime, streamID, pid). <seq> es el número decimal del chunk en el subtitle storage (contador independiente del video storage).