EPG-сервер (XMLTV)

Отдельный встроенный HTTP-сервер Perfect Streamer выдаёт полный XMLTV для заданного набора каналов. Эндпоинт предназначен для middleware и плееров, которые хранят программу передач локально и обновляют её редко, файлом целиком — обычно один-два раза в сутки.

Сервер и его клиенты настраиваются в разделе Configuration/EPG/EPG Server.

URL и аутентификация

Сервис обслуживается отдельным HTTP-сервером epg-server (не тем, что /data/*). По умолчанию он слушает порты 10444 (HTTP) и 10445 (HTTPS); порты и SSL настраиваются в разделе /config/epg-server.

Маршруты:

URL

Content-Type

Поведение

GET /xmltv

text/xml

При Accept-Encoding: gzip ответ сжимается на лету (Content-Encoding: gzip), иначе отдаётся как обычный XML.

GET /xmltv.gz

application/octet-stream

Всегда возвращает gzip-поток с Content-Disposition: inline; filename="xmltv.xml.gz" — удобно сохранять как файл.

Поддерживаются три способа аутентификации:

  • HTTP Digest (предпочтительно) — учётная запись из /config/epg-server/login.

  • Параметры в URL — ?l=<login>&p=<password> (синонимы: login=…, password=…).

  • Loopback — запрос с 127.0.0.1 обрабатывается анонимно. Удобно для скриптов, развёрнутых на той же машине.

Предупреждение

Логин и пароль в URL попадают в access-логи reverse-proxy и в историю браузера. Для публичной раздачи XMLTV используйте HTTP Digest либо принимайте запросы только с приватных адресов.

Доступ по channel-set

Каждая учётная запись epg-server/login привязана к одному channel-set из /config/epg-channel-set. EPG в ответе содержит только те каналы, которые входят в указанный набор. Это позволяет одной инсталляции PSS выдавать разный XMLTV разным операторам и middleware.

Базовая настройка в UI:

  1. В Configuration/EPG/EPG Channel Sets создайте группу каналов и назначьте в неё нужные каналы у источников EPG.

  2. В Configuration/EPG/EPG Server Clients заведите учётную запись и привяжите к ней созданную группу. Без привязки channel-set клиенту отдаётся пустой XMLTV.

Дополнительные ограничения для login:

  • ip-addr — если задан и не wildcard, запрос с другого IP получит 403 Forbidden.

  • limit-day — Unix epoch-сек, после которого учётная запись перестаёт обслуживаться (403 Forbidden). Удобно для абонементной модели.

  • pause — временно отключить login без удаления.

Формат ответа

Тело ответа — XMLTV-документ с корнем <tv>. Структура соответствует общепринятой схеме XMLTV DTD:

<?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>

Заметки по полям:

  • Атрибуты source-info-name и source-info-url корня <tv> заполняются из полей EPG Source Name и EPG Source URL в Configuration/EPG/EPG Server.

  • Атрибуты start и stop оформлены в формате YYYYMMDDhhmmss ±zone (часовой пояс берётся из поля time_zone канала).

  • В <programme> могут быть несколько <title>/<desc> для разных языков. Атрибут lang пустой, если в исходных данных EPG идентификатор языка не сопоставился со словарём (запись всё равно попадёт в выдачу).

  • Каналы с конфликтующим channel_id (если один и тот же id пришёл из разных источников) показываются один раз, остальные источники пропускаются с предупреждением в логе сервера.

  • В выдачу попадают только события с stop_time >= now.

HTTP-заголовки

Сервер всегда отправляет:

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

Для /xmltv дополнительно — Content-Encoding: gzip при Accept-Encoding: gzip в запросе. Для /xmltv.gz — Content-Disposition: inline; filename="xmltv.xml.gz".

Запрет клиентского кеша сделан намеренно: XMLTV меняется при каждом импорте EIT либо обновлении внешних XMLTV-источников, и плеер не должен держать устаревшие данные у себя бессрочно. Edge-кеш (nginx) при этом вполне допустим — см. раздел про производительность ниже.

Серверный кеш и его сброс

Готовый XMLTV кешируется в памяти процесса PSS:

  • По одной записи на каждый channel-set; внутри хранятся обе версии тела (raw и gzip) — повторные запросы с Accept-Encoding: gzip или /xmltv.gz не пересжимают данные.

  • Запись помечается счётчиком update-time. Любое обновление EPG (импорт EIT, обновление XMLTV-источника) увеличивает счётчик, и при следующем запросе кеш перестраивается.

Принудительный сброс кеша:

POST /xmltv/reset-cache

Маршрут обслуживается на admin-сервере (порт 43971/43981), не на epg-server. Тело — пустое; ответ — 200 OK с JSON-конвертом.

HTTP-коды ответа

Код

Условие

200 OK

Запрос обработан. Тело — XMLTV-документ (возможно, пустой <tv></tv> при временном сбое БД).

401 Unauthorized

Ни Digest, ни параметры l/p не прошли проверку (для не loopback-запросов).

403 Forbidden

Login существует, но запрос не с разрешённого IP, либо истёк limit-day.

404 Not Found

Любой URL, кроме /xmltv и /xmltv.gz.

405 Method Not Allowed

Метод не GET.

Тело ошибки — JSON-конверт фиксированного формата:

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

Производительность и масштабирование

Серверный кеш

Серверный кеш обслуживает повторные обращения к одному channel-set без обращения к SQLite — копированием готового тела.

Построение XMLTV «с нуля» (cache miss) дороже: для каждого канала делается отдельный SELECT по channel_name, для каждого события — по event_text и event_rating. Время построения ориентировочно:

Размер выдачи

Cache hit

Cache miss (build)

100 каналов / сутки

десятки мс

~0.5–1 с

500 каналов / сутки

~50 мс

2–5 с

1000+ каналов / неделя

~100–300 мс

5–15 с

Для большинства middleware приемлемо запрашивать XMLTV раз в несколько часов или раз в сутки.

Когда нужен внешний reverse-proxy (nginx)

В отличие от /data/epg/channel (короткие JSON-ответы), XMLTV — это один большой документ на channel-set, идеально подходящий для edge-кеша:

  • Десятки и сотни клиентов на один channel-set — внутреннего кеша PSS обычно достаточно, если они опрашивают XMLTV раз в час–сутки.

  • Тысячи клиентов одновременно — рекомендуется reverse-proxy с кешем. Выдача XMLTV-файла на сотни/тысячи запросов — это в первую очередь сетевая нагрузка (сотни КБ — единицы МБ на ответ), которую стоит снимать с PSS.

  • Географически распределённая раздача — без CDN/edge-кеша не обойтись независимо от числа клиентов.

PSS отдаёт Cache-Control: no-cache, поэтому в nginx нужно явно указать nginx игнорировать заголовок upstream-сервера и держать собственный TTL.

Пример конфигурации 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;
    }
}

Пояснения и рекомендации

  • TTL ``proxy_cache_valid 200 30m`` — XMLTV редко меняется чаще получаса. Если синхронизация с источниками идёт раз в час и больше, можно поднимать до 1 часа и более; если важна свежесть после POST /xmltv/reset-cache — снижайте.

  • ``proxy_cache_lock_timeout 60s`` — увеличен по сравнению с /data/epg/channel (где обычно 5 секунд), потому что построение XMLTV для большого channel-set занимает дольше.

  • Отдельный ``keys_zone`` — даже на крупной инсталляции уникальных ключей XMLTV единицы (по числу учётных записей × channel-set), 8 МБ хватит с запасом. max_size выбирается по объёму XMLTV, а не по числу ключей.

  • gzip на стороне nginx не нужен: для /xmltv.gz ответ уже сжат, для /xmltv PSS сам ответит gzip-encoded при наличии Accept-Encoding: gzip.

  • HTTPS-терминация на nginx даёт лучшую производительность при многих одновременных TLS-рукопожатиях.