EPG для OTT middleware¶
Сервер выдаёт данные программы передач (EPG) за выбранные сутки по одному каналу в формате JSON. Эндпоинт предназначен для бэкенда OTT middleware, который агрегирует расписание из Perfect Streamer для построения программы передач у конечного клиента.
URL и аутентификация¶
Эндпоинт обслуживается встроенным admin-сервером. По умолчанию он доступен
по портам 43971 (HTTP) и 43981 (HTTPS); порты настраиваются в разделе
Settings/Server Settings.
GET /data/epg/channel?src=<src_id>&ch=<channel_id>&lang=<lang>&t=<time>
Аутентификация — HTTP Digest, как у остальных /data/*. Для middleware
достаточно учётной записи с ролью viewer (только чтение).
Примечание
Запросы с loopback-адреса (127.0.0.1) выполняются без проверки
HTTP Digest — сервер считает их анонимными. Это удобно для локальных
скриптов и health-check’ов middleware, развёрнутого на той же машине,
что и Perfect Streamer; для удалённых обращений учётные данные
обязательны.
Параметры запроса¶
Параметр |
Тип |
Обязат. |
По умолчанию |
Описание |
|---|---|---|---|---|
|
целое без знака |
нет |
|
Источник EPG. |
|
строка |
да |
— |
Идентификатор канала из базы EPG: значение поля |
|
целое или ISO 639 |
нет |
системный язык по умолчанию |
|
|
целое без знака (Unix epoch, сек) |
нет |
текущее время сервера |
Любая точка внутри интересующих суток. Сервер выдаёт события за
UTC-сутки, которым принадлежит |
Примечание
Сутки берутся в UTC, а не в локальном часовом поясе. Если middleware
формирует расписание по локальному календарному дню, граница UTC-суток
может не совпадать с локальной полночью; в таком случае нужно сделать
два запроса (за смежные UTC-сутки) и склеить результаты по полю
start.
Формат ответа¶
Content-Type:application/json.HTTP-заголовки запрещают кеширование на стороне клиента и промежуточных прокси:
Cache-Control: no-cache, no-store, must-revalidate Pragma: no-cache Expires: 0
Тело ответа — JSON со списком событий за сутки:
{
"event": [
{"start": 1715000000, "end": 1715003400, "title": "Morning News", "desc": "Overview of the day's events"},
{"start": 1715003400, "end": 1715007000, "title": "Weather Forecast", "desc": ""}
]
}
Поля события:
start,end— моменты начала и окончания передачи в Unix epoch (сек), UTC.title— заголовок передачи на выбранном языке. Если для события на запрошенном языке заголовка нет, сервер берёт запись на системном языке по умолчанию, затем — на любом другом доступном.desc— расширенное описание. Может быть пустой строкой, если в базе для события не было отдельного описания.
Особенности выдачи:
В выдачу не попадают события с пустым заголовком и без описания, а также события с некорректными метками времени.
Дубликаты по
startотсекаются: для одного и того же момента начала возвращается одна запись с лучшим приоритетом языка (запрошенный → системный default → остальные).Порядок событий в массиве не гарантируется — при необходимости middleware сортирует список по полю
startсамостоятельно.Если канал не найден, источник не существует или за выбранные сутки нет событий, сервер возвращает
200 OKс пустым массивом{"event":[]}либо с пустым телом (в редком случае отсутствия источника). Middleware должен корректно обрабатывать оба варианта.
Кеширование на сервере¶
Готовый JSON кешируется сервером по ключу (channel_id, дата UTC, язык),
поэтому повторные запросы за те же сутки одного канала обрабатываются без
обращения к базе данных. От middleware никаких действий по управлению кешем
не требуется.
Сброс кеша выполняется автоматически:
при поступлении новых событий из MPEG-TS EIT входных потоков (
src=0);при успешном обновлении внешнего XMLTV-источника (
src > 0— плановое обновление либо принудительное черезPOST /data/epg/update?s=<src_id>);при выходе суток за окно хранения EPG.
Принудительный сброс кеша отдельным эндпоинтом не предусмотрен и не требуется.
HTTP-коды ответа¶
Код |
Условие |
|---|---|
|
Запрос обработан. Тело — JSON со списком событий (возможно, пустым). |
|
Параметр |
|
Отсутствует либо некорректна HTTP Digest аутентификация (для запросов не с loopback-адреса). |
Прочие ситуации (неизвестный src, отсутствие канала, отсутствие событий
за сутки) не приводят к 4xx — middleware получает 200 OK с пустым
массивом {"event":[]}.
Тело ответа при ошибке — JSON-конверт фиксированного формата:
{"status": 400, "message": "Bad Request"}
Поле status дублирует HTTP-код, поле message содержит уточнённую
причину ошибки на английском (либо стандартный текст HTTP-статуса, если
дополнительной информации нет). Content-Type ответа об ошибке —
application/json.
Пример¶
curl -u middleware:secret --digest \
'http://pss.example.com:43971/data/epg/channel?src=0&ch=12.0.1&lang=rus&t=1715000000'
Пример ответа:
{
"event": [
{"start": 1715000000, "end": 1715003400, "title": "Morning News", "desc": "Overview of the day's events"},
{"start": 1715003400, "end": 1715007000, "title": "Weather Forecast", "desc": ""}
]
}
Производительность и масштабирование¶
Серверный кеш Perfect Streamer¶
Внутри процесса PSS реализован in-memory LRU-кеш ответов с ключом
(channel_id, дата UTC, язык) и хард-капом 1024 записи на источник EPG.
При типовой нагрузке (десятки–сотни каналов × 1–3 языка × keep-day дней)
все актуальные записи помещаются в кеш целиком; ответы на повторные запросы
обрабатываются без обращения к SQLite.
Порядок величин (debug-сборка, локальный loopback, без HTTPS):
Сценарий |
Задержка (одиночный запрос) |
Пропускная способность (P=8) |
|---|---|---|
Cache hit |
~0.3 мс |
~1100 запросов/с |
Cache miss (SQL + JSON) |
~1.0–1.5 мс |
~1000 запросов/с |
В release-сборке и без отладочного логирования цифры примерно в 2–3 раза лучше. Пропускная способность — около 14 КБ на ответ для типового канала за сутки.
Когда нужен внешний reverse-proxy (nginx)¶
Серверный кеш ускоряет повторные обращения к одному и тому же
(channel, день, язык), но каждый запрос всё равно проходит через
встроенный HTTP-сервер PSS и тратит поток из его пула. На большом числе
клиентов имеет смысл вынести кеширование на edge:
до ~1 000 онлайн-клиентов — внутреннего кеша обычно достаточно, reverse-proxy не обязателен.
десятки тысяч и больше — рекомендуется reverse-proxy с кешем (например, nginx). Edge-кеш отрабатывает 99 % запросов без участия PSS, амортизирует пики (старт middleware, массовое обновление плеера) и даёт возможность поставить SSL-терминацию на отдельный узел.
географически распределённая раздача — внешний CDN/прокси нужен ещё до подсчёта числа клиентов.
PSS отдаёт собственный заголовок Cache-Control: no-cache, no-store,
must-revalidate, чтобы конечные клиенты не закешировали EPG надолго.
Reverse-proxy при этом может (и должен) кешировать ответ самостоятельно —
ниже показано, как явно сказать nginx игнорировать Cache-Control
upstream-а и держать собственный TTL.
Пример конфигурации nginx¶
Минимальный конфиг для edge-кеша EPG, рассчитанный на десятки тысяч клиентов с интервалом опроса 1–5 минут:
# /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;
}
}
Пояснения и рекомендации¶
TTL ``proxy_cache_valid 200 60s`` — компромисс между свежестью EPG и нагрузкой на upstream. Программа передач не меняется в реальном времени, поэтому 30–300 секунд выглядят разумно. После импорта новых событий PSS сбрасывает свой кеш мгновенно, а edge-кеш догонит за следующий TTL.
``proxy_cache_lock on`` обязательно для большого числа клиентов: при cache miss он коалесцирует параллельные запросы к одному ключу в один upstream-запрос, защищая SQLite от пиковых
BUSYпод нагрузкой.``keys_zone`` и ``max_size`` подбираются по числу
(канал × сутки × язык): 32 МБkeys_zoneхватит на сотни тысяч ключей; 2 ГБmax_size— на месяц истории по сотням каналов с запасом.gzip существенно режет трафик: ответы хорошо сжимаются (повторяющиеся ключи JSON, кириллица в UTF-8).
``X-Cache-Status`` в ответе позволяет на стороне middleware видеть
HIT/MISS/EXPIREDи оценить эффективность кеша.Если nginx и PSS живут на одной машине — admin-сервер не требует HTTP Digest для loopback, поэтому upstream-блок можно оставить без
proxy_set_header Authorization …. Для разнесения по сетям заведите отдельную учёткуviewerи добавьте Digest-аутентификацию вproxy_pass.HTTPS разумно терминировать на nginx: PSS поддерживает HTTPS напрямую, но edge-сервер обычно эффективнее в обработке TLS-handshakes при тысячах одновременных клиентов.