EPG server (XMLTV)¶
A separate built-in HTTP server in Perfect Streamer serves the full XMLTV for a given set of channels. The endpoint is intended for middleware and players that store the programme guide locally and refresh it infrequently, as a whole file — typically once or twice a day.
The server and its clients are configured under Configuration/EPG/EPG Server.
URL and authentication¶
The service is served by a separate HTTP server epg-server (not the one for /data/*). By default it listens on ports 10444 (HTTP) and 10445 (HTTPS); the ports and SSL are configured under /config/epg-server.
Routes:
URL |
Content-Type |
Behavior |
|---|---|---|
|
|
With |
|
|
Always returns a gzip stream with |
Three authentication methods are supported:
HTTP Digest (preferred) — account from
/config/epg-server/login.URL parameters —
?l=<login>&p=<password>(synonyms:login=…,password=…).Loopback — a request from
127.0.0.1is handled anonymously. Convenient for scripts deployed on the same machine.
Warning
A login and password in the URL end up in the reverse-proxy access logs and in the browser history. For public XMLTV distribution use HTTP Digest or accept requests only from private addresses.
Access by channel-set¶
Each epg-server/login account is bound to one channel-set from /config/epg-channel-set. The EPG in the response contains only the channels that belong to the specified set. This lets a single PSS deployment serve different XMLTV to different operators/middleware.
Basic setup in the UI:
Under Configuration/EPG/EPG Channel Sets, create a channel group and assign the desired channels to it at the EPG sources.
Under Configuration/EPG/EPG Server Clients, create an account and bind the channel group to it. Without a
channel-setbinding the client gets an empty XMLTV.
Additional restrictions for a login:
ip-addr— if set and not a wildcard, a request from a different IP gets403 Forbidden.limit-day— Unix epoch sec after which the account stops being served (403 Forbidden). Convenient for a subscription model.pause— temporarily disable a login without deleting it.
Response format¶
Response body is an XMLTV document rooted at <tv>. The structure follows the commonly used 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>
Field notes:
The
source-info-nameandsource-info-urlattributes of the<tv>root are filled from the EPG Source Name and EPG Source URL fields under Configuration/EPG/EPG Server.The
startandstopattributes use theYYYYMMDDhhmmss ±zoneformat (the time zone comes from the channel’stime_zonefield).A
<programme>may contain several<title>/<desc>for different languages. Thelangattribute is empty when the source EPG language id could not be mapped to the dictionary (the entry still appears in the output).Channels with a conflicting
channel_id(if the same id came from multiple sources) are listed once, the remaining sources are skipped with a warning in the server log.Only events with
stop_time >= noware included in the output.
HTTP headers¶
The server always sends:
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Connection: close
For /xmltv additionally — Content-Encoding: gzip when Accept-Encoding: gzip is present in the request. For /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".
The client-side caching ban is intentional: XMLTV changes on every EIT import or external XMLTV-source refresh, and the player must not hold stale data indefinitely. An edge cache (nginx) is fully acceptable, however — see the performance section below.
Server cache and how to flush it¶
The ready XMLTV is cached in the PSS process memory:
One entry per
channel-set; both body variants (raw and gzip) are stored — repeat requests withAccept-Encoding: gzipor/xmltv.gzdo not re-compress the data.Each entry is tagged with an
update-timecounter. Any EPG update (EIT import, XMLTV source refresh) increments the counter, and the cache is rebuilt on the next request.
Force cache flush:
POST /xmltv/reset-cache
The route is served by the admin server (port 43971/43981), not by epg-server. Empty request body; the response is 200 OK with a JSON envelope.
HTTP response codes¶
Code |
Condition |
|---|---|
|
Request processed. Body is an XMLTV document (possibly an empty |
|
Neither Digest nor |
|
The login exists but the request is not from an allowed IP, or |
|
Any URL other than |
|
Method is not GET. |
Error body is a fixed-format JSON envelope:
{"status": 401, "message": "Unauthorized"}
Performance and scaling¶
Server cache¶
The server cache serves repeated requests to a single channel-set without touching SQLite — by copying the ready body.
Building XMLTV from scratch (cache miss) is more expensive: a separate SELECT on channel_name is issued per channel, and on event_text and event_rating per event. Approximate build times:
Output size |
Cache hit |
Cache miss (build) |
|---|---|---|
100 channels / day |
tens of ms |
~0.5–1 s |
500 channels / day |
~50 ms |
2–5 s |
1000+ channels / week |
~100–300 ms |
5–15 s |
For most middleware it is acceptable to fetch XMLTV every few hours or once a day.
When an external reverse-proxy (nginx) is needed¶
Unlike /data/epg/channel (short JSON responses), XMLTV is a single large document per channel-set, ideally suited for an edge cache:
Tens to hundreds of clients per channel-set — the internal PSS cache is usually enough if they poll XMLTV every hour-to-day.
Thousands of concurrent clients — a caching reverse-proxy is recommended. Serving an XMLTV file to hundreds/thousands of requests is, first of all, a network load (hundreds of KB – a few MB per response) that is best taken off PSS.
Geographically distributed delivery — a CDN/edge cache is unavoidable regardless of client count.
PSS sends Cache-Control: no-cache, so nginx must be told explicitly to ignore the upstream header and keep its own TTL.
Sample nginx configuration¶
# /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;
}
}
Notes and recommendations¶
TTL ``proxy_cache_valid 200 30m`` — XMLTV rarely changes more often than every half hour. If source sync runs hourly or less often, this can be raised to 1 hour or more; if freshness after
POST /xmltv/reset-cachematters, lower it.``proxy_cache_lock_timeout 60s`` — increased compared to
/data/epg/channel(where 5 seconds is typical), because building the XMLTV for a large channel-set takes longer.A dedicated ``keys_zone`` — even on a large deployment, unique XMLTV keys are few (login count ×
channel-set); 8 MB is plenty.max_sizeis sized by XMLTV volume, not by key count.gzip on the nginx side is unnecessary: for
/xmltv.gzthe response is already compressed, and for/xmltvPSS itself replies gzip-encoded whenAccept-Encoding: gzipis present.HTTPS termination on nginx gives better performance under many concurrent TLS handshakes.