Serviço OTT

Entrega fluxos por protocolos baseados em HTTP — HLS (sobre MPEG-TS), MPEG-DASH e Low-Latency HLS (sobre CMAF — MP4 fragmentado, desde a versão 1.13), bem como MPEG-TS over HTTP. São suportados HTTPS (SSL) e HTTP/3 (QUIC). A entrega é ativada na aba OTT das configurações de Stream.

As URLs de conexão têm o formato:

host e port são definidos nas configurações do http server.

streamID do stream. Não confundir com o número de ordem na lista de streams. O ID é exibido no topo da página de estatísticas do stream e na coluna ID da lista de streams; é definido na criação do stream e nunca muda.

De forma análoga para HLS, DASH e Low-Latency HLS (os dois últimos — apenas em OTT/HLS/LL-HLS/LL-Dash, veja abaixo):

Na página de estatísticas do stream são exibidas as URLs dos protocolos conectados (como template) e seu status atual. O acesso não autorizado é proibido; os clientes devem estar registrados em Peers.

Para HLS e DASH estão disponíveis parâmetros adicionais na URL (opcionais):

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

  • a: 1 — caminho absoluto na playlist, 0 — caminho relativo (padrão).

  • s: duração da playlist dinâmica (s), 40 s por padrão.

  • m: duração mínima da playlist dinâmica (s), 40 s por padrão. Tamanho máximo da playlist dinâmica 60 s. Se o tamanho atual do buffer de chunks for menor que o tamanho mínimo solicitado na requisição, é retornado um erro 404. Assim, o HLS arranca com um buffer de chunks cheio no servidor.

  • v: a versão do protocolo HLS emitida na playlist. Por padrão, o valor depende do modo HLS (veja abaixo): OTT/HLS e OTT/HLS/LL-HLS/LL-Dash6, Peering/HLS3. Um valor explícito na URL substitui o padrão. Trocar a versão pode ser necessário para compatibilidade com um cliente HLS específico.

  • h3: opt-in para HTTP/3 (QUIC) nesta sessão OTT. Faz com que o servidor emita um cabeçalho Alt-Svc na resposta, após o que um leitor / browser compatível passa para QUIC. Ver a secção HTTP/3 (QUIC) abaixo.

Para compatibilidade com alguns clientes HLS é possível adicionar o nome de arquivo index.m3u8 à URL, ex.: http://host:port/hls/stream/login/password/index.m3u8.

O modo de entrega é definido pela configuração de stream OTT HLS (aba OTT): Peering/HLS, OTT/HLS ou OTT/HLS/LL-HLS/LL-Dash.

Peering/HLS — um modo com divisão simples em segmentos (chunks). Recomendado para o peering (distribuição) de fluxos. É entregue apenas HLS sobre MPEG-TS (/hls). Por padrão, a playlist é servida como EXT-X-VERSION:3 para compatibilidade com clientes peer.

OTT/HLS — um modo com divisão de segmentos otimizada para um início rápido dos players na transmissão OTT. Nesse modo, a carga de CPU é maior; é recomendado para transmissão. É entregue HLS sobre MPEG-TS (/hls). Por padrão, a playlist é servida como EXT-X-VERSION:6 com EXT-X-INDEPENDENT-SEGMENTS e o atributo CHARACTERISTICS em EXT-X-MEDIA TYPE=SUBTITLES (Apple HLS, hls.js, Safari, dash.js/Shaka). Se um cliente específico precisar de um valor anterior, defina-o pelo parâmetro de consulta ?v= (veja a lista de parâmetros acima).

OTT/HLS/LL-HLS/LL-Dash — um modo de entrega sobre CMAF (MP4 fragmentado, fMP4; desde a versão 1.13). O stream gera segmentos fMP4 (.m4s + um init.mp4 comum, mimeType="video/mp4"), sobre os quais são entregues:

  • MPEG-DASH em /dash — agora sobre CMAF, e não sobre MPEG-TS;

  • Low-Latency HLS em /llhls (veja Low-Latency HLS abaixo);

  • com a configuração de stream Enable TS Chunk ativada (padrão true) — adicionalmente HLS legacy sobre MPEG-TS (/hls), como no OTT/HLS; com false, são entregues apenas segmentos fMP4, o que economiza disco e CPU.

A playlist HLS no OTT/HLS/LL-HLS/LL-Dash também é EXT-X-VERSION:6 por padrão.

Nota

MPEG-DASH e Low-Latency HLS estão disponíveis apenas no OTT/HLS/LL-HLS/LL-Dash. No OTT/HLS e no Peering/HLS, é entregue exclusivamente HLS sobre MPEG-TS.

É possível habilitar SSL (HTTPS) no servidor HTTP nas configurações do servidor.

Chunk Min Interval e Chunk Max Interval

No modo OTT, o fluxo é analisado para PAT/PMT/SPS/PPS/IFrame e os chunks são fatiados segundo o critério de arranque rápido dos reprodutores. A análise começa em min interval e, se por algum motivo os dados não forem encontrados, o chunk é fatiado à força em max interval.

GOP-aligned segments

No OTT/HLS, o segmentador alinha as fronteiras dos chunks aos pontos de acesso aleatório e distingue entre IDR e um I-frame comum. Se a fonte transmite com closed-GOP (com IDR), cada segmento TS .ts começa garantidamente com SPS / PPS / IDR (para HEVC — também VPS) — um verdadeiro ponto de entrada a partir do qual o player abre o fluxo. Se a fonte for open-GOP / sem IDR, a fronteira é o I-frame mais próximo (o comportamento anterior). O segmentador remove a «cauda» do GOP anterior — os slices P / B — da janela entre o PAT inicial e o primeiro SPS do chunk. Isso:

  • elimina o quadro preto inicial no começo da sessão VOD (?t= / ?epg=) em hls.js, Safari e VLC: o decodificador MSE recebe o conjunto correto de unidades NAL de cabeçalho já no primeiro segmento e inicia imediatamente;

  • alinha a saída com HLS RFC 8216 §3 («Each Media Segment MUST contain a SPS and a PPS that decode its first Access Unit»);

  • torna correta a declaração EXT-X-INDEPENDENT-SEGMENTS em uma playlist EXT-X-VERSION:6.

Controlado pela configuração por stream gop-aligned-segment (padrão true). Aplica-se apenas quando HLS = OTT/HLS: em Peering/HLS e para streams MPTS o comportamento não muda. O PSI (PAT / PMT) e o áudio são preservados integralmente; o único efeito colateral é um salto de um quadro do continuity_counter na PID de vídeo na fronteira entre dois chunks vizinhos, o que constitui um decode boundary legítimo para HLS / DASH MSE.

Ao alinhar aos pontos de entrada, a duração real do chunk pode ser arredondada para cima além de Chunk Min Interval. Por isso, na playlist live o valor EXT-X-TARGETDURATION reflete a duração de segmento real máxima (seção 4.3.3.1 de RFC 8216) em vez do mínimo configurado. Isso mantém o manifesto dentro do padrão: hls.js e os players compatíveis não reduzem o intervalo de atualização da playlist nem emitem bufferStalledError falsos.

Low-Latency HLS (/llhls)

No OTT/HLS/LL-HLS/LL-Dash, além de /dash há um endpoint Low-Latency HLS separado — entrega de baixa latência sobre os mesmos segmentos fMP4 de CMAF:

A playlist de mídia é dividida em segmentos parciais (parts, diretivas #EXT-X-PART): o player inicia a reprodução sem esperar o segmento completo ficar pronto. São aplicados o recarregamento bloqueante da playlist (o servidor retém a requisição até o próximo part ficar pronto) e a dica de pré-carregamento #EXT-X-PRELOAD-HINT.

A duração-alvo de um part é definida pela configuração de stream Part Target Duration (ms; padrão 500, faixa 100–5000). O valor é aplicado em tempo real, sem reiniciar o fluxo, e deve ser menor que Chunk Min Interval.

HLS / DASH / LL-HLS Adaptive Multistream

HLS Adaptive Multistream é suportado desde a versão 1.10, DASH Adaptive Multistream desde a versão 1.12 (desde a versão 1.13 — sobre CMAF), e Low-Latency HLS Adaptive desde a versão 1.13.

Para os fluxos adaptativos, configura-se uma playlist separada. Para isso:

  • Ativar a entrega OTT nos fluxos que farão parte da playlist adaptativa: OTT/HLS — para HLS adaptativo sobre MPEG-TS; OTT/HLS/LL-HLS/LL-Dash — para DASH / Low-Latency HLS adaptativos sobre CMAF.

  • No menu principal aparecerá uma seção de fluxos adaptativos. Nela é preciso adicionar um fluxo e indicar todos os fluxos que devem entrar nesta playlist.

  • Os fluxos podem ter um parâmetro de bitrate definido. Por padrão, é 0 — o bitrate é obtido do valor medido; caso contrário, pode ser definido explicitamente.

Para playlists adaptativas a URL é diferente:

Aos peers (clientes) pode-se impor restrições de acesso aos fluxos adaptativos, assim como aos comuns. A permissão para um fluxo adaptativo inclui a permissão para todos os fluxos que o compõem.

HTTP/3 (QUIC)

Desde a versão 1.13.1.438, o Perfect Streamer inclui um servidor HTTP/3 integrado para a entrega OTT de HLS, MPEG-DASH e Low-Latency HLS sobre QUIC (RFC 9000 + RFC 9114). A pilha é ngtcp2 (QUIC v1) + nghttp3 (HTTP/3 frame layer); a infraestrutura TLS reutiliza o mesmo certificado que o listener HTTPS.

O QUIC serve apenas rotas OTT:

  • / — redirecionamento raiz,

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

  • /h<sessID>/... — URL por sessão (media playlists, segmentos, VTT),

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

Low-Latency HLS e DASH são entregues sobre QUIC de forma incremental (chunked): as parts são enviadas ao cliente à medida que ficam prontas, sem esperar o segmento completo.

Os caminhos administrativos (/data, /config, /xmltv, /db/, /login, /logout, /restart) devolvem 404 sobre QUIC — a API de administração permanece em HTTPS / HTTP-TCP. Trata-se de uma restrição deliberada para reduzir a superfície de ataque do listener QUIC.

Ativação

As definições de QUIC encontram-se na secção Configuration / HTTP server (nó /config/http-server):

  • HTTP/3 Enable (http3-enable) — indicador global que ativa o listener QUIC. Valor por omissão: off. A ativação abre um socket UDP em http3-port; a desativação fecha-o.

  • HTTP/3 Port (http3-port) — porta UDP do listener QUIC. Valor por omissão: 43984. O QUIC opera sobre UDP e o HTTPS sobre TCP; as portas podem coincidir ou diferir, sem conflito entre si.

  • HTTP/3 0-RTT Enable (http3-zero-rtt-enable) — permite o handshake 0-RTT (RFC 9001 §4.6.1) em ligações retomadas do mesmo cliente. Reduz a latência de arranque; o risco de reprodução deve ser considerado para pedidos não idempotentes (é seguro para cargas OTT de apenas leitura). Valor por omissão: on.

As alterações de configuração são aplicadas a quente, sem reiniciar o serviço.

O certificado e a chave são obtidos do listener HTTPS — não existe configuração de certificado separada para HTTP/3. Se o HTTPS não estiver configurado (ssl-enable=false), a ativação do HTTP/3 não produz efeito — o handshake não será concluído.

O parâmetro ?h3 — opt-in por sessão

Um browser não liga diretamente a um endpoint HTTP/3 — acede primeiro via HTTPS/TCP, recebe na resposta o cabeçalho Alt-Svc: h3=":<port>"; ma=86400 (RFC 7838) e só os pedidos seguintes a esse origin são comutados para QUIC.

No Perfect Streamer, a emissão de Alt-Svc é opt-in por sessão OTT. O servidor não anuncia QUIC a todos os clientes indistintamente: o cliente deve solicitar HTTP/3 explicitamente através do parâmetro de consulta ?h3 no URL master HLS / DASH.

Valor no URL

Valor opt-in

Alt-Svc na resposta

parâmetro ausente

off (default)

não

?h3 (sem valor)

on

sim

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

on

sim

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

explicit off

não (como se ausente)

Depois de o cliente obter o URL de sessão /h<sess>/..., o indicador wantH3 fica armazenado na sessão — todas as atualizações posteriores de media playlist, GET de segmentos e GET de chunks VTT sob esse URL de sessão recebem também Alt-Svc, mesmo que ?h3 esteja ausente do pedido em si. Sem opt-in no master, não se estabelece comportamento sticky.

Filtragem de Alt-Svc:

  1. O listener QUIC está globalmente ativado (http3-enable=true); caso contrário o anúncio é suprimido mesmo com ?h3 definido.

  2. O pedido não chegou por QUIC — em pedidos já servidos sobre H3, Alt-Svc não faz sentido e não é emitido.

  3. Por sessão ou por URL wantH3=true (ver a tabela acima).

  4. Os servidores de administração e EPG nunca emitem Alt-Svc — não possuem listener QUIC.

Este comportamento está em conformidade com a RFC 7838 §3 — o próprio servidor decide em que respostas emite Alt-Svc.

Cenário de comutação para QUIC no browser

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

  1. O leitor abre o URL master com opt-in explícito:

    https://stream.example.com:41982/hls/test1/login/password/index.m3u8?h3=1
    
  2. O primeiro pedido vai por HTTPS/TCP. Na resposta o servidor emite Alt-Svc: h3=":43984"; ma=86400.

  3. O browser memoriza o mapeamento stream.example.com:41982 h3=":43984". No Chrome pode ser visto na página chrome://net-internals/#alt-svc; no Firefox — about:networking#http3.

  4. Nos pedidos seguintes ao mesmo origin (atualização da playlist, GET de segmentos, VTT) o browser abre uma ligação QUIC em udp/43984 e prossegue sobre HTTP/3. Em DevTools → Network a coluna Protocol desses pedidos passa a h3.

Se a ligação QUIC não puder ser estabelecida (porta UDP bloqueada pela firewall, certificado não confiável no sistema), o browser permanece de forma transparente em HTTPS/TCP — funcionalmente o fluxo continua a reproduzir sem interrupção.

Contabilização de clientes e monitorização

O endereço IP real de um cliente QUIC na contabilização de pares ativos (/data/http-clients, limites de ligações concorrentes, ACL por IP) é o endereço de peer real da ligação QUIC, não o loopback da ponte de backend interna.

O atributo ott-type em /data/http-clients é composto, com o formato <PROTO>/<scheme>:

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

  • scheme — o transporte de rede efetivo: http, https ou quic.

Exemplos: HLS/quic, DASH/https, HTTP/http. A interface de administração mostra tanto o protocolo OTT como o transporte de rede de cada cliente numa única coluna.

O tempo-limite de uma sessão OTT é de 60 segundos, independentemente do transporte. Quando o cliente alterna entre transportes, o esquema apenas é atualizado para cima segundo a cadeia de prioridade httphttpsquic. Um retorno paralelo a uma ligação menos segura não sobrescreve o tipo atual — na contabilização permanece o transporte mais seguro observado.

Compatibilidade dos leitores

O HTTP/3 para OTT é suportado por:

  • iOS AVPlayer / Safari — nativamente, através de Alt-Svc; com 0-RTT.

  • Chrome, Firefox, Edge, Brave — através de Alt-Svc; o HLS é reproduzido com hls.js e o DASH com dash.js / Shaka; o tráfego do leitor herda o HTTP/3 da fetch API do browser.

  • Android ExoPlayer — através do motor QUIC Cronet (não é o transporte predefinido; requer configuração no cliente).

VLC (libVLC) não suporta HTTP/3 — nestes clientes o fluxo continua a funcionar sobre HTTPS/TCP, sem o benefício do QUIC.

O benefício real do QUIC face ao HTTPS/TCP é mais notório em redes móveis / Wi-Fi / redes com perdas:

  • ausência de head-of-line blocking ao nível do TCP — uma perda num fluxo HTTP não atrasa os restantes;

  • handshake 0-RTT em ligações retomadas do mesmo cliente;

  • taxa de bits ABR mais estável com perdas de pacotes de 1 a 5 %.

Em redes geridas / Wi-Fi corporativo, o ganho costuma ser modesto — de 5 a 10 % na latência de arranque e praticamente nulo em regime estacionário. A carga de CPU no servidor é maior com QUIC do que com o TCP do kernel — é o preço de uma pilha TLS + transporte em user space.

Modelo de cache para OTT HLS e DASH

O servidor produz respostas em três categorias, distintas pelo tempo de vida do conteúdo e pela adequação ao cache em nós intermediários (reverse proxy, CDN, cache do cliente).

1. Modelo de cache

1.1. Recursos e cabeçalhos 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

não definido; não é cacheado

1.2. Características dos segmentos

O identificador hexadecimal do segmento na URL (<keyHex> nos caminhos /h<sess>/<keyHex>.ts) é calculado como um CRC64 sobre o tempo de início do segmento e o ID do stream, e é globalmente único. A URL do segmento endereça conteúdo imutável — em requisições repetidas para a mesma URL é devolvido um fluxo de bytes idêntico (enquanto o segmento permanecer dentro da janela deslizante).

A diretiva immutable suprime a revalidação condicional pelo cliente (If-None-Match, If-Modified-Since). O valor max-age=60 é compatível com o típico timeShiftBufferDepth=40s.

Os segmentos fMP4 de CMAF (.m4s) e o init.mp4 comum para DASH / Low-Latency HLS são endereçados de forma análoga e armazenados em cache pelo mesmo modelo (immutable, max-age=60). Os segmentos parciais (parts) do LL-HLS são requisições byte-range dentro do mesmo .m4s, portanto não formam uma entrada de cache separada.

1.3. Características dos manifestos

max-age=1 limita o limite superior de obsolescência do conteúdo no cache em um segundo. Em conjunto com proxy_cache_lock on (nginx), picos de requisições ao manifesto coalescem em uma única requisição ao origin por segundo.

1.4. Variabilidade do conteúdo

Com absPath=0 (valor padrão; sem o parâmetro de URL a) os manifestos HLS media e DASH MPD não incluem identificador de sessão no corpo. O conteúdo do manifesto é idêntico entre sessões que pertencem à mesma combinação (stream, param). Isso permite que o cache do reverse-proxy reutilize uma única entrada entre sessões quando a chave de cache está normalizada.

Com absPath=1 (parâmetro de URL a=1) o corpo do manifesto contém URLs absolutas que incluem o esquema, o host e o identificador de sessão. O conteúdo torna-se específico da sessão; a reutilização de cache entre sessões não está disponível.

2. Comportamento dos clientes

Cliente

URL de atualização do manifesto

Impacto no número de sessões

VLC 3.x HLS

/h<sess>/index.m3u8

Uma sessão por reprodução

VLC 3.x DASH

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

Tratado por session reuse (ver 3.3)

ffmpeg 5.x HLS

/h<sess>/index.m3u8

Uma sessão por reprodução

ffmpeg 5.x DASH

/dash/<stream>/.../index.mpd (laço de repetição)

Tratado por session reuse (ver 3.3)

dash.js, hls.js

/h<sess>/... via <Location> / URL de sessão

Uma sessão por reprodução

3. Mecanismos especializados

3.1. Redirect HTTP 302 para DASH

Uma requisição da forma /dash/<stream>/<login>/<pass>/index.mpd retorna a resposta 302 Found com o cabeçalho Location: /h<sess>/index.mpd. O corpo da resposta é vazio. A autenticação e a alocação da sessão acontecem na fase de processamento do redirecionamento.

Clientes que suportam cache de redirecionamento acessam diretamente a URL da sessão nas requisições subsequentes. Clientes que não suportam repetem a requisição de redirecionamento. O custo do reprocessamento do redirecionamento limita-se à verificação de autenticação e às operações de session reuse.

3.2. Session reuse para DASH

Ao processar uma requisição /dash/.../index.mpd do mesmo login para o mesmo stream (com o mesmo indicador adaptive), o servidor encontra uma sessão DASH já existente e devolve novamente o identificador dela. Nenhuma nova sessão é criada; nenhum slot do limite de conexões simultâneas é consumido.

Aplica-se apenas ao DASH. Para HLS não é necessário um mecanismo de reuse separado: os clientes HLS atualizam a media playlist via URL de sessão e não criam uma nova sessão a cada refresh.

3.3. Reutilização de segmentos entre sessões

O caminho /h<sess>/<keyHex>.ts não depende de <sess> ao resolver <keyHex> em conteúdo: <keyHex> identifica de forma globalmente única um segmento TS dentro de um stream. Nginx com uma chave de cache normalizada (que remove o prefixo /h<sess>/) atende cada requisição para o mesmo <keyHex> a partir de uma única entrada de cache, independentemente de quais clientes a emitiram.

O mesmo vale para os segmentos fMP4 de CMAF (.m4s) e init.mp4: seu conteúdo é imutável e endereçado dentro do stream, de modo que a normalização da cache key produz a mesma deduplicação entre sessões no cache.

4. Parâmetros da requisição

Parâmetro

Valor padrão

Impacto

a

0

1 — URLs absolutas nos manifestos; 0 — relativas

s

40

timeShiftBufferDepth em segundos

m

40

Comprimento mínimo da janela para emitir o manifesto

v

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

#EXT-X-VERSION em HLS (ignorado pelo DASH); um valor explícito na URL sobrepõe-se ao valor padrão

h3

ausente (off)

opt-in para HTTP/3 (QUIC) — faz com que o servidor emita Alt-Svc na resposta. Valores reconhecidos: presence ⇒ on, 1/on/yes/true ⇒ on, 0/off/no/false ⇒ explicit off. Sticky no URL de sessão /h<sess>/.... Ver a secção HTTP/3 (QUIC).

A alteração de um parâmetro via query string atualiza os valores armazenados na sessão na sua próxima reabertura.

5. Características de carga

A carga no origin escala com o número de streams distintos assistidos simultaneamente. O aumento do número de clientes assistindo o mesmo stream não aumenta o número de requisições ao origin quando há reverse-proxy cache com chave de cache normalizada.

Cenário

Taxa de requisições ao origin (ref.)

1 cliente por fluxo X

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

N clientes em um mesmo fluxo X (cache ativo)

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

N clientes ffmpeg em modo replay no mesmo fluxo

MPD: 1 req/s (com proxy_cache_lock)

N clientes em N fluxos distintos

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

6. Nginx como reverse proxy com cache

6.1. Configuração 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. Função das diretivas

Diretiva

Finalidade

proxy_cache_lock on

Serializa as requisições upstream em cache misses simultâneos para a mesma chave

proxy_cache_use_stale updating

Devolve a cópia obsoleta às requisições paralelas durante a atualização do cache

proxy_cache_revalidate on

Usa If-Modified-Since em cache miss com cópia salva

proxy_cache_valid 404 403 0s

Proíbe o cache de erros de autorização e 404

keepalive 64 no upstream

Mantém um pool de conexões persistentes ao origin

proxy_buffering on

Para segmentos; ativa o bufferização da resposta no nginx

proxy_buffering off

Para a seção /; desativa o bufferização (raw streaming)

6.3. Cálculo de max_size do cache de segmentos

Valor de referência: bitrate × timeShiftBufferDepth × distinct_streams × 2

Exemplo: 10 fluxos × 8 Mbps × 40 s × 2 ≈ 800 MB. Recomenda-se uma margem de 10× para absorver a variabilidade do bitrate.

6.4. Terminação TLS

O servidor do Perfect Streamer aceita conexões nas portas HTTP e HTTPS. Com terminação TLS no nginx, o upstream usa a porta HTTP. O encaminhamento dos cabeçalhos X-Forwarded-Proto e X-Forwarded-Host é obrigatório para a composição correta de URLs absolutas quando 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 e origin aplicam-se proxy_ssl_verify e proxy_ssl_trusted_certificate. Em conexões loopback a criptografia é redundante.

6.5. Multi-host

Quando um único processo nginx atende vários server_name, $host é adicionado à cache key para isolar o conteúdo:

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

O tamanho de keys_zone é dimensionado em 8000 chaves/MB. Para instalações multi-host com milhares de fluxos, recomenda-se keys_zone=...:300m ou mais.

7. Cache do cliente

Cache-Control: immutable é processado pelos navegadores Chrome/Firefox/Safari. O cache do cliente devolve o segmento sem requisição condicional em acessos repetidos (incluindo seek para trás dentro do buffer do player).

Service Workers podem aplicar a estratégia cache-first baseada no conteúdo de Cache-Control. Os players DASH (dash.js, Shaka) usam MSE através de SourceBuffer; o segmento colocado no buffer permanece disponível sem nova requisição HTTP até sair da janela deslizante.

Para requisições cross-domain, o cabeçalho Access-Control-Allow-Origin: * permite o cache em shared caches sem Vary: Origin. Ao trocar o valor ACAO para um Origin específico passa a ser necessário Vary: Origin, o que reduz a eficiência do shared cache.

8. Distribuição via CDN

Perfect Streamer é compatível com CDNs em modo pull-from-origin (Cloudflare, Akamai, Fastly, BunnyCDN, Amazon CloudFront).

Origin shield. Recomenda-se colocar um ou mais nós shield entre o edge do CDN e o origin para reduzir a taxa de requisições ao origin quando os clientes estão distribuídos globalmente.

Purge. Segmentos content-addressed não precisam de purge. Quando os metadados do stream mudam (codec, resolução), os manifestos atualizam dentro de max-age=1 sem purge explícito.

Cache warming. Em caso de pico esperado em um stream específico, é admissível pré-aquecer a CDN a partir de vários pontos geográficos antes do início da transmissão.

Geodistribuição. Os segmentos (max-age=60) são bem adequados para cache geograficamente distribuído. Os manifestos (max-age=1) toleram atraso de entrega de até um segundo — aceitável para live non-low-latency.

9. Monitoramento

9.1. X-Cache-Status

Adicionar add_header X-Cache-Status $upstream_cache_status; em cada location com cache. Valores:

Valor

Descrição

HIT

Resposta do cache

MISS

Não estava em cache; obtido do origin e armazenado

EXPIRED

Expirado, atualizado

UPDATING

Cópia stale entregue à requisição paralela durante a atualização

STALE

use_stale retornou a cópia expirada (origin indisponível)

REVALIDATED

Origin retornou 304 Not Modified

BYPASS

proxy_cache_bypass foi acionado

9.2. Formato do 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

O módulo nginx-vts exporta métricas por zona no formato Prometheus:

GET /status/format/prometheus

Limites recomendados para alertas:

Métrica

Limite

Causa possível

Segment HIT rate

< 90 % em 5 minutos

Normalização de cache key quebrada; max_size pequeno

Manifest MISS rate

> 50 % em 1 minuto

proxy_cache_lock não serializa as requisições

Upstream response time p95

> 500 ms em 1 minuto

Sobrecarga do origin

Cache zone fill

> 90 % em 10 minutos

Aproximação de max_size; eviction LRU prevista

10. Diagnóstico

Sintoma

Causa provável

Solução

Taxa HIT de segmento baixa

Vary: Origin com alta variabilidade do Origin; normalização quebrada em map

Verificar cabeçalhos e regex na diretiva map

404 em segmentos após sair da janela

404 em cache para segmento que saiu da janela deslizante

Adicionar proxy_cache_valid 404 0s na location de segments

Atraso do início da reprodução 2–5 s

proxy_cache_lock_timeout excede a latência alvo

Reduzir para 1–2 s; ativar proxy_cache_use_stale updating

O manifesto não atualiza

proxy_cache_valid sobrescreve max-age

Definir explicitamente proxy_cache_valid 200 1s

Crescimento de TIME_WAIT no upstream

Falta keepalive no bloco upstream

Adicionar keepalive 64, proxy_http_version 1.1, proxy_set_header Connection ""

403 em /dash/.../<segment>.m4s do ffmpeg

O cliente resolve URLs relativas a partir da URL pré-redirect

O servidor emite <BaseURL>/h<sess>/</BaseURL> (caminho absoluto); compatível no build atual

Lags, rebuffering frequente em clientes remotos

Throughput TCP efetivo baixo devido a slow start e idle restart com RTTs grandes (300 ms e superiores)

Ajuste da pilha de rede do Linux no origin: ver 10.1

10.1. Ajuste TCP do origin para clientes high-RTT

O problema se manifesta em clientes com RTT grande até o origin (por exemplo 300 ms ou mais) quando o bitrate do stream está próximo da capacidade do canal. Sintomas no reprodutor (VLC, ffmpeg, dash.js) — rebuffering frequente, warnings do tipo ES_OUT_SET_PCR called too late (aumento de pts_delay), buffer deadlock prevented, interrupções do stream. No servidor, enquanto isso, o cliente parece normal, não há erros, e o throughput em /data/stream/... corresponde ao stream de entrada.

Causa:

  • TCP slow start. Cada nova conexão TCP começa com um congestion window de cerca de 14 KB e o aumenta ao longo de vários RTT. Com um RTT de 300 ms, atingir a janela completa leva 2-3 segundos. Durante esse tempo, um segmento HLS/DASH com duração de 5 s (4-6 MB) é baixado visivelmente mais devagar do que em tempo real.

  • TCP idle restart. Entre solicitações de segmentos, um cliente HLS em pull-model faz uma pausa de 4-5 s. Por padrão, após tal pausa o kernel do Linux redefine o congestion window da conexão para o initial cwnd (comportamento net.ipv4.tcp_slow_start_after_idle=1). Como resultado, no próximo GET a conexão keep-alive retoma a transmissão a partir do slow start — mesmo em uma sessão já aquecida.

Um agravante adicional — o congestion control CUBIC padrão lida mal com RTTs longos e perdas de pacotes em trechos intermediários da rede.

Solução — dois parâmetros sysctl no 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 aplicação 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

O principal efeito vem do primeiro parâmetro (tcp_slow_start_after_idle=0). Ele elimina diretamente o slow start repetido entre solicitações de segmentos dentro de uma mesma conexão keep-alive. O segundo (BBR) oferece robustez adicional e aplica-se a todas as novas conexões.

O ajuste não requer reinicialização do Perfect Streamer e aplica-se a todas as novas conexões TCP imediatamente após sysctl -w. As conexões existentes conservam o congestion control com o qual foram estabelecidas.

11. Segurança

11.1. Session URL

A URL no formato /h<sess>/... cumpre a função de token de sessão — não exige reautenticação. O tempo de vida é limitado pelo idle timeout (valor 30 s). Em caso de inatividade, a sessão é removida pela tarefa cleaner.

Requisitos:

  • HTTPS em todos os caminhos OTT (/hls/, /dash/, /h<sess>/) em produção

  • O Session ID no cabeçalho Location do 302 não é cacheado (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;
    }
}

URLs de sessão (/h<sess>/) não exigem rate limiting — o processamento é barato e as respostas são cacheadas.

11.3. Cache de respostas com erros

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

Proíbe o cache de redirecionamentos (sess único em Location) e de respostas de erro de autorização ou recurso ausente.

11.4. Restrição do acesso de rede ao origin

A porta 41972 (41982 para HTTPS) deve estar fechada ao tráfego externo. Configurações aceitáveis:

  1. Bind do Perfect Streamer em 127.0.0.1 (com nginx local)

  2. Regra de firewall:

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

12. Integração com middleware

12.1. Modelo prefix-login

O Perfect Streamer permite delegar a identificação de usuário a middleware/sistema de billing via o mecanismo prefix-login. Um conector externo ao sistema de billing não está incluído nesta versão.

Configuração do usuário embedded:

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

Com is-prefix: true o servidor aceita URLs cujo login segue <prefix><billing_user_id>:

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

12.2. Formato das estatí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>

O campo login-id contém o hash do login URL. login é o valor configurado. match-login é o login URL usado pelo cliente.

12.3. Limitações do prefix-login

  • Senha compartilhada. Todos os assinantes do pool prefix usam um único valor de senha. O comprometimento da senha concede acesso a qualquer <prefix><string>.

  • Granularidade de ACL. accept-stream aplica-se a todo o pool prefix; não há ACL por assinante sem billing externo.

  • Rotação da senha. A alteração da senha desconecta todos os assinantes ativos. Uma substituição gradual requer o uso temporário de dois prefix-logins.

13. Legendas WebVTT

A fonte das legendas é DVB Teletext / DVB Subtitling do MPEG-TS de entrada. As faixas de legendas Teletext devem estar presentes nas seções Media Information ou Original Media Information. A seção Analyzer também permite verificar se os pacotes dos PIDs correspondentes estão ativos.

Para OTT HLS/DASH o modo OTT deve estar ativado (em Peering/HLS as legendas WebVTT não estão disponíveis). Na seção Output # OTT o contador de chunks OTT WebVTT buffer chunk count deve ficar diferente de zero.

Para diagnosticar as legendas, ativar Analyze e Trace no stream. Ao iniciar o fluxo, o log do stream deve exibir:

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

Em seguida, o log registra o texto decodificado das legendas.

13.1. URL dos segmentos VTT

Esquema

URL

Conteúdo

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 com #EXTINF

Segmento VTT HLS

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

VTT com X-TIMESTAMP-MAP estilo HLS

DASH MPD AdaptationSet

em index.mpd

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

Segmento VTT DASH

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

VTT com X-TIMESTAMP-MAP estilo DASH

<keyHex> é um CRC64 hex de 16 caracteres calculado a partir do tempo de início do segmento, do ID do stream e do PID da faixa de legendas. <seq> é o número decimal sequencial de um chunk do stream de legendas (a numeração das legendas não está relacionada com a dos chunks TS).