Serviço OTT

Emite fluxos por protocolos baseados em HTTP — HLS, MPEG-DASH (desde a versão 1.12) e MPEG-TS over HTTP. Suporta HTTPS (SSL). A emissão é 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.

Analogamente para HLS e DASH:

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 estão disponíveis parâmetros adicionais na URL (opcionais):

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

  • a: 1 — caminhos absolutos na playlist (padrão), 0 — caminhos relativos.

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

  • m: duração mínima do playlist dinâmico (s), por padrão 40 s. O tamanho máximo do playlist dinâmico é 60 s. Se o tamanho atual do buffer de chunks for menor que o mínimo solicitado, será retornado erro 404. Isso é feito para que o HLS inicie a partir de um buffer de chunks já preenchido no servidor.

  • v: versão do protocolo HLS na playlist. Por padrão 5; alguns clientes HLS podem exigir uma versão diferente.

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 servidor HLS tem dois modos — Peer mode e OTT mode.

Peer mode — modo com segmentação (chunks) simples. Recomendado para peering / distribuição de fluxos.

OTT mode — modo com segmentação otimizada para transmissão OTT e início rápido dos players. A carga de CPU é maior; recomendado para transmissão.

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

Chunk Min Interval e Chunk Max Interval

No modo OTT é feita análise do fluxo em PAT/PMT/SPS/PPS/IFrame e os chunks são cortados pelo critério de início rápido dos players. A análise começa em min interval e, se por alguma razão os dados não forem encontrados, o chunk é forçosamente cortado em max interval.

HLS Adaptive Multistream

A partir da versão 1.10 há suporte a HLS Adaptive Multistream e, da 1.12, a DASH Adaptive Multistream

Para fluxos adaptativos é configurada uma playlist HLS separada. Para isso:

  • Nos fluxos que serão incluídos na playlist adaptativa, ative o HLS com OTT Mode.

  • No menu principal aparecerá a seção de fluxos adaptativos. Nela adicione um fluxo e indique todos os fluxos que devem entrar nessa playlist.

  • Para os fluxos pode ser definido um parâmetro de bitrate. Por padrão 0 significa usar o 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.

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

/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

não definido; não é cacheado

1.2. Características dos segmentos TS

O identificador keyID é formado como CRC64(startTime || streamID) e é globalmente único. A URL do segmento endereça conteúdo imutável — em requisições repetidas da mesma URL é retornado 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.

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 (padrão; sem o parâmetro de URL «a»), os manifestos HLS media e DASH MPD não contêm o identificador de sessão no corpo. O conteúdo do manifesto é idêntico entre sessões pertencentes a uma mesma combinação (stream, param). Isso permite que o cache do reverse-proxy reutilize a entrada entre sessões com a normalização da chave de cache.

Com absPath=1 (parâmetro de URL «a=1»), o corpo do manifesto contém URLs absolutas, incluindo esquema, host e identificador da sessão. O conteúdo torna-se específico da sessão; a possibilidade de reutilização do cache entre sessões deixa de existir.

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

No processamento da requisição /dash/.../index.mpd, executada sob login-id L para stream-id S e a flag adaptive=A, se em _ottClientList já existir uma sessão DASH com o mesmo (L, S, A), seu sessID é retornado. Não é criada nova sessão e nenhum slot maxConn é consumido.

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

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

O caminho /h<sess>/<keyID>.ts é independente de sess na resolução do keyID em conteúdo: keyID identifica univocamente o segmento dentro das ChunkList registradas (ver _ottStreamList). O Nginx com cache key normalizada (removendo o prefixo /h<sess>/) atende todas as requisições do mesmo keyID a partir de uma única entrada de 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

3

#EXT-X-VERSION em HLS (ignorado pelo DASH)

Alterar o parâmetro via query string atualiza os valores armazenados na sessão na próxima chamada de applyNewOTTSess.

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

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;

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

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 subscribers do pool prefix usam uma mesma senha. O comprometimento dá 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. Alterá-la desconecta todos os subscribers ativos. Para substituição gradual são necessários temporariamente 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 Peer mode 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> é o hex de 16 caracteres de CRC64(startTime, streamID, pid). <seq> é o número decimal do chunk no subtitle storage (contador separado do video storage).