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 |
|---|---|---|---|---|
|
entero sin signo |
no |
|
Fuente EPG. |
|
cadena |
sí |
— |
Identificador del canal en la base EPG: valor del campo |
|
entero o ISO 639 |
no |
idioma por defecto del sistema |
|
|
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 |
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:
{
"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
startse 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
startpor 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 OKcon 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íaPOST /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 |
|---|---|
|
Solicitud procesada. El cuerpo es un JSON con la lista de eventos (posiblemente vacía). |
|
El parámetro |
|
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:
{"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¶
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:
{
"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:
# /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
BUSYbajo carga.``keys_zone`` y ``max_size`` se dimensionan por el número de
(canal × día × idioma): 32 MB dekeys_zonebastan para cientos de miles de claves; 2 GB demax_sizecubren 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/EXPIREDy 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 cuentavieweraparte y añada la autenticación Digest enproxy_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.