---
title: OTT сервис
url: https://doc.pstreamer.tv/ru/manual/ott.html
lang: ru
product: Perfect Streamer
version: 1.13.3.445
---

# OTT сервис

Выдаёт потоки по протоколам на базе HTTP — **HLS** (на MPEG-TS), **MPEG-DASH** и **Low-Latency HLS** (на CMAF — фрагментированном MP4, с версии 1.13), а также **MPEG-TS over HTTP**. Поддерживается HTTPS (SSL) и HTTP/3 (QUIC). Выдача включается на вкладке **OTT** настроек **Stream**.

URL для подключения имеют формат:

- [http://host:port/http/stream/login/password](http://host:port/http/stream/login/password) — авторизация по логину и паролю
- [http://host:port/http/stream/login](http://host:port/http/stream/login) — авторизация по логину (токен)
- [http://host:port/http/stream/](http://host:port/http/stream/) — авторизация по IP

**host** и **port** — задаются в настройках **http server**.

**stream** — **ID** стрима. Не путать с порядковым номером в списке стримов. **ID** отображается вверху страницы статистики стрима и в колонке *ID* списка стримов. **ID** задаётся при создании стрима и никогда не меняется.

Аналогично для **HLS**, **DASH** и **Low-Latency HLS** (последние два — только в режиме *OTT/HLS/LL-HLS/LL-Dash*, см. ниже):

- [http://host:port/hls/stream/login/password](http://host:port/hls/stream/login/password)
- [http://host:port/hls/stream/login](http://host:port/hls/stream/login)
- [http://host:port/hls/stream/](http://host:port/hls/stream/)
- [http://host:port/dash/stream/login/password](http://host:port/dash/stream/login/password)
- [http://host:port/dash/stream/login](http://host:port/dash/stream/login)
- [http://host:port/dash/stream/](http://host:port/dash/stream/)
- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

На странице статистики стрима отображаются URL подключенных протоколов (в виде шаблона) и текущий статус их работы. Неавторизованный доступ запрещён. Клиенты должны быть прописаны в **Peers**.

Для **HLS** и **DASH** в URL доступны дополнительные параметры (опционально):

[URL]?a=1&s=40&m=40&v=5&h3=1

- **a**: 1 — абсолютный путь в плейлисте, 0 — относительный путь (по умолчанию).
- **s**: продолжительность динамического плейлиста (сек), по умолчанию 40 сек.
- **m**: минимальная продолжительность динамического плейлиста (сек), по умолчанию 40 сек. Максимальный размер динамического плейлиста 60 сек. Если текущий размер буфера чанков менее минимального размера, указанного в запросе, то будет выдана ошибка 404. Это сделано для того, чтобы HLS стартовал с заполненного буфера чанков на сервере.
- **v**: версия HLS-протокола, выдаваемого в плейлисте. По умолчанию значение зависит от режима HLS (см. ниже): *OTT/HLS* и *OTT/HLS/LL-HLS/LL-Dash* — *6*, *Peering/HLS* — *3*. Явное значение в URL переопределяет значение по умолчанию. Смена версии может потребоваться для совместимости с конкретным HLS-клиентом.
- **h3**: opt-in на HTTP/3 (QUIC) для этой OTT-сессии. Заставляет сервер выдать заголовок `Alt-Svc` в ответе, после чего совместимый плеер / браузер переключается на QUIC. См. раздел HTTP/3 (QUIC) ниже.

Для совместимости с некоторыми HLS-клиентами в URL может быть добавлено имя файла index.m3u8, например, [http://host:port/hls/stream/login/password/index.m3u8](http://host:port/hls/stream/login/password/index.m3u8).

Режим выдачи задаётся настройкой стрима *OTT HLS* (вкладка **OTT**): *Peering/HLS*, *OTT/HLS* или *OTT/HLS/LL-HLS/LL-Dash*.

*Peering/HLS* — режим с простой разбивкой сегментов (чанков). Рекомендуется для пиринга (дистрибьюции) потоков. Выдаётся только **HLS** на MPEG-TS (`/hls`). По умолчанию плейлист отдаётся как *EXT-X-VERSION:3* для совместимости с пир-клиентами.

*OTT/HLS* — режим с разбивкой сегментов, оптимизированной для быстрого старта проигрывателей при OTT-вещании. В этом режиме нагрузка на CPU больше, рекомендуется для вещания. Выдаётся **HLS** на MPEG-TS (`/hls`). По умолчанию плейлист отдаётся как *EXT-X-VERSION:6* с *EXT-X-INDEPENDENT-SEGMENTS* и атрибутом *CHARACTERISTICS* в *EXT-X-MEDIA TYPE=SUBTITLES* (Apple HLS, hls.js, Safari, dash.js/Shaka). Если конкретному клиенту нужно более раннее значение — задайте его через query-параметр `?v=` (см. список параметров выше).

*OTT/HLS/LL-HLS/LL-Dash* — режим выдачи на **CMAF** (фрагментированный MP4, *fMP4*; с версии 1.13). Стрим формирует fMP4-сегменты (`.m4s` + общий `init.mp4`, `mimeType="video/mp4"`), поверх которых отдаются:

- **MPEG-DASH** на `/dash` — теперь на CMAF, а **не** на MPEG-TS;
- **Low-Latency HLS** на `/llhls` (см. Low-Latency HLS ниже);
- при включённой настройке стрима *Enable TS Chunk* (по умолчанию *true*) — дополнительно legacy **HLS** на MPEG-TS (`/hls`), как в режиме *OTT/HLS*; при *false* выдаются только fMP4-сегменты, что экономит диск и CPU.

Плейлист **HLS** в режиме *OTT/HLS/LL-HLS/LL-Dash* по умолчанию также *EXT-X-VERSION:6*.

> **Примечание**
>
> **MPEG-DASH** и **Low-Latency HLS** доступны только в режиме *OTT/HLS/LL-HLS/LL-Dash*. В режимах *OTT/HLS* и *Peering/HLS* выдаётся исключительно **HLS** на MPEG-TS.

Для HTTP-сервера может быть включён SSL (HTTPS) — это делается в настройках сервера.

**Chunk Min Interval и Chunk Max Interval**

В режиме OTT делается анализ потока на PAT/PMT/SPS/PPS/IFrame и чанки нарезаются по критерию быстрого старта проигрывателей. Анализ начинается с *min interval*, и если по какой-то причине данные не найдены, то чанк принудительно нарезается по *max interval*.

**GOP-aligned segments**

В режиме *OTT/HLS* сегментер выравнивает границы чанков по точкам случайного доступа и различает *IDR* и обычный *I-frame*. Если источник вещает с *closed-GOP* (есть IDR), каждый TS-сегмент *.ts* гарантированно начинается с *SPS / PPS / IDR* (для HEVC — также *VPS*) — настоящей точки входа, с которой плеер открывает поток. Если источник *open-GOP* / без IDR, границей служит ближайший I-frame (прежнее поведение). Сегментер вырезает «хвост» предыдущего GOP — *P / B*-слайсы — из окна между лидирующим PAT и первым SPS чанка. Это:

- устраняет начальный чёрный кадр на старте VOD-сессий (`?t=` / `?epg=`) в *hls.js*, *Safari* и *VLC*: декодер MSE получает корректный заголовочный набор NAL-юнитов уже в первом сегменте и стартует немедленно;
- приводит выдачу в соответствие *HLS RFC 8216 §3* («Each Media Segment MUST contain a SPS and a PPS that decode its first Access Unit»);
- делает корректным заявление *EXT-X-INDEPENDENT-SEGMENTS* в *EXT-X-VERSION:6* плейлисте.

Управление — настройка стрима *gop-aligned-segment* (по умолчанию *true*). Применяется только при *HLS = OTT/HLS*: в режиме *Peering/HLS* и для MPTS-стримов поведение не меняется. PSI (PAT / PMT) и аудио сохраняются в полном объёме; единственный побочный эффект — однокадровый разрыв *continuity_counter* на видео-PID на границе двух соседних чанков, который для HLS / DASH MSE является легитимной точкой decode boundary.

При выравнивании по точкам входа фактическая длительность чанка может округляться вверх за пределы *Chunk Min Interval*. Поэтому в live-плейлисте значение *EXT-X-TARGETDURATION* отражает **максимальную фактическую** длительность сегмента (раздел 4.3.3.1 *RFC 8216*), а не заданный минимум. Это удерживает манифест в рамках стандарта: *hls.js* и совместимые плееры не сокращают интервал обновления плейлиста и не выдают ложных *bufferStalledError*.

**Low-Latency HLS (/llhls)**

В режиме *OTT/HLS/LL-HLS/LL-Dash* помимо `/dash` доступен отдельный endpoint **Low-Latency HLS** — выдача с пониженной задержкой на тех же fMP4-сегментах CMAF:

- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

Медиаплейлист дробится на *частичные сегменты* (*parts*, директивы `#EXT-X-PART`): плеер начинает воспроизведение, не дожидаясь готовности полного сегмента. Применяются блокирующая перезагрузка плейлиста (сервер удерживает запрос до готовности следующего part) и подсказка предзагрузки `#EXT-X-PRELOAD-HINT`.

Целевая длительность part задаётся настройкой стрима *Part Target Duration* (мс; по умолчанию 500, диапазон 100–5000). Значение применяется на лету, без рестарта потока, и должно быть меньше *Chunk Min Interval*.

**HLS / DASH / LL-HLS Adaptive Multistream**

С версии 1.10 поддерживается HLS Adaptive Multistream, с версии 1.12 — DASH Adaptive Multistream (с версии 1.13 — на CMAF), с версии 1.13 — Low-Latency HLS Adaptive.

Для адаптивных потоков настраивается отдельный плейлист. Для этого надо:

- Включить OTT-выдачу у стримов, которые войдут в адаптивный плейлист: *OTT/HLS* — для адаптивного **HLS** на MPEG-TS; *OTT/HLS/LL-HLS/LL-Dash* — для адаптивных **DASH** / **Low-Latency HLS** на CMAF.
- В главном меню появится раздел адаптивных потоков. В нём надо добавить поток и прописать все потоки, которые должны входить в этот плейлист.
- У потоков может быть задан параметр битрейт. По умолчанию он 0 — битрейт берётся от измеренного значения; иначе его можно задать явно.

Для адаптивных плейлистов будет другой URL:

- [http://host:port/hls/adaptive/stream/login/password](http://host:port/hls/adaptive/stream/login/password)
- [http://host:port/hls/adaptive/stream/login](http://host:port/hls/adaptive/stream/login)
- [http://host:port/hls/adaptive/stream/](http://host:port/hls/adaptive/stream/)
- [http://host:port/dash/adaptive/stream/login/password](http://host:port/dash/adaptive/stream/login/password)
- [http://host:port/dash/adaptive/stream/login](http://host:port/dash/adaptive/stream/login)
- [http://host:port/dash/adaptive/stream/](http://host:port/dash/adaptive/stream/)
- [http://host:port/llhls/adaptive/stream/login/password](http://host:port/llhls/adaptive/stream/login/password)
- [http://host:port/llhls/adaptive/stream/login](http://host:port/llhls/adaptive/stream/login)
- [http://host:port/llhls/adaptive/stream/](http://host:port/llhls/adaptive/stream/)

У пиров (клиентов) может быть назначено ограничение доступа к адаптивным потокам, так же, как и к обычным. Разрешение для адаптивного потока включает разрешение ко всем потокам, которые входят в него.

# HTTP/3 (QUIC)

С версии 1.13.1.438 в **Perfect Streamer** реализован встроенный сервер **HTTP/3** для OTT-доставки HLS, MPEG-DASH и Low-Latency HLS поверх **QUIC** (RFC 9000 + RFC 9114). Стек — **ngtcp2** (QUIC v1) + **nghttp3** (HTTP/3 frame layer); TLS-инфраструктура переиспользует тот же сертификат, что и HTTPS-listener.

QUIC обслуживает только OTT-маршруты:

- `/` — root-redirect,
- `/hls/...`, `/dash/...` и `/llhls/...` — master playlists / MPD,
- `/h<sessID>/...` — per-session URL (media playlists, сегменты, VTT),
- `/http/<stream>/...` — raw MPEG-TS over HTTP.

Low-Latency HLS и DASH отдаются поверх QUIC инкрементально (chunked): *parts* уходят клиенту по мере готовности, без ожидания полного сегмента.

Административные пути (`/data`, `/config`, `/xmltv`, `/db/`, `/login`, `/logout`, `/restart`) на QUIC отвечают **404** — admin-API остаётся на HTTPS/HTTP-TCP. Это сознательное ограничение для уменьшения поверхности атаки QUIC-listener’а.

## Включение

Настройки QUIC живут в разделе **Configuration / HTTP server** (узел `/config/http-server`):

- **HTTP/3 Enable** (`http3-enable`) — глобальный флаг включения QUIC-listener’а. По умолчанию **off**. Включение поднимает UDP-сокет на `http3-port`; выключение — закрывает его.
- **HTTP/3 Port** (`http3-port`) — UDP-порт QUIC-listener’а. По умолчанию **43984**. QUIC живёт на UDP, HTTPS — на TCP; порты могут совпадать или отличаться, конфликта между ними нет.
- **HTTP/3 0-RTT Enable** (`http3-zero-rtt-enable`) — разрешение 0-RTT-handshake’а (RFC 9001 §4.6.1) при повторном соединении того же клиента. Снижает задержку старта; нужно учитывать replay-риск для не-идемпотентных запросов (для read-only OTT-нагрузки это безопасно). По умолчанию **on**.

Изменения настроек применяются «горячо», без рестарта сервиса.

Сертификат и ключ берутся из HTTPS-listener’а — отдельной настройки сертификата для HTTP/3 нет. Если HTTPS не сконфигурирован (`ssl-enable=false`), включение HTTP/3 ничего не отдаёт — handshake не пройдёт.

## Параметр `?h3` — opt-in на сеанс

Браузер не подключается к HTTP/3 endpoint напрямую — он сначала идёт на HTTPS/TCP, получает в ответе заголовок `Alt-Svc: h3=":<port>"; ma=86400` (RFC 7838), и только следующие запросы к этому origin переключает на QUIC.

В Perfect Streamer выдача `Alt-Svc` — **opt-in per OTT-сессия**. Сервер не анонсирует QUIC всем клиентам подряд: клиент должен явно попросить про HTTP/3 query-параметром `?h3` на master HLS / DASH URL.

| Значение в URL | Значение opt-in | `Alt-Svc` в ответе |
| --- | --- | --- |
| параметр отсутствует | off (default) | нет |
| `?h3` (без значения) | on | есть |
| `?h3=1`, `?h3=on`, `?h3=yes`, `?h3=true` | on | есть |
| `?h3=0`, `?h3=off`, `?h3=no`, `?h3=false` | explicit off | нет (как при отсутствии) |

После того как клиент получил session-URL `/h<sess>/...`, признак `wantH3` сохраняется в сессии — все последующие refresh’ы media playlist, GET’ы сегментов и VTT-чанков под этим session-URL **также** получают `Alt-Svc`, даже если `?h3` нет в самом запросе. Без opt-in на master — sticky-поведение не возникает.

Гейтинг `Alt-Svc`:

1. Глобально включён QUIC-listener (`http3-enable=true`) — иначе анонс подавляется даже при `?h3`.
2. Запрос пришёл **не** по QUIC — на запросах, уже идущих по H3, `Alt-Svc` бессмысленен и не отдаётся.
3. Per-session или per-URL `wantH3=true` (см. таблицу выше).
4. Admin- и EPG-серверы `Alt-Svc` не отдают никогда — у них нет QUIC-listener’а.

Это поведение совместимо с RFC 7838 §3 — сервер сам решает, на каких ответах выдавать `Alt-Svc`.

## Сценарий перехода на QUIC в браузере

Типичный поток (Chrome / Firefox / Safari):

1. Плеер открывает master URL с явным opt-in:

   ```
   https://stream.example.com:41982/hls/test1/login/password/index.m3u8?h3=1
   ```
2. Первый запрос идёт по HTTPS/TCP. В ответе сервер выдаёт `Alt-Svc: h3=":43984"; ma=86400`.
3. Браузер запоминает сопоставление `stream.example.com:41982 → h3=":43984"`. В Chrome его можно увидеть на странице `chrome://net-internals/#alt-svc`; в Firefox — `about:networking#http3`.
4. На следующих запросах к тому же origin (refresh playlist, GET-запросы сегментов, VTT) браузер открывает QUIC-соединение на `udp/43984` и продолжает по HTTP/3. В DevTools → Network колонка **Protocol** на этих запросах становится `h3`.

Если QUIC-соединение не устанавливается (UDP-порт закрыт на firewall, сертификат не доверен в системе), браузер прозрачно остаётся на HTTPS/TCP — функционально стрим продолжает играть, без перерыва.

## Учёт клиентов и мониторинг

Реальный IP-адрес QUIC-клиента в учёте активных пиров (`/data/http-clients`, лимиты конкурентных подключений, ACL по IP) — это **настоящий peer-address** QUIC-соединения, а не loopback внутреннего backend-моста.

Атрибут `ott-type` в `/data/http-clients` — составной, формат `<PROTO>/<scheme>`:

- `PROTO` — OTT-протокол: `HLS`, `DASH` или `HTTP`.
- `scheme` — фактический сетевой транспорт: `http`, `https` или `quic`.

Примеры: `HLS/quic`, `DASH/https`, `HTTP/http`. Админ-UI видит и OTT-протокол, и сетевой транспорт каждого клиента в одной колонке.

Таймаут OTT-сессии — **60 секунд** независимо от транспорта. При переключении клиента между транспортами схема обновляется только «вверх» по приоритету: `http` → `https` → `quic`. Параллельное возвращение к менее защищённому соединению не «затирает» текущий тип — в учёте остаётся максимально безопасный.

## Совместимость плееров

HTTP/3 для OTT поддерживают:

- **iOS AVPlayer / Safari** — нативно, через `Alt-Svc`, есть 0-RTT.
- **Chrome, Firefox, Edge, Brave** — через `Alt-Svc`; HLS играется через hls.js, DASH — через dash.js / Shaka, трафик плеера наследует HTTP/3 от browser fetch API.
- **Android ExoPlayer** — через Cronet QUIC engine (не используемый по умолчанию transport, требует настройки клиента).

**VLC** (libVLC) не поддерживает HTTP/3 — на этих клиентах поток продолжит работать через HTTPS/TCP без выигрыша QUIC.

Реальный выигрыш QUIC vs HTTPS/TCP заметен в мобильных сетях / Wi-Fi / сетях с потерями:

- нет head-of-line blocking на TCP-уровне — потеря в одном HTTP-stream не задерживает остальные;
- 0-RTT handshake при повторном соединении того же клиента;
- стабильнее ABR-bitrate на потерях 1–5 %.

В управляемых сетях / корпоративном Wi-Fi выигрыш обычно скромный — 5–10 % по startup latency и почти нулевой по steady-state. CPU-нагрузка на сервере для QUIC выше, чем для kernel-TCP — это плата за user-space TLS+транспорт.

# Модель кеширования OTT HLS и DASH

Сервер формирует ответы трёх категорий, различающихся сроком жизни содержимого и пригодностью для кеширования промежуточными узлами (reverse proxy, CDN, клиентский кеш).

## 1. Модель кеширования

### 1.1. Ресурсы и HTTP-заголовки

| Ресурс | URL | Content-Type | Cache-Control |
| --- | --- | --- | --- |
| TS-сегмент (HLS) | `/h<sess>/<keyHex>.ts`, `/h<sess>/sub/<pid>/<keyHex>.vtt` | `video/mp2t` | `public, max-age=60, immutable` |
| fMP4-сегмент (DASH / LL-HLS) | `/h<sess>/init.mp4`, `/h<sess>/<key\|N>.m4s` | `video/mp4` | `public, max-age=60, immutable` |
| DASH MPD | `/h<sess>/index.mpd` | `application/dash+xml; charset=utf-8` | `public, max-age=1` |
| HLS master | `/hls/<stream>/<login>/<pass>/index.m3u8` | `application/vnd.apple.mpegurl` | `public, max-age=1` |
| HLS media | `/h<sess>/index.m3u8`, `/h<sess>/sub/<pid>/index.m3u8` | `application/vnd.apple.mpegurl` | `public, max-age=1` |
| 302 Redirect | `/dash/<stream>/<login>/<pass>/index.mpd` | — | `no-cache, no-store` |
| Raw TS | `/http/<stream>/<login>/<pass>` | `video/mp2t` | не задан; не кешируется |

### 1.2. Характеристики сегментов

Шестнадцатеричный идентификатор сегмента в URL (`<keyHex>` в путях `/h<sess>/<keyHex>.ts`) формируется как CRC64 от стартового времени сегмента и ID стрима и глобально уникален. URL сегмента адресует неизменное содержимое — при повторных запросах того же URL возвращается идентичный байтовый поток (пока сегмент остаётся в пределах скользящего окна).

Директива `immutable` подавляет условную ревалидацию клиентом (`If-None-Match`, `If-Modified-Since`). Значение `max-age=60` обеспечивает совместимость с типичным `timeShiftBufferDepth=40s`.

fMP4-сегменты CMAF (`.m4s`) и общий `init.mp4` для **DASH** / **Low-Latency HLS** адресуются аналогично и кешируются по той же модели (`immutable`, `max-age=60`). Частичные сегменты (*parts*) LL-HLS — это byte-range-запросы внутрь того же `.m4s`, поэтому отдельной записи в кеше не образуют.

### 1.3. Характеристики манифестов

`max-age=1` ограничивает верхнюю границу устаревания содержимого в кеше одной секундой. При совместном использовании с `proxy_cache_lock on` (nginx) всплески запросов к манифесту коалесцируются в один запрос на origin в секунду.

### 1.4. Вариативность содержимого

При `absPath=0` (значение по умолчанию, URL параметра `a` нет) манифесты HLS media и DASH MPD не содержат идентификатора сессии в теле. Содержимое манифеста идентично между сессиями, принадлежащими одной (stream, param)-комбинации. Это позволяет reverse-proxy кешу переиспользовать запись между сессиями при нормализации ключа кеша.

При `absPath=1` (URL параметр `a=1`) в теле манифеста присутствуют абсолютные URL, включающие схему, хост и идентификатор сессии. Содержимое становится специфичным для сессии, возможность кросс-сессионного переиспользования кеша отсутствует.

## 2. Поведение клиентов

| Клиент | URL обновления манифеста | Воздействие на количество сессий |
| --- | --- | --- |
| VLC 3.x HLS | `/h<sess>/index.m3u8` | Одна сессия на сеанс воспроизведения |
| VLC 3.x DASH | `/dash/<stream>/.../index.mpd` | Обрабатывается session reuse (см. 3.3) |
| ffmpeg 5.x HLS | `/h<sess>/index.m3u8` | Одна сессия на сеанс воспроизведения |
| ffmpeg 5.x DASH | `/dash/<stream>/.../index.mpd` (цикл повтора) | Обрабатывается session reuse (см. 3.3) |
| dash.js, hls.js | `/h<sess>/...` через `<Location>` / session URL | Одна сессия на сеанс воспроизведения |

## 3. Специальные механизмы

### 3.1. HTTP 302 Redirect для DASH

Запрос вида `/dash/<stream>/<login>/<pass>/index.mpd` возвращает ответ `302 Found` с заголовком `Location: /h<sess>/index.mpd`. Тело ответа пустое. Авторизация и выделение сессии выполняются на этапе обработки редиректа.

Клиенты, поддерживающие кеширование редиректа, обращаются к session URL напрямую в последующих запросах. Клиенты, не поддерживающие, повторяют запрос редиректа. Стоимость повторной обработки редиректа ограничена проверкой аутентификации и операциями session reuse.

### 3.2. Session reuse для DASH

При обработке запроса `/dash/.../index.mpd` от того же логина к тому же стриму (с тем же признаком *adaptive*) сервер находит уже существующую DASH-сессию и возвращает её идентификатор повторно. Новая сессия не создаётся, слот лимита одновременных подключений не расходуется.

Применяется только к DASH. Для HLS отдельный механизм reuse не требуется: HLS-клиенты обновляют media playlist по session-URL и не создают новую сессию на каждом refresh.

### 3.3. Переиспользование сегментов между сессиями

Путь `/h<sess>/<keyHex>.ts` не зависит от `<sess>` при разрешении `<keyHex>` в содержимое: `<keyHex>` глобально однозначно идентифицирует TS-сегмент в рамках стрима. Nginx с нормализованным cache key (обрезающим префикс `/h<sess>/`) обслуживает все запросы одного и того же `<keyHex>` из единственной записи кеша, независимо от того, какие клиенты их выдали.

То же справедливо для fMP4-сегментов CMAF (`.m4s`) и `init.mp4`: их содержимое неизменно и адресуется в рамках стрима, поэтому нормализация cache key даёт ту же кросс-сессионную дедупликацию в кеше.

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

| Параметр | Значение по умолчанию | Влияние |
| --- | --- | --- |
| `a` | `0` | `1` — абсолютные URL в манифестах; `0` — относительные |
| `s` | `40` | `timeShiftBufferDepth` в секундах |
| `m` | `40` | Минимальная длина окна для выдачи манифеста |
| `v` | `6` для *OTT/HLS* и *OTT/HLS/LL-HLS/LL-Dash*, `3` для *Peering/HLS* | `#EXT-X-VERSION` в HLS (игнорируется DASH); явное значение в URL переопределяет значение по умолчанию |
| `h3` | отсутствует (off) | opt-in на HTTP/3 (QUIC) — заставляет сервер выдать `Alt-Svc` в ответе. Опознаваемые значения: presence ⇒ on, `1`/`on`/`yes`/`true` ⇒ on, `0`/`off`/`no`/`false` ⇒ explicit off. Sticky на session-URL `/h<sess>/...`. См. раздел HTTP/3 (QUIC). |

Изменение параметра через query string обновляет сохранённые в сессии значения при её следующем переоткрытии.

## 5. Нагрузочные характеристики

Нагрузка на origin масштабируется с числом одновременно наблюдаемых различных стримов. Увеличение числа клиентов, наблюдающих один и тот же стрим, не увеличивает количество запросов к origin при наличии reverse-proxy кеша и нормализованного cache key.

| Сценарий | Частота origin-запросов (реф.) |
| --- | --- |
| 1 клиент на стрим X | MPD: 0.4 req/s, segment: 0.2 req/s |
| N клиентов на один стрим X (кеш включён) | MPD: 1 req/s, segment: 0.2 req/s |
| N ffmpeg-клиентов в режиме повтора на одном стриме | MPD: 1 req/s (с `proxy_cache_lock`) |
| N клиентов на N различных стримов | MPD: 0.4·N req/s, segment: 0.2·N req/s |

## 6. Nginx как кеширующий reverse proxy

### 6.1. Базовая конфигурация

```nginx
proxy_cache_path /var/cache/nginx/pss_segments
    levels=1:2 keys_zone=pss_segments:100m
    max_size=20g inactive=30m use_temp_path=off;

proxy_cache_path /var/cache/nginx/pss_manifests
    levels=1:2 keys_zone=pss_manifests:10m
    max_size=256m inactive=5m use_temp_path=off;

upstream pss_backend {
    server 127.0.0.1:41972;
    keepalive 64;
}

map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "stream:$tail";
    default                                     $uri;
}

server {
    listen 80;
    server_name stream.example.com;

    location ~* "^/h[0-9a-f]{16}(/[0-9]+)?/([0-9a-f]+\.(ts|m4s)|init\.mp4)$" {
        proxy_cache               pss_segments;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 60s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_use_stale     updating error timeout;
        proxy_cache_revalidate    on;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_buffering           on;
    }

    location ~* "(^/h[0-9a-f]{16}(/[0-9]+)?/index\.(m3u8|mpd)$|^/(hls|dash)/.*\.(m3u8|mpd)$)" {
        proxy_cache               pss_manifests;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 1s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_lock_timeout  2s;
        proxy_cache_use_stale     updating;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
    }

    location / {
        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_set_header          X-Forwarded-Proto $scheme;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_buffering           off;
        proxy_read_timeout        3600s;
    }
}
```

### 6.2. Назначение директив

| Директива | Назначение |
| --- | --- |
| `proxy_cache_lock on` | Сериализует выполнение upstream-запросов при одновременных cache miss по одному ключу |
| `proxy_cache_use_stale updating` | Возвращает устаревшую копию параллельным запросам в период обновления кеша |
| `proxy_cache_revalidate on` | Использует `If-Modified-Since` при cache miss с сохранённой копией |
| `proxy_cache_valid 404 403 0s` | Запрещает кеширование ошибок авторизации и 404 |
| `keepalive 64` в upstream | Поддерживает пул persistent-соединений к origin |
| `proxy_buffering on` | Для сегментов; включает буферизацию ответа в nginx |
| `proxy_buffering off` | Для раздела `/`; отключает буферизацию (raw streaming) |

### 6.3. Расчёт `max_size` кеша сегментов

Ориентировочное значение: `bitrate × timeShiftBufferDepth × distinct_streams × 2`

Пример: 10 стримов × 8 Mbps × 40s × 2 ≈ 800 MB. Рекомендуется задать запас 10x для учёта вариативности битрейта.

### 6.4. TLS-терминация

Сервер Perfect Streamer принимает соединения на портах HTTP и HTTPS. При TLS-терминации на nginx upstream использует порт HTTP. Пересылка заголовков `X-Forwarded-Proto` и `X-Forwarded-Host` обязательна для корректного формирования абсолютных URL при `absPath=1`.

```nginx
server {
    listen 443 ssl http2;
    server_name stream.example.com;

    ssl_certificate           /etc/letsencrypt/live/stream.example.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/stream.example.com/privkey.pem;
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_session_cache         shared:SSL:10m;
    ssl_session_timeout       1d;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location ... {
        proxy_pass                http://pss_backend;
        proxy_set_header          X-Forwarded-Proto https;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_set_header          Host              $host;
        # + caching directives from 6.1
    }
}

server {
    listen 80;
    server_name stream.example.com;
    return 301 https://$host$request_uri;
}
```

При HTTPS между nginx и origin применяются директивы `proxy_ssl_verify`, `proxy_ssl_trusted_certificate`. Для loopback-соединений шифрование избыточно.

### 6.5. Мультихост

При обслуживании нескольких `server_name` из одного nginx-процесса `$host` добавляется в cache key для изоляции контента:

```nginx
map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "$host:stream:$tail";
    default                                     "$host:$uri";
}
```

Размер `keys_zone` рассчитывается как 8000 ключей/MB. Для мультихост-инсталляций с тысячами стримов рекомендуется `keys_zone=...:300m` или выше.

## 7. Клиентское кеширование

`Cache-Control: immutable` обрабатывается браузерами Chrome/Firefox/Safari. Клиентский кеш возвращает сегмент без условного запроса при повторном обращении (в том числе при обратном seek в пределах буфера плеера).

Service Workers могут применять стратегию `cache-first` на основании содержимого Cache-Control. DASH-плееры (dash.js, Shaka) используют MSE через `SourceBuffer`; сегмент, помещённый в буфер, остаётся доступен без повторного HTTP-запроса до выхода за границу скольжения.

Для кросс-доменных запросов заголовок `Access-Control-Allow-Origin: *` позволяет кеширование в shared caches без `Vary: Origin`. При смене значения ACAO на конкретный Origin требуется `Vary: Origin`, что снижает эффективность shared cache.

## 8. Размещение через CDN

Perfect Streamer совместим с CDN в режиме pull-from-origin (Cloudflare, Akamai, Fastly, BunnyCDN, Amazon CloudFront).

**Origin shield.** Рекомендуется размещение одного или нескольких shield-узлов между CDN edge и origin для снижения частоты запросов на origin при глобальном распределении клиентов.

**Purge.** Content-addressed сегменты не требуют purge. При изменении метаданных стрима (кодек, разрешение) manifest’ы обновляются в течение `max-age=1` без явного purge.

**Cache warming.** При ожидаемом росте нагрузки на конкретный стрим допустим прогрев CDN с нескольких географических точек до начала трансляции.

**Геораспределение.** Сегменты (`max-age=60`) хорошо подходят для географически распределённого кеширования. Манифесты (`max-age=1`) допускают задержку доставки до одной секунды — приемлемо для non-low-latency live.

## 9. Мониторинг

### 9.1. `X-Cache-Status`

Добавление `add_header X-Cache-Status $upstream_cache_status;` в каждый location с кешированием. Значения:

| Значение | Описание |
| --- | --- |
| `HIT` | Ответ из кеша |
| `MISS` | В кеше отсутствовал, получен от origin и сохранён |
| `EXPIRED` | Просрочен, обновлён |
| `UPDATING` | Отдана stale-копия параллельному запросу во время обновления |
| `STALE` | `use_stale` вернул просроченную копию (origin недоступен) |
| `REVALIDATED` | Origin вернул 304 Not Modified |
| `BYPASS` | Сработал `proxy_cache_bypass` |

### 9.2. Формат access-log

```nginx
log_format pss_cache '$remote_addr $status $request_method "$request" '
                     '$body_bytes_sent rt=$request_time ut=$upstream_response_time '
                     'cache=$upstream_cache_status key=$pss_cache_key';

server {
    access_log /var/log/nginx/pss.log pss_cache;
}
```

### 9.3. Метрики

Модуль `nginx-vts` экспортирует метрики per-zone в формате Prometheus:

```
GET /status/format/prometheus
```

Рекомендуемые пороги для алертов:

| Метрика | Порог | Возможная причина |
| --- | --- | --- |
| Segment HIT rate | < 90% за 5 минут | Нарушена нормализация cache key; малый `max_size` |
| Manifest MISS rate | > 50% за 1 минуту | `proxy_cache_lock` не сериализует запросы |
| Upstream response time p95 | > 500 мс за 1 минуту | Перегрузка origin |
| Cache zone fill | > 90% за 10 минут | Приближение к `max_size`, планируется LRU-eviction |

## 10. Диагностика

| Симптом | Вероятная причина | Решение |
| --- | --- | --- |
| Segment HIT rate низкий | `Vary: Origin` с высокой вариативностью Origin; нарушение normalisation в `map` | Проверить заголовки и regex в директиве `map` |
| 404 на сегментах после выхода из окна | Закешированный 404 при сегменте, выпавшем из sliding window | Добавить `proxy_cache_valid 404 0s` в location segments |
| Задержка playback start 2–5 с | `proxy_cache_lock_timeout` превышает target latency | Снизить до 1–2 с; включить `proxy_cache_use_stale updating` |
| Manifest не обновляется | `proxy_cache_valid` переопределяет `max-age` | Явно указать `proxy_cache_valid 200 1s` |
| Рост TIME_WAIT на upstream | Отсутствует `keepalive` в upstream-блоке | Добавить `keepalive 64`, `proxy_http_version 1.1`, `proxy_set_header Connection ""` |
| 403 на `/dash/.../<segment>.m4s` от ffmpeg | Клиент резолвит относительные URL от pre-redirect URL | Сервер эмитит `<BaseURL>/h<sess>/</BaseURL>` (absolute path); совместимо в текущем билде |
| Лаги, частые ребуферы у удалённых клиентов | Низкий эффективный throughput TCP из-за slow start и idle restart при больших RTT (300 мс и выше) | Тюнинг сетевого стека Linux на origin: см. 10.1 |

### 10.1. TCP-тюнинг origin для high-RTT клиентов

Проблема проявляется у клиентов с большим RTT до origin (например, 300 мс и выше) при битрейте стрима, близком к пропускной способности канала. Симптомы в плеере (VLC, ffmpeg, dash.js) — частые ребуферы, warnings вида `ES_OUT_SET_PCR called too late` (увеличение `pts_delay`), `buffer deadlock prevented`, разрывы потока. На сервере клиент при этом нормальный, ошибок нет, throughput по `/data/stream/...` соответствует входному потоку.

Причина:

- **TCP slow start.** Каждое новое TCP-соединение начинает с congestion window около 14 КБ и наращивает его за несколько RTT. При RTT 300 мс выход на полное окно занимает 2–3 секунды. За это время HLS/DASH-сегмент длительностью 5 с (4–6 МБ) скачивается заметно медленнее реального времени.
- **TCP idle restart.** Между запросами сегментов клиент по HLS pull-модели делает паузу 4–5 с. По умолчанию ядро Linux после такой паузы сбрасывает congestion window соединения обратно к initial cwnd (поведение `net.ipv4.tcp_slow_start_after_idle=1`). В результате при следующем GET keep-alive-соединение начинает передачу с медленного start заново — даже на уже разогретой сессии.

Дополнительное усугубление — congestion control CUBIC по умолчанию плохо переносит длинные RTT и пакетные потери на промежуточных участках сети.

Решение — два sysctl-параметра на origin:

```bash
# Keep congestion window across idle pauses inside keep-alive sessions.
sysctl -w net.ipv4.tcp_slow_start_after_idle=0

# Use BBR instead of CUBIC: better behaviour on long-RTT paths
# with mild packet loss; paces sending instead of bursting.
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr
```

Для постоянного применения:

```bash
cat > /etc/sysctl.d/99-pss-net.conf <<EOF
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_congestion_control    = bbr
EOF
sysctl --system
```

Главный эффект даёт первый параметр (`tcp_slow_start_after_idle=0`). Он напрямую устраняет повторный slow start между сегментными запросами в одном keep-alive соединении. Второй (BBR) даёт дополнительную устойчивость и применяется ко всем новым соединениям.

Тюнинг не требует перезапуска Perfect Streamer и применяется ко всем новым TCP-соединениям сразу после `sysctl -w`. Существующие соединения сохраняют congestion control, с которым были установлены.

## 11. Безопасность

### 11.1. Session URL

URL формата `/h<sess>/...` выполняет функцию сессионного токена — не требует повторной аутентификации. Срок жизни ограничен idle timeout (значение 30 с). При отсутствии активности сессия удаляется cleaner-задачей.

Требования:

- HTTPS для всех OTT-путей (`/hls/`, `/dash/`, `/h<sess>/`) в production
- Session ID в `Location` заголовке 302 не кешируется (`no-cache, no-store`)

### 11.2. Rate limiting

```nginx
limit_req_zone $binary_remote_addr zone=dash_top:10m  rate=5r/s;
limit_req_zone $binary_remote_addr zone=hls_top:10m   rate=5r/s;
limit_req_zone $binary_remote_addr zone=llhls_top:10m rate=5r/s;

server {
    location /dash/ {
        limit_req zone=dash_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /hls/ {
        limit_req zone=hls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /llhls/ {
        limit_req zone=llhls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
}
```

Session URL (`/h<sess>/`) не требует rate limiting — обработка дешёвая, ответы кешируются.

### 11.3. Кеширование ответов с ошибками

```nginx
proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;
```

Запрещает кеширование редиректов (уникальный sess в Location) и ответов с ошибками авторизации или отсутствия ресурса.

### 11.4. Ограничение сетевого доступа к origin

Порт 41972 (41982 для HTTPS) должен быть закрыт для внешнего трафика. Допустимые конфигурации:

1. Bind Perfect Streamer на `127.0.0.1` (при локальном nginx)
2. Firewall-правило:

```
iptables -A INPUT -p tcp --dport 41972 ! -s 10.0.0.0/8 -j DROP
```

## 12. Интеграция с middleware

### 12.1. Модель prefix-login

Perfect Streamer поддерживает делегирование идентификации пользователя middleware/биллинг-системе через механизм prefix-login. Внешний коннектор к биллинг-системе не включён в текущий релиз.

Конфигурация embedded-пользователя:

```
{
  "id": 9,
  "login": "sub",
  "password": "xxx",
  "is-prefix": true,
  "max-conn-http-hls": 1,
  "accept-stream": [ ... ]
}
```

При `is-prefix: true` сервер принимает URL с логином вида `<prefix><billing_user_id>`:

```
/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8
```

### 12.2. Формат статистики

```
<clients>
  <client login-id="-1974387287" login="sub" match-login="sub42"
          sess-id="11331..." ott-type="dash" stream-id="10000" .../>
  <client login-id="-2147031294" login="sub" match-login="sub43"
          sess-id="11132..." ott-type="dash" stream-id="10000" .../>
</clients>
```

Поле `login-id` содержит хеш URL-логина. Поле `login` — конфигурационное значение. Поле `match-login` — URL-логин, использованный клиентом.

### 12.3. Ограничения prefix-login

- **Общий пароль.** Все подписчики prefix-пула используют одно значение пароля. Компрометация пароля предоставляет доступ к любому `<prefix><string>`.
- **Гранулярность ACL.** `accept-stream` применяется ко всему prefix-пулу. Per-subscriber ACL недоступен без внешнего биллинга.
- **Ротация пароля.** Изменение пароля отключает всех активных подписчиков. Для постепенной замены требуется временное использование двух prefix-логинов.

## 13. WebVTT субтитры

Источник субтитров — DVB Teletext / DVB Subtitling из входного MPEG-TS. В разделах **Media Information** или **Original Media Information** должны присутствовать дорожки Teletext subtitles. Также в разделе **Analyzer** можно убедиться, что пакеты соответствующих PID активны.

Для OTT HLS/DASH должен быть включён режим OTT (в режиме *Peering/HLS* WebVTT субтитры недоступны). В разделе **Output # OTT** должен появиться ненулевой счётчик чанков **OTT WebVTT buffer chunk count**.

Для диагностики субтитров включить у стрима **Analyze** и **Trace**. При старте потока в логе стрима должно появиться:

```
Start Teletext subtitle decoder
[ttxsubdec] ttx: pid=331 magazine=8 page=0x88 lang=***
```

Далее в лог пишется декодированный текст субтитров.

### 13.1. URL VTT-сегментов

| Схема | URL | Содержимое |
| --- | --- | --- |
| HLS master | `/hls/.../index.m3u8` | `#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",...,URI="/h<sess>/sub/<pid>/index.m3u8"` |
| HLS subtitle playlist | `/h<sess>/sub/<pid>/index.m3u8` | список `<keyHex>.vtt` с `#EXTINF` |
| HLS VTT-сегмент | `/h<sess>/sub/<pid>/<keyHex>.vtt` | VTT с HLS-flavored X-TIMESTAMP-MAP |
| DASH MPD AdaptationSet | в `index.mpd` | `contentType="text" mimeType="text/vtt"` + `<SegmentTemplate media="$Number$.vtt">` |
| DASH VTT-сегмент | `/h<sess>/sub/<pid>/<seq>.vtt` | VTT с DASH-flavored X-TIMESTAMP-MAP |

`<keyHex>` — 16-символьный hex CRC64 от стартового времени сегмента, ID стрима и PID субтитровой дорожки. `<seq>` — десятичный порядковый номер чанка субтитрового потока (нумерация субтитров не связана с нумерацией TS-чанков).
