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

# EPG para middleware OTT

El servidor entrega los datos de la guía electrónica de programación (EPG) para el día elegido y un único canal en formato JSON. El endpoint está pensado para back-ends de middleware OTT que agregan la programación desde Perfect Streamer para construir la guía del cliente final.

## URL y autenticación

El endpoint lo atiende el servidor admin integrado. Por defecto está disponible en los puertos `43971` (HTTP) y `43981` (HTTPS); los puertos se configuran en **Settings/Server Settings**.

```
GET /data/epg/channel?src=<src_id>&ch=<channel_id>&lang=<lang>&t=<time>
```

La autenticación es HTTP Digest, igual que para el resto de `/data/*`. Para el middleware basta con una cuenta con rol `viewer` (solo lectura).

> **Nota**
>
> Las solicitudes desde la dirección loopback (`127.0.0.1`) se ejecutan sin verificar HTTP Digest — el servidor las trata como anónimas. Es útil para scripts locales y comprobaciones de salud del middleware desplegado en la misma máquina que Perfect Streamer; para accesos remotos las credenciales son obligatorias.

## Parámetros de la solicitud

| Parámetro | Tipo | Obligatorio | Por defecto | Descripción |
| --- | --- | --- | --- | --- |
| `src` | entero sin signo | no | `0` | Fuente EPG. `0` — datos importados desde el EIT MPEG-TS de los flujos de entrada. `1, 2, …` — identificador de entrada en `/config/epg/epg-source` (fuente XMLTV externa). |
| `ch` | cadena | **sí** | — | Identificador del canal en la base EPG: valor del campo `channel_id` de la tabla `channel`. La lista de identificadores disponibles puede obtenerse mediante una consulta SQL por `POST /data/epg/sql`. |
| `lang` | entero o ISO 639 | no | idioma por defecto del sistema | `0` — idioma predeterminado del sistema; un entero > 0 — identificador interno de idioma (véase `GET /schema/lang`); una cadena — código ISO 639 de dos o tres letras, p. ej. `eng`, `rus`, `fra`. |
| `t` | entero sin signo (época Unix, s) | no | hora actual del servidor | Cualquier instante dentro del día de interés. El servidor devuelve los eventos del **día UTC** al que pertenece `t`: intervalo `[t / 86400 · 86400, (t / 86400 + 1) · 86400)`. Para los datos del «día siguiente» basta con sumar `86400` a la hora actual. |

> **Nota**
>
> El día se toma en UTC, no en la zona horaria local. Si el middleware arma la programación por el día natural local, la frontera del día UTC puede no coincidir con la medianoche local; en tal caso hay que hacer dos solicitudes (para días UTC adyacentes) y unir los resultados por el campo `start`.

## Formato de la respuesta

- `Content-Type`: `application/json`.
- Los encabezados HTTP prohíben el almacenamiento en caché en el cliente y en proxies intermedios:

  ```
  Cache-Control: no-cache, no-store, must-revalidate
  Pragma: no-cache
  Expires: 0
  ```
- El cuerpo de la respuesta es un JSON con la lista de eventos del día:

```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 del evento:

- `start`, `end` — inicio y fin del programa en época Unix (s), UTC.
- `title` — título del programa en el idioma elegido. Si no existe el título en el idioma solicitado para el evento, el servidor toma la entrada en el idioma predeterminado del sistema y, después, en cualquier otro disponible.
- `desc` — descripción ampliada. Puede ser una cadena vacía si la base no tenía una descripción aparte para el evento.

Particularidades de la salida:

- Los eventos con título vacío y sin descripción, así como aquellos con marcas de tiempo incorrectas, quedan excluidos de la salida.
- Los duplicados por `start` se descartan: para un mismo momento de inicio se devuelve una sola entrada con la mejor prioridad de idioma (solicitado → predeterminado del sistema → resto).
- El orden de los eventos en el array no está garantizado — si es necesario, el middleware ordena la lista por el campo `start` por su cuenta.
- Si el canal no se encuentra, la fuente no existe o no hay eventos para el día elegido, el servidor devuelve `200 OK` con el array vacío `{"event":[]}` o, en el raro caso de una fuente ausente, con cuerpo vacío. El middleware debe manejar correctamente ambas variantes.

## Almacenamiento en caché en el servidor

El JSON listo lo almacena el servidor en caché con la clave `(channel_id, fecha UTC, idioma)`, por lo que las solicitudes repetidas del mismo día y canal se sirven sin acceder a la base de datos. El middleware no necesita gestionar la caché.

La caché se vacía automáticamente:

- cuando llegan nuevos eventos desde EIT MPEG-TS de los flujos de entrada (`src=0`);
- al actualizar correctamente una fuente XMLTV externa (`src > 0` — programada o forzada vía `POST /data/epg/update?s=<src_id>`);
- cuando un día sale de la ventana de retención EPG.

No se proporciona ni es necesario un endpoint dedicado para el vaciado forzado de la caché.

## Códigos de respuesta HTTP

| Código | Condición |
| --- | --- |
| `200 OK` | Solicitud procesada. El cuerpo es un JSON con la lista de eventos (posiblemente vacía). |
| `400 Bad Request` | El parámetro `ch` falta o está vacío; o `src`, `t` no se analizan como entero sin signo; o un `lang` numérico apunta a un identificador de idioma inexistente. |
| `401 Unauthorized` | Falta la autenticación HTTP Digest o es incorrecta (para solicitudes no procedentes de una dirección loopback). |

Otras situaciones (`src` desconocido, canal ausente, sin eventos en el día) no devuelven 4xx — el middleware recibe `200 OK` con el array vacío `{"event":[]}`.

En caso de error, el cuerpo de la respuesta es un envoltorio JSON de formato fijo:

```json
{"status": 400, "message": "Bad Request"}
```

El campo `status` duplica el código HTTP; el campo `message` contiene la causa concreta del error en inglés (o el texto estándar del estado HTTP si no hay información adicional). El `Content-Type` de la respuesta de error es `application/json`.

## Ejemplo

```bash
curl -u middleware:secret --digest \
  'http://pss.example.com:43971/data/epg/channel?src=0&ch=12.0.1&lang=rus&t=1715000000'
```

Ejemplo de respuesta:

```json
{
  "event": [
    {"start": 1715000000, "end": 1715003400, "title": "Morning News", "desc": "Overview of the day's events"},
    {"start": 1715003400, "end": 1715007000, "title": "Weather Forecast", "desc": ""}
  ]
}
```

## Rendimiento y escalado

### Caché del servidor Perfect Streamer

Dentro del proceso PSS hay una caché LRU de respuestas en memoria con clave `(channel_id, fecha UTC, idioma)` y un tope duro de 1024 entradas por fuente EPG. Con la carga típica (decenas a cientos de canales × 1–3 idiomas × `keep-day` días) todas las entradas actuales caben completas en la caché; las solicitudes repetidas se atienden sin tocar SQLite.

Orden de magnitud (build debug, loopback local, sin HTTPS):

| Escenario | Latencia (solicitud única) | Rendimiento (P=8) |
| --- | --- | --- |
| Cache hit | ~0,3 ms | ~1100 solicitudes/s |
| Cache miss (SQL + JSON) | ~1,0–1,5 ms | ~1000 solicitudes/s |

En build release y sin logging de depuración las cifras son aproximadamente 2–3× mejores. Ancho de banda — unos 14 KB por respuesta para un canal-día típico.

### Cuándo se necesita un reverse-proxy externo (nginx)

La caché del servidor acelera las solicitudes repetidas para el mismo `(canal, día, idioma)`, pero cada solicitud pasa igualmente por el servidor HTTP integrado de PSS y consume un hilo de su pool. Con muchos clientes conviene trasladar el almacenamiento en caché al edge:

- **hasta ~1.000 clientes en línea** — el caché interno suele bastar, no se requiere reverse-proxy.
- **decenas de miles o más** — se recomienda un reverse-proxy con caché (por ejemplo, nginx). Una caché edge atiende el 99 % de las solicitudes sin PSS, amortigua los picos (arranque de middleware, refresco masivo de reproductores) y permite trasladar la terminación SSL a un nodo aparte.
- **distribución geográficamente repartida** — se necesita un CDN/proxy externo incluso antes de contar clientes.

PSS envía su propia cabecera `Cache-Control: no-cache, no-store, must-revalidate` para que los clientes finales no almacenen el EPG durante mucho tiempo. El reverse-proxy, sin embargo, puede (y debe) cachear la respuesta por su cuenta — más abajo se muestra cómo indicarle explícitamente a nginx que ignore el `Cache-Control` del upstream y mantenga su propio TTL.

### Ejemplo de configuración nginx

Configuración mínima para un caché edge de EPG dimensionada para decenas de miles de clientes con intervalo de sondeo 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;
    }
}
```

### Explicaciones y recomendaciones

- **TTL ``proxy_cache_valid 200 60s``** — compromiso entre frescura del EPG y carga en el upstream. La programación no cambia en tiempo real, por lo que 30–300 segundos es razonable. Tras importar nuevos eventos PSS vacía su caché al instante y la caché edge se pone al día en el siguiente TTL.
- **``proxy_cache_lock on``** es obligatorio con muchos clientes: ante un cache miss, fusiona las solicitudes paralelas sobre la misma clave en una sola solicitud upstream, protegiendo a SQLite de picos de `BUSY` bajo carga.
- **``keys_zone`` y ``max_size``** se dimensionan por el número de `(canal × día × idioma)`: 32 MB de `keys_zone` bastan para cientos de miles de claves; 2 GB de `max_size` cubren un mes de historia para cientos de canales con margen.
- **gzip** reduce notablemente el tráfico: las respuestas se comprimen bien (claves JSON repetidas, cirílico en UTF-8).
- **``X-Cache-Status``** en la respuesta permite al middleware ver `HIT`/`MISS`/`EXPIRED` y evaluar la eficacia de la caché.
- Si nginx y PSS conviven en la misma máquina, el servidor admin no exige HTTP Digest para loopback, así que el bloque upstream puede quedarse sin `proxy_set_header Authorization …`. Para separarlos por redes, cree una cuenta `viewer` aparte y añada la autenticación Digest en `proxy_pass`.
- **HTTPS** conviene terminarlo en nginx: PSS admite HTTPS directamente, pero un servidor edge suele ser más eficiente al gestionar handshakes TLS con miles de clientes simultáneos.

## Endpoints relacionados

- `POST /data/epg/sql?s=<src_id>` — consulta SQL arbitraria contra la base EPG (en particular, para obtener una lista de `channel_id`).
- `POST /data/epg/update?s=<src_id>` — actualización forzada de una fuente XMLTV externa.

La lista completa y la descripción detallada de la API HTTP se encuentran en `manual/http_data_api.txt`.
