---
title: EPG для OTT middleware
url: https://doc.pstreamer.tv/ru/manual/web_server_epg.html
lang: ru
product: Perfect Streamer
version: 1.13.3.445
---

# 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; для удалённых обращений учётные данные обязательны.

## Параметры запроса

| Параметр | Тип | Обязат. | По умолчанию | Описание |
| --- | --- | --- | --- | --- |
| `src` | целое без знака | нет | `0` | Источник EPG. `0` — данные, импортированные из MPEG-TS EIT входных потоков. `1, 2, …` — идентификатор записи из `/config/epg/epg-source` (внешний XMLTV-источник). |
| `ch` | строка | **да** | — | Идентификатор канала из базы EPG: значение поля `channel_id` таблицы `channel`. Список доступных идентификаторов можно получить SQL-запросом через `POST /data/epg/sql`. |
| `lang` | целое или ISO 639 | нет | системный язык по умолчанию | `0` — системный язык по умолчанию; целое > 0 — внутренний идентификатор языка (см. `GET /schema/lang`); строка — двух- или трёхбуквенный код ISO 639, например `eng`, `rus`, `fra`. |
| `t` | целое без знака (Unix epoch, сек) | нет | текущее время сервера | Любая точка внутри интересующих суток. Сервер выдаёт события за **UTC-сутки**, которым принадлежит `t`: интервал `[t / 86400 · 86400, (t / 86400 + 1) · 86400)`. Для получения данных за «следующие сутки» достаточно прибавить `86400` к текущему времени. |

> **Примечание**
>
> Сутки берутся в UTC, а не в локальном часовом поясе. Если middleware формирует расписание по локальному календарному дню, граница UTC-суток может не совпадать с локальной полночью; в таком случае нужно сделать два запроса (за смежные UTC-сутки) и склеить результаты по полю `start`.

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

- `Content-Type`: `application/json`.
- HTTP-заголовки запрещают кеширование на стороне клиента и промежуточных прокси:

  ```
  Cache-Control: no-cache, no-store, must-revalidate
  Pragma: no-cache
  Expires: 0
  ```
- Тело ответа — JSON со списком событий за сутки:

```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-коды ответа

| Код | Условие |
| --- | --- |
| `200 OK` | Запрос обработан. Тело — JSON со списком событий (возможно, пустым). |
| `400 Bad Request` | Параметр `ch` не указан или пуст; либо `src`, `t` не разбираются как беззнаковое целое; либо числовой `lang` указывает на несуществующий идентификатор языка. |
| `401 Unauthorized` | Отсутствует либо некорректна HTTP Digest аутентификация (для запросов не с loopback-адреса). |

Прочие ситуации (неизвестный `src`, отсутствие канала, отсутствие событий за сутки) не приводят к 4xx — middleware получает `200 OK` с пустым массивом `{"event":[]}`.

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

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

Поле `status` дублирует HTTP-код, поле `message` содержит уточнённую причину ошибки на английском (либо стандартный текст HTTP-статуса, если дополнительной информации нет). `Content-Type` ответа об ошибке — `application/json`.

## Пример

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

Пример ответа:

```json
{
  "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 минут:

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

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

- **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 при тысячах одновременных клиентов.

## Связанные эндпоинты

- `POST /data/epg/sql?s=<src_id>` — произвольный SQL-запрос к базе EPG (в частности, для получения списка `channel_id`).
- `POST /data/epg/update?s=<src_id>` — принудительное обновление внешнего XMLTV-источника.

Полный перечень и подробное описание HTTP API приведены в `manual/http_data_api.txt`.
