EPG para middleware OTT¶
O servidor fornece os dados do guia de programação (EPG) para o dia selecionado e para um único canal no formato JSON. O endpoint é destinado aos back-ends de middleware OTT que agregam a programação do Perfect Streamer para montar o guia no cliente final.
URL e autenticação¶
O endpoint é servido pelo servidor admin integrado. Por padrão está disponível nas portas 43971 (HTTP) e 43981 (HTTPS); as portas são configuradas em Settings/Server Settings.
GET /data/epg/channel?src=<src_id>&ch=<channel_id>&lang=<lang>&t=<time>
A autenticação é HTTP Digest, como para o restante de /data/*. Para o middleware, basta uma conta com o papel viewer (somente leitura).
Nota
Solicitações do endereço loopback (127.0.0.1) são executadas sem verificação HTTP Digest — o servidor as trata como anônimas. É conveniente para scripts locais e health-checks de middleware implantado na mesma máquina que o Perfect Streamer; para acessos remotos as credenciais são obrigatórias.
Parâmetros da solicitação¶
Parâmetro |
Tipo |
Obrigatório |
Padrão |
Descrição |
|---|---|---|---|---|
|
inteiro sem sinal |
não |
|
Fonte EPG. |
|
cadeia |
sim |
— |
Identificador do canal na base EPG: valor do campo |
|
inteiro ou ISO 639 |
não |
idioma padrão do sistema |
|
|
inteiro sem sinal (Unix epoch, s) |
não |
hora atual do servidor |
Qualquer ponto dentro do dia de interesse. O servidor retorna eventos do dia UTC ao qual |
Nota
O dia é tomado em UTC, não no fuso local. Se o middleware monta a programação pelo dia calendário local, o limite do dia UTC pode não coincidir com a meia-noite local; nesse caso é preciso fazer duas solicitações (para dias UTC adjacentes) e juntar os resultados pelo campo start.
Formato da resposta¶
Content-Type:application/json.Os cabeçalhos HTTP proíbem o cache no lado do cliente e em proxies intermediários:
Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0
O corpo da resposta é um JSON com a lista de eventos do dia:
{
"event": [
{"start": 1715000000, "end": 1715003400, "title": "Morning News", "desc": "Overview of the day's events"},
{"start": 1715003400, "end": 1715007000, "title": "Weather Forecast", "desc": ""}
]
}
Campos do evento:
start,end— início e fim do programa em Unix epoch (s), UTC.title— título do programa no idioma escolhido. Se o título no idioma solicitado não existir para o evento, o servidor recorre à entrada no idioma padrão do sistema e, depois, em qualquer outro disponível.desc— descrição estendida. Pode ser uma cadeia vazia se a base não tiver descrição separada para o evento.
Particularidades da saída:
Eventos com título vazio e sem descrição, assim como aqueles com timestamps inválidos, são excluídos da saída.
Os duplicados por
startsão descartados: para o mesmo momento de início é retornada uma única entrada com a melhor prioridade de idioma (solicitado → padrão do sistema → demais).A ordem dos eventos no array não é garantida — se necessário, o middleware ordena a lista pelo campo
startpor conta própria.Se o canal não for encontrado, se a fonte não existir ou se não houver eventos para o dia escolhido, o servidor retorna
200 OKcom o array vazio{"event":[]}ou, no caso raro de fonte ausente, com corpo vazio. O middleware deve tratar corretamente as duas variantes.
Cache no servidor¶
O JSON pronto é armazenado em cache pelo servidor sob a chave (channel_id, data UTC, idioma), de modo que solicitações repetidas para o mesmo dia e canal são atendidas sem acessar o banco de dados. O middleware não precisa gerenciar o cache.
O cache é limpo automaticamente:
quando chegam novos eventos do EIT MPEG-TS dos fluxos de entrada (
src=0);ao atualizar com sucesso uma fonte XMLTV externa (
src > 0— agendada ou forçada viaPOST /data/epg/update?s=<src_id>);quando um dia sai da janela de retenção do EPG.
Um endpoint dedicado para a limpeza forçada do cache não está previsto nem é necessário.
Códigos de resposta HTTP¶
Código |
Condição |
|---|---|
|
Solicitação processada. O corpo é um JSON com a lista de eventos (possivelmente vazia). |
|
O parâmetro |
|
A autenticação HTTP Digest está ausente ou incorreta (para solicitações que não vêm de um endereço loopback). |
Outras situações (src desconhecido, canal ausente, sem eventos no dia) não geram 4xx — o middleware recebe 200 OK com o array vazio {"event":[]}.
Em caso de erro, o corpo da resposta é um envelope JSON de formato fixo:
{"status": 400, "message": "Bad Request"}
O campo status duplica o código HTTP; o campo message traz a causa refinada do erro em inglês (ou o texto padrão do status HTTP, se não houver informação adicional). O Content-Type da resposta de erro é application/json.
Exemplo¶
curl -u middleware:secret --digest \
'http://pss.example.com:43971/data/epg/channel?src=0&ch=12.0.1&lang=rus&t=1715000000'
Exemplo de resposta:
{
"event": [
{"start": 1715000000, "end": 1715003400, "title": "Morning News", "desc": "Overview of the day's events"},
{"start": 1715003400, "end": 1715007000, "title": "Weather Forecast", "desc": ""}
]
}
Desempenho e escalabilidade¶
Cache do servidor Perfect Streamer¶
Dentro do processo PSS há um cache LRU de respostas em memória com chave (channel_id, data UTC, idioma) e teto rígido de 1024 entradas por fonte EPG. Sob carga típica (dezenas a centenas de canais × 1–3 idiomas × keep-day dias) todas as entradas atuais cabem inteiras no cache; solicitações repetidas são atendidas sem acessar o SQLite.
Ordem de grandeza (build de depuração, loopback local, sem HTTPS):
Cenário |
Latência (solicitação única) |
Vazão (P=8) |
|---|---|---|
Cache hit |
~0,3 ms |
~1100 solicitações/s |
Cache miss (SQL + JSON) |
~1,0–1,5 ms |
~1000 solicitações/s |
Em um build de release e sem logging de depuração, os números ficam cerca de 2–3× melhores. Bandwidth — cerca de 14 KB por resposta para um canal-dia típico.
Quando é preciso um reverse-proxy externo (nginx)¶
O cache do servidor acelera solicitações repetidas para o mesmo (canal, dia, idioma), mas cada solicitação ainda passa pelo servidor HTTP integrado do PSS e consome um thread do seu pool. Com muitos clientes faz sentido mover o cache para o edge:
até ~1.000 clientes online — o cache interno costuma ser suficiente, reverse-proxy não é obrigatório.
dezenas de milhares ou mais — recomenda-se um reverse-proxy com cache (por exemplo, nginx). Um cache edge atende 99 % das solicitações sem o PSS, amortiza picos (start do middleware, refresh em massa do player) e permite mover a terminação SSL para um nó separado.
entrega geograficamente distribuída — um CDN/proxy externo é necessário antes mesmo de contar os clientes.
O PSS envia o próprio cabeçalho Cache-Control: no-cache, no-store, must-revalidate para que os clientes finais não armazenem o EPG por muito tempo. Já o reverse-proxy pode (e deve) cachear a resposta sozinho — abaixo mostra-se como informar explicitamente ao nginx que ignore o Cache-Control do upstream e mantenha o próprio TTL.
Exemplo de configuração nginx¶
Configuração mínima para um cache edge de EPG dimensionada para dezenas de milhares de clientes com intervalo de polling de 1–5 minutos:
# /etc/nginx/conf.d/pss-epg.conf
proxy_cache_path /var/cache/nginx/pss-epg
levels=1:2
keys_zone=pss_epg:32m
max_size=2g
inactive=30m
use_temp_path=off;
upstream pss_admin {
server 127.0.0.1:43971;
keepalive 64;
}
server {
listen 80;
# listen 443 ssl http2; # recommended: SSL termination here
server_name epg.example.com;
# gzip helps: typical EPG JSON compresses ~5–8x.
gzip on;
gzip_types application/json;
gzip_min_length 512;
gzip_proxied any;
location = /data/epg/channel {
proxy_pass http://pss_admin;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# PSS returns no-cache; force-cache it at the edge.
proxy_ignore_headers Cache-Control Expires Set-Cookie;
proxy_hide_header Cache-Control;
proxy_hide_header Pragma;
proxy_hide_header Expires;
# Cache key = full URL with query string. The src/ch/lang/t
# parameters already determine response uniqueness.
proxy_cache_key "$scheme$host$request_uri";
proxy_cache pss_epg;
proxy_cache_valid 200 60s; # staleness TTL
proxy_cache_valid 400 404 10s;
proxy_cache_lock on; # coalesce cache misses
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503;
# Serve the JSON to the client with its own TTL
# (the player won't recheck EPG before that).
add_header Cache-Control "public, max-age=60";
add_header X-Cache-Status $upstream_cache_status always;
}
}
Explicações e recomendações¶
TTL ``proxy_cache_valid 200 60s`` — compromisso entre frescura do EPG e carga no upstream. A programação não muda em tempo real, então 30–300 segundos são razoáveis. Após importar novos eventos, o PSS limpa o próprio cache imediatamente, e o cache edge alcança no próximo TTL.
``proxy_cache_lock on`` é obrigatório com muitos clientes: em um cache miss, ele coalesce solicitações paralelas para a mesma chave em uma única solicitação upstream, protegendo o SQLite de picos de
BUSYsob carga.``keys_zone`` e ``max_size`` são dimensionados pelo número de
(canal × dia × idioma): 32 MB dekeys_zonecobrem centenas de milhares de chaves; 2 GB demax_sizecobrem um mês de histórico para centenas de canais com folga.gzip reduz significativamente o tráfego: as respostas comprimem bem (chaves JSON repetidas, cirílico em UTF-8).
``X-Cache-Status`` na resposta permite que o middleware veja
HIT/MISS/EXPIREDe avalie a eficácia do cache.Se nginx e PSS coabitam na mesma máquina, o servidor admin não exige HTTP Digest para loopback, portanto o bloco upstream pode ficar sem
proxy_set_header Authorization …. Para distribuir por redes diferentes, crie uma contaviewerdedicada e adicione autenticação Digest emproxy_pass.HTTPS é melhor terminado no nginx: o PSS suporta HTTPS diretamente, mas um servidor edge costuma ser mais eficiente para lidar com handshakes TLS de milhares de clientes simultâneos.