EPG-Server (XMLTV)

Ein separater integrierter HTTP-Server in Perfect Streamer liefert das vollständige XMLTV für einen vorgegebenen Kanalsatz. Der Endpunkt ist für Middleware und Player gedacht, die das Programm lokal vorhalten und es selten aktualisieren — als ganze Datei, typischerweise ein- bis zweimal täglich.

Der Server und seine Clients werden unter Configuration/EPG/EPG Server konfiguriert.

URL und Authentifizierung

Der Dienst wird von einem separaten HTTP-Server epg-server bereitgestellt (nicht von dem für /data/*). Standardmäßig lauscht er auf den Ports 10444 (HTTP) und 10445 (HTTPS); Ports und SSL werden unter /config/epg-server konfiguriert.

Routen:

URL

Content-Type

Verhalten

GET /xmltv

text/xml

Bei Accept-Encoding: gzip wird die Antwort spontan komprimiert (Content-Encoding: gzip), sonst als unkomprimiertes XML ausgeliefert.

GET /xmltv.gz

application/octet-stream

Liefert stets einen Gzip-Stream mit Content-Disposition: inline; filename="xmltv.xml.gz" — praktisch zum Speichern als Datei.

Drei Authentifizierungsmethoden werden unterstützt:

  • HTTP Digest (bevorzugt) — Konto aus /config/epg-server/login.

  • URL-Parameter — ?l=<login>&p=<password> (Synonyme: login=…, password=…).

  • Loopback — eine Anfrage von 127.0.0.1 wird anonym behandelt. Praktisch für Skripte, die auf derselben Maschine laufen.

Warnung

Login und Passwort in der URL landen in den Access-Logs des Reverse-Proxys und im Browserverlauf. Für die öffentliche XMLTV-Verteilung verwenden Sie HTTP Digest oder lassen Sie Anfragen nur von privaten Adressen zu.

Zugriff per channel-set

Jedes epg-server/login-Konto ist an ein channel-set aus /config/epg-channel-set gebunden. Das EPG in der Antwort enthält nur die Kanäle des angegebenen Sets. So kann eine einzige PSS-Installation unterschiedlichen Betreibern/Middleware unterschiedliches XMLTV ausliefern.

Grundeinrichtung in der UI:

  1. Erstellen Sie unter Configuration/EPG/EPG Channel Sets eine Kanalgruppe und weisen Sie die gewünschten Kanäle bei den EPG-Quellen zu.

  2. Legen Sie unter Configuration/EPG/EPG Server Clients ein Konto an und binden Sie die erstellte Gruppe daran. Ohne channel-set-Bindung erhält der Client ein leeres XMLTV.

Zusätzliche Einschränkungen für einen Login:

  • ip-addr — wenn gesetzt und kein Platzhalter, erhält eine Anfrage von einer anderen IP 403 Forbidden.

  • limit-day — Unix-Epoche-Sek., nach der das Konto nicht mehr bedient wird (403 Forbidden). Praktisch für ein Abonnementmodell.

  • pause — einen Login vorübergehend deaktivieren, ohne ihn zu löschen.

Antwortformat

Der Antwortkörper ist ein XMLTV-Dokument mit der Wurzel <tv>. Die Struktur folgt dem gängigen XMLTV DTD-Schema:

<?xml version="1.0" encoding="utf-8"?>
<tv source-info-name="..." source-info-url="...">
  <channel id="channel.one">
    <display-name lang="en">Channel One</display-name>
    <icon src="https://.../channel-one.png"/>
  </channel>
  ...
  <programme start="20260504060000 +0300"
             stop ="20260504070000 +0300"
             channel="channel.one">
    <title lang="en">Morning News</title>
    <desc  lang="en">Overview of the day's events</desc>
    <rating system="MPAA"><value>PG</value></rating>
  </programme>
  ...
</tv>

Hinweise zu Feldern:

  • Die Attribute source-info-name und source-info-url der <tv>-Wurzel werden aus den Feldern EPG Source Name und EPG Source URL unter Configuration/EPG/EPG Server gefüllt.

  • Die Attribute start und stop werden im Format YYYYMMDDhhmmss ±zone angegeben (die Zeitzone stammt aus dem Feld time_zone des Kanals).

  • Ein <programme> darf mehrere <title>/<desc> für verschiedene Sprachen enthalten. Das Attribut lang ist leer, wenn die Sprach-ID aus den EPG-Quelldaten nicht im Wörterbuch zugeordnet werden konnte (der Eintrag erscheint dennoch in der Ausgabe).

  • Kanäle mit einem konfliktbehafteten channel_id (wenn dieselbe ID aus mehreren Quellen kommt) werden einmal angezeigt, die übrigen Quellen werden mit einer Warnung im Serverlog übersprungen.

  • Nur Ereignisse mit stop_time >= now werden in die Ausgabe aufgenommen.

HTTP-Header

Der Server sendet immer:

Cache-Control: no-cache, no-store, must-revalidate
Pragma:        no-cache
Expires:       0
Connection:    close

Für /xmltv zusätzlich — Content-Encoding: gzip bei vorhandenem Accept-Encoding: gzip in der Anfrage. Für /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".

Das Verbot des clientseitigen Cachens ist beabsichtigt: XMLTV ändert sich bei jedem EIT-Import oder Refresh externer XMLTV-Quellen, und der Player darf veraltete Daten nicht unbegrenzt vorhalten. Ein Edge-Cache (nginx) ist hingegen voll akzeptabel — siehe Abschnitt zur Leistung weiter unten.

Server-Cache und sein Zurücksetzen

Das fertige XMLTV wird im Speicher des PSS-Prozesses gecacht:

  • Ein Eintrag pro channel-set; intern werden beide Körpervarianten (raw und gzip) vorgehalten — wiederholte Anfragen mit Accept-Encoding: gzip oder /xmltv.gz komprimieren die Daten nicht erneut.

  • Jeder Eintrag erhält einen update-time-Zähler. Jede EPG-Aktualisierung (EIT-Import, XMLTV-Quellen-Refresh) erhöht den Zähler, und der Cache wird bei der nächsten Anfrage neu aufgebaut.

Cache erzwungen leeren:

POST /xmltv/reset-cache

Die Route wird vom Admin-Server (Port 43971/43981) bedient, nicht vom epg-server. Der Anfragekörper ist leer; die Antwort ist 200 OK mit einem JSON-Umschlag.

HTTP-Antwortcodes

Code

Bedingung

200 OK

Anfrage verarbeitet. Der Körper ist ein XMLTV-Dokument (eventuell ein leeres <tv></tv> bei einem vorübergehenden DB-Fehler).

401 Unauthorized

Weder Digest noch l/p-Parameter haben die Prüfung bestanden (für nicht-Loopback-Anfragen).

403 Forbidden

Der Login existiert, aber die Anfrage kommt nicht von einer erlaubten IP, oder limit-day ist abgelaufen.

404 Not Found

Jede URL außer /xmltv und /xmltv.gz.

405 Method Not Allowed

Methode ist nicht GET.

Der Fehlerkörper ist ein JSON-Umschlag mit festem Format:

{"status": 401, "message": "Unauthorized"}

Leistung und Skalierung

Server-Cache

Der Server-Cache bedient wiederholte Anfragen an dasselbe channel-set ohne SQLite-Zugriff — durch Kopieren des fertigen Körpers.

Der XMLTV-Aufbau „von Grund auf“ (Cache-Miss) ist teurer: pro Kanal ein eigenes SELECT über channel_name, pro Ereignis eines über event_text und event_rating. Anhaltspunkte zur Bauzeit:

Ausgabegröße

Cache hit

Cache miss (build)

100 Kanäle / Tag

einige Dutzend ms

~0,5–1 s

500 Kanäle / Tag

~50 ms

2–5 s

1000+ Kanäle / Woche

~100–300 ms

5–15 s

Für die meiste Middleware ist es akzeptabel, XMLTV alle paar Stunden oder einmal täglich abzufragen.

Wann ein externer Reverse-Proxy (nginx) nötig ist

Im Gegensatz zu /data/epg/channel (kurze JSON-Antworten) ist XMLTV ein einziges großes Dokument pro channel-set und damit ideal für einen Edge-Cache:

  • Zehn bis Hunderte Clients pro channel-set — der interne PSS-Cache reicht meist aus, wenn sie XMLTV stündlich bis täglich abfragen.

  • Tausende gleichzeitige Clients — ein cachender Reverse-Proxy wird empfohlen. Das Ausliefern einer XMLTV-Datei an Hunderte/Tausende Anfragen ist in erster Linie eine Netzwerklast (Hunderte KB – einige MB pro Antwort), die man besser von PSS fernhält.

  • Geografisch verteilte Auslieferung — ohne CDN/Edge-Cache geht es nicht, unabhängig von der Clientzahl.

PSS sendet Cache-Control: no-cache, daher muss nginx explizit angewiesen werden, den Upstream-Header zu ignorieren und einen eigenen TTL zu führen.

Beispielkonfiguration für nginx

# /etc/nginx/conf.d/pss-xmltv.conf

proxy_cache_path /var/cache/nginx/pss-xmltv
                 levels=1:2
                 keys_zone=pss_xmltv:8m
                 max_size=4g
                 inactive=2h
                 use_temp_path=off;

upstream pss_epg {
    server 127.0.0.1:10444;
    keepalive 16;
}

server {
    listen 80;
    # listen 443 ssl http2;     # SSL termination makes sense here
    server_name epg-files.example.com;

    # Do not enable gzip on: /xmltv.gz is already compressed, and /xmltv
    # arrives gzip-encoded from PSS — re-compressing is pointless.

    location ~ ^/xmltv(\.gz)?$ {
        proxy_pass http://pss_epg;
        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 including query (login/password in /xmltv?l=...&p=...)
        # produce distinct keys for different accounts with different channel-sets.
        proxy_cache_key     "$scheme$host$request_uri";

        proxy_cache         pss_xmltv;
        proxy_cache_valid   200      30m;       # XMLTV changes infrequently
        proxy_cache_valid   401 403  1m;
        proxy_cache_lock           on;          # coalesce cache misses
        proxy_cache_lock_timeout   60s;         # XMLTV build may take seconds
        proxy_cache_use_stale      error timeout updating
                                   http_500 http_502 http_503;

        # Large buffer: XMLTV for a big channel-set can reach
        # several megabytes.
        proxy_buffering      on;
        proxy_buffers        16 256k;
        proxy_buffer_size    256k;
        proxy_busy_buffers_size 1m;

        # Let the client cache for a short period.
        add_header Cache-Control "public, max-age=600";
        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Erläuterungen und Empfehlungen

  • TTL ``proxy_cache_valid 200 30m`` — XMLTV ändert sich selten häufiger als alle halbe Stunde. Erfolgt die Synchronisation mit den Quellen stündlich oder seltener, kann der Wert auf 1 Stunde und mehr angehoben werden; ist Frische nach POST /xmltv/reset-cache wichtig, senken Sie ihn.

  • ``proxy_cache_lock_timeout 60s`` — gegenüber /data/epg/channel erhöht (dort üblich sind 5 Sekunden), weil der Aufbau eines XMLTV für ein großes channel-set länger dauert.

  • Eine dedizierte ``keys_zone`` — selbst bei einer großen Installation sind eindeutige XMLTV-Schlüssel rar (Anzahl Logins × channel-set), 8 MB reichen mit Reserve. max_size wird nach dem XMLTV-Volumen, nicht nach der Schlüsselanzahl bemessen.

  • gzip auf nginx-Seite ist unnötig: Für /xmltv.gz ist die Antwort bereits komprimiert, für /xmltv antwortet PSS selbst gzip-encoded bei vorhandenem Accept-Encoding: gzip.

  • HTTPS-Terminierung auf nginx liefert bessere Leistung bei vielen gleichzeitigen TLS-Handshakes.