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

# EPG pour middleware OTT

Le serveur fournit les données du guide des programmes (EPG) pour la journée sélectionnée pour une chaîne, au format JSON. L’endpoint est destiné aux back-ends de middleware OTT qui agrègent la grille depuis Perfect Streamer pour construire le programme côté client final.

## URL et authentification

L’endpoint est servi par le serveur admin intégré. Par défaut, il est disponible sur les ports `43971` (HTTP) et `43981` (HTTPS) ; les ports se configurent dans **Settings/Server Settings**.

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

L’authentification est HTTP Digest, comme pour le reste de `/data/*`. Pour le middleware, un compte avec le rôle `viewer` (lecture seule) suffit.

> **Note**
>
> Les requêtes depuis l’adresse loopback (`127.0.0.1`) sont exécutées sans contrôle HTTP Digest — le serveur les considère comme anonymes. Pratique pour les scripts locaux et les health-checks d’un middleware déployé sur la même machine que Perfect Streamer ; pour les accès distants, des identifiants sont obligatoires.

## Paramètres de la requête

| Paramètre | Type | Obligatoire | Par défaut | Description |
| --- | --- | --- | --- | --- |
| `src` | entier non signé | non | `0` | Source EPG. `0` — données importées depuis l’EIT MPEG-TS des flux d’entrée. `1, 2, …` — identifiant d’entrée dans `/config/epg/epg-source` (source XMLTV externe). |
| `ch` | chaîne | **oui** | — | Identifiant de la chaîne dans la base EPG : valeur du champ `channel_id` de la table `channel`. La liste des identifiants disponibles peut être obtenue via une requête SQL par `POST /data/epg/sql`. |
| `lang` | entier ou ISO 639 | non | langue système par défaut | `0` — langue système par défaut ; un entier > 0 — identifiant interne de langue (voir `GET /schema/lang`) ; une chaîne — code ISO 639 à deux ou trois lettres, par exemple `eng`, `rus`, `fra`. |
| `t` | entier non signé (Unix epoch, s) | non | heure actuelle du serveur | Tout point à l’intérieur de la journée concernée. Le serveur renvoie les événements de la **journée UTC** à laquelle appartient `t` : intervalle `[t / 86400 · 86400, (t / 86400 + 1) · 86400)`. Pour obtenir les données du « lendemain », il suffit d’ajouter `86400` à l’heure courante. |

> **Note**
>
> La journée est prise en UTC, pas dans le fuseau local. Si le middleware construit la grille selon le jour calendaire local, la frontière de la journée UTC peut ne pas coïncider avec minuit local ; il faut alors lancer deux requêtes (pour deux jours UTC adjacents) et recoller les résultats par le champ `start`.

## Format de la réponse

- `Content-Type`: `application/json`.
- Les en-têtes HTTP interdisent la mise en cache côté client et sur les proxys intermédiaires :

  ```
  Cache-Control: no-cache, no-store, must-revalidate
  Pragma: no-cache
  Expires: 0
  ```
- Le corps de la réponse est un JSON avec la liste des événements du jour :

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

Champs de l’événement :

- `start`, `end` — début et fin de l’émission en Unix epoch (s), UTC.
- `title` — titre de l’émission dans la langue choisie. Si le titre n’existe pas dans la langue demandée pour l’événement, le serveur prend l’entrée dans la langue système par défaut, puis dans toute autre langue disponible.
- `desc` — description étendue. Peut être une chaîne vide si la base ne contenait pas de description séparée pour l’événement.

Particularités de la sortie :

- Les événements avec un titre vide et sans description, ainsi que ceux avec des horodatages incorrects, sont exclus de la sortie.
- Les doublons par `start` sont éliminés : pour un même instant de début, une seule entrée est renvoyée avec la meilleure priorité de langue (demandée → défaut système → autres).
- L’ordre des événements dans le tableau n’est pas garanti — si nécessaire, le middleware trie lui-même la liste par le champ `start`.
- Si la chaîne est introuvable, si la source n’existe pas ou s’il n’y a aucun événement pour la journée choisie, le serveur renvoie `200 OK` avec le tableau vide `{"event":[]}` ou, dans le rare cas d’une source absente, avec un corps vide. Le middleware doit gérer correctement les deux variantes.

## Mise en cache sur le serveur

Le JSON prêt est mis en cache par le serveur avec la clé `(channel_id, date UTC, langue)` ; les requêtes répétées pour les mêmes jour et chaîne sont donc servies sans accéder à la base de données. Le middleware n’a rien à faire pour gérer le cache.

Le cache est purgé automatiquement :

- à l’arrivée de nouveaux événements depuis l’EIT MPEG-TS des flux d’entrée (`src=0`) ;
- lors d’une actualisation réussie d’une source XMLTV externe (`src > 0` — planifiée ou forcée via `POST /data/epg/update?s=<src_id>`) ;
- lorsqu’un jour sort de la fenêtre de conservation EPG.

Un endpoint dédié pour la purge forcée du cache n’est ni prévu ni nécessaire.

## Codes de réponse HTTP

| Code | Condition |
| --- | --- |
| `200 OK` | Requête traitée. Le corps est un JSON avec la liste des événements (éventuellement vide). |
| `400 Bad Request` | Le paramètre `ch` est absent ou vide ; ou `src`, `t` ne s’analysent pas comme entier non signé ; ou un `lang` numérique pointe sur un identifiant de langue inexistant. |
| `401 Unauthorized` | L’authentification HTTP Digest est absente ou invalide (pour les requêtes ne provenant pas d’une adresse loopback). |

Les autres situations (`src` inconnu, chaîne absente, pas d’événements ce jour-là) ne produisent pas de 4xx — le middleware reçoit `200 OK` avec le tableau vide `{"event":[]}`.

En cas d’erreur, le corps de la réponse est une enveloppe JSON de format fixe :

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

Le champ `status` reprend le code HTTP ; le champ `message` contient une cause d’erreur précisée en anglais (ou le texte standard du statut HTTP s’il n’y a pas d’information supplémentaire). Le `Content-Type` de la réponse d’erreur est `application/json`.

## Exemple

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

Exemple de réponse :

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

## Performance et mise à l’échelle

### Cache serveur Perfect Streamer

Au sein du processus PSS, il existe un cache LRU en mémoire des réponses, indexé par `(channel_id, date UTC, langue)`, avec un plafond strict de 1024 entrées par source EPG. Avec une charge typique (dizaines à centaines de chaînes × 1–3 langues × `keep-day` jours), toutes les entrées actuelles tiennent intégralement en cache ; les requêtes répétées sont servies sans accéder à SQLite.

Ordre de grandeur (build de débogage, loopback local, sans HTTPS) :

| Scénario | Latence (requête unique) | Débit (P=8) |
| --- | --- | --- |
| Cache hit | ~0,3 ms | ~1100 requêtes/s |
| Cache miss (SQL + JSON) | ~1,0–1,5 ms | ~1000 requêtes/s |

En build release et sans journalisation de debug, les chiffres sont environ 2 à 3 fois meilleurs. Bande passante — environ 14 Ko par réponse pour une chaîne typique sur la journée.

### Quand un reverse-proxy externe (nginx) est nécessaire

Le cache serveur accélère les requêtes répétées pour le même `(chaîne, jour, langue)`, mais chaque requête passe tout de même par le serveur HTTP intégré de PSS et consomme un thread de son pool. Avec beaucoup de clients, il est judicieux de déplacer la mise en cache au edge :

- **jusqu’à ~1 000 clients en ligne** — le cache interne suffit généralement, un reverse-proxy n’est pas obligatoire.
- **dizaines de milliers et plus** — un reverse-proxy avec cache (par exemple nginx) est recommandé. Un cache edge traite 99 % des requêtes sans PSS, amortit les pics (démarrage de middleware, rafraîchissement massif des lecteurs) et permet de placer la terminaison SSL sur un nœud séparé.
- **distribution géographiquement répartie** — un CDN/proxy externe est nécessaire avant même de compter les clients.

PSS envoie son propre en-tête `Cache-Control: no-cache, no-store, must-revalidate` pour que les clients finaux ne mettent pas l’EPG en cache durablement. Le reverse-proxy peut (et doit) mettre la réponse en cache lui-même — on montre plus bas comment indiquer explicitement à nginx d’ignorer le `Cache-Control` upstream et de tenir son propre TTL.

### Exemple de configuration nginx

Une configuration minimale pour un cache edge EPG dimensionnée pour des dizaines de milliers de clients avec un intervalle d’interrogation de 1 à 5 minutes :

```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;
    }
}
```

### Explications et recommandations

- **TTL ``proxy_cache_valid 200 60s``** — compromis entre fraîcheur de l’EPG et charge sur l’upstream. La grille ne change pas en temps réel, 30 à 300 s sont donc raisonnables. Après l’import de nouveaux événements, PSS purge son propre cache instantanément, et le cache edge rattrape au prochain TTL.
- **``proxy_cache_lock on``** est obligatoire pour un grand nombre de clients : lors d’un cache miss, il coalesce les requêtes parallèles sur la même clé en une seule requête upstream, protégeant SQLite des pics de `BUSY` sous charge.
- **``keys_zone`` et ``max_size``** sont dimensionnés selon le nombre `(chaîne × jour × langue)` : 32 Mo de `keys_zone` couvrent des centaines de milliers de clés ; 2 Go de `max_size` couvrent un mois d’historique pour des centaines de chaînes, avec marge.
- **gzip** réduit nettement le trafic : les réponses se compressent bien (clés JSON répétées, cyrillique en UTF-8).
- **``X-Cache-Status``** dans la réponse permet au middleware de voir `HIT`/`MISS`/`EXPIRED` et d’évaluer l’efficacité du cache.
- Si nginx et PSS résident sur la même machine, le serveur admin n’exige pas HTTP Digest pour le loopback ; le bloc upstream peut donc rester sans `proxy_set_header Authorization …`. Pour un déploiement à travers les réseaux, créez un compte `viewer` dédié et ajoutez l’authentification Digest dans `proxy_pass`.
- **HTTPS** se termine de préférence sur nginx : PSS prend HTTPS directement en charge, mais un serveur edge est généralement plus efficace pour gérer les handshakes TLS avec des milliers de clients simultanés.

## Endpoints associés

- `POST /data/epg/sql?s=<src_id>` — requête SQL arbitraire sur la base EPG (notamment pour obtenir la liste des `channel_id`).
- `POST /data/epg/update?s=<src_id>` — actualisation forcée d’une source XMLTV externe.

La liste complète et la description détaillée de l’API HTTP figurent dans `manual/http_data_api.txt`.
