---
title: Serviço OTT
url: https://doc.pstreamer.tv/pt/manual/ott.html
lang: pt-BR
product: Perfect Streamer
version: 1.13.3.445
---

# 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:

- [http://host:port/http/stream/login/password](http://host:port/http/stream/login/password) — autorização por login e senha
- [http://host:port/http/stream/login](http://host:port/http/stream/login) — autorização por login (token)
- [http://host:port/http/stream/](http://host:port/http/stream/) — autorização por IP

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

**stream** — **ID** 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):

- [http://host:port/hls/stream/login/password](http://host:port/hls/stream/login/password)
- [http://host:port/hls/stream/login](http://host:port/hls/stream/login)
- [http://host:port/hls/stream/](http://host:port/hls/stream/)
- [http://host:port/dash/stream/login/password](http://host:port/dash/stream/login/password)
- [http://host:port/dash/stream/login](http://host:port/dash/stream/login)
- [http://host:port/dash/stream/](http://host:port/dash/stream/)
- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

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-Dash* — *6*, *Peering/HLS* — *3*. 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](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:

- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

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:

- [http://host:port/hls/adaptive/stream/login/password](http://host:port/hls/adaptive/stream/login/password)
- [http://host:port/hls/adaptive/stream/login](http://host:port/hls/adaptive/stream/login)
- [http://host:port/hls/adaptive/stream/](http://host:port/hls/adaptive/stream/)
- [http://host:port/dash/adaptive/stream/login/password](http://host:port/dash/adaptive/stream/login/password)
- [http://host:port/dash/adaptive/stream/login](http://host:port/dash/adaptive/stream/login)
- [http://host:port/dash/adaptive/stream/](http://host:port/dash/adaptive/stream/)
- [http://host:port/llhls/adaptive/stream/login/password](http://host:port/llhls/adaptive/stream/login/password)
- [http://host:port/llhls/adaptive/stream/login](http://host:port/llhls/adaptive/stream/login)
- [http://host:port/llhls/adaptive/stream/](http://host:port/llhls/adaptive/stream/)

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 `http` → `https` → `quic`. 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

```nginx
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`.

```nginx
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:

```nginx
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

```nginx
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:

```bash
# 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:

```bash
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

```nginx
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

```nginx
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).
