---
title: EPG para middleware OTT
url: https://doc.pstreamer.tv/pt/manual/web_server_epg.html
lang: pt-BR
product: Perfect Streamer
version: 1.13.3.445
---

# 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 |
| --- | --- | --- | --- | --- |
| `src` | inteiro sem sinal | não | `0` | Fonte EPG. `0` — dados importados do EIT MPEG-TS dos fluxos de entrada. `1, 2, …` — identificador de entrada em `/config/epg/epg-source` (fonte XMLTV externa). |
| `ch` | cadeia | **sim** | — | Identificador do canal na base EPG: valor do campo `channel_id` da tabela `channel`. A lista de identificadores disponíveis pode ser obtida por uma consulta SQL via `POST /data/epg/sql`. |
| `lang` | inteiro ou ISO 639 | não | idioma padrão do sistema | `0` — idioma padrão do sistema; um inteiro > 0 — identificador interno de idioma (veja `GET /schema/lang`); uma cadeia — código ISO 639 de duas ou três letras, p. ex. `eng`, `rus`, `fra`. |
| `t` | 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 `t` pertence: intervalo `[t / 86400 · 86400, (t / 86400 + 1) · 86400)`. Para obter dados do «dia seguinte», basta somar `86400` ao horário atual. |

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

```json
{
  "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 `start` sã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 `start` por 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 OK` com 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 via `POST /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 |
| --- | --- |
| `200 OK` | Solicitação processada. O corpo é um JSON com a lista de eventos (possivelmente vazia). |
| `400 Bad Request` | O parâmetro `ch` está ausente ou vazio; ou `src`, `t` não são analisáveis como inteiro sem sinal; ou um `lang` numérico aponta para um identificador de idioma inexistente. |
| `401 Unauthorized` | 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:

```json
{"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

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

```json
{
  "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:

```nginx
# /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 `BUSY` sob carga.
- **``keys_zone`` e ``max_size``** são dimensionados pelo número de `(canal × dia × idioma)`: 32 MB de `keys_zone` cobrem centenas de milhares de chaves; 2 GB de `max_size` cobrem 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`/`EXPIRED` e 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 conta `viewer` dedicada e adicione autenticação Digest em `proxy_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.

## Endpoints relacionados

- `POST /data/epg/sql?s=<src_id>` — consulta SQL arbitrária ao banco EPG (em especial, para obter a lista de `channel_id`).
- `POST /data/epg/update?s=<src_id>` — atualização forçada de uma fonte XMLTV externa.

A lista completa e a descrição detalhada da API HTTP estão em `manual/http_data_api.txt`.
