DVR / Архив

С версии 1.13 в Perfect Streamer доступен встроенный DVR — постоянный архив стрима на диске, который работает параллельно с обычной OTT-выдачей (HLS / DASH). Архив пишется автоматически, без отдельного процесса, и воспроизводится через те же URL OTT, что и live-вещание — отличие только в query-параметре.

Возможности:

  • Запись каждого OTT-стрима в архив на выбранном хранилище.

  • HLS, DASH и Low-Latency HLS воспроизведение архива (VOD) на тех же URL, что и live (DASH и LL-HLS — на CMAF, в режиме OTT/HLS/LL-HLS/LL-Dash).

  • Поддержка субтитров (WebVTT) — пишутся вместе с TS-чанками.

  • Несколько хранилищ — стрим привязывается к одному, разные стримы могут писать на разные диски.

  • Автоматическая очистка по времени удержания и по заполнению диска.

  • EPG-aligned VOD — выдача архива по указанию EPG-события.

  • Адаптивный VOD — поддерживается для адаптивных групп.

DVR не требует отдельной лицензии. Включается на уровне стрима добавлением привязки к хранилищу.

DVR не заменяет live-вещание. Если у стрима есть архив — клиент получает live-плейлист с тем же поведением, что и без DVR. Архив начинает воспроизводиться только тогда, когда клиент явно запросил VOD-режим через query-параметр URL (см. ниже).

Конфигурация хранилища

Хранилище — это запись в разделе Configuration / DVR Storage. Каждая запись описывает одну директорию на диске, в которую PSS пишет файлы архива. Стрим использует одно хранилище.

При добавлении хранилища настраиваются:

Name — отображаемое имя.

Dir Path — путь к директории на диске. После создания записи путь изменить нельзя — чтобы перенести архив на другой диск, нужно удалить запись и добавить новую с новым путём. Существующие файлы при удалении записи на диске не трогаются.

Max Usage, % — порог заполнения диска (по умолчанию 90 %). При превышении начинает работать size-based очистка (см. ниже). Минимум 1 %, максимум 100 %.

Cleanup Interval, сек — период работы задачи очистки (по умолчанию 10 сек). На каждом тике сначала срезается всё старше глубины хранения стрима, затем — при превышении Max Usage — старые чанки.

Disk Pressure Grace, сек — сколько секунд Used % должен непрерывно превышать Max Usage, прежде чем начнётся Size-based cleanup (по умолчанию 60 сек). Фильтрует короткие пики.

Disk Pressure Cut, сек — верхний предел для одного тика очистки: сколько секунд видео на один стрим может быть удалено за раз (по умолчанию 300 сек). Остальное переносится на следующий тик.

Disk Emergency Bytes — порог свободного места, ниже которого хранилище переходит в состояние Error и запись останавливается (по умолчанию 2 ГиБ). Автоматическое восстановление — при свободном месте ≥ 2 × этого значения.

Alarm Disk-Full Hysteresis, % — запас ниже Max Usage, до которого size-based очистка опускает заполнение, чтобы Used % не колебался у самого порога (по умолчанию 2 %).

Большинство значений по умолчанию подходят для типовых установок; коррекция требуется обычно только для Max Usage и Dir Path.

На один диск имеет смысл создавать одно хранилище. Если на одном диске указать несколько записей с разными подкаталогами, они будут конкурировать за свободное место — диск общий, а очистка у каждого хранилища своя.

Привязка стрима к хранилищу

В настройках Stream / OTT появляется секция DVR:

Storage — выпадающий список доступных хранилищ; 0 означает «архив выключен для этого стрима».

Storage Hours — глубина архива для этого стрима в часах, от 1 до 2160 (до 90 суток). Чанки старше этого значения удаляются на каждом тике задачи очистки (Rolling cleanup).

Storage Min Hour — нижний порог защиты (часы). Задача очистки никогда не удаляет чанки моложе этого значения, даже под давлением Max Usage. Используйте, если бизнес-логика требует гарантированной свежей записи, например «последние 2 часа всегда есть».

Изменение Storage на лету:

  • выставление 0 — отключает архив; чанки на диске сохраняются, новые не пишутся. VOD-сессии по этому стриму начинают отвечать 404;

  • выбор другого хранилища — стрим отвязывается от старого и начинает писать в новое. Файлы со старого диска не переносятся.

Изменение Storage Hours и Storage Min Hour применяется мгновенно — на следующем тике задача очистки увидит новое значение.

После настройки стрим начинает писать архив автоматически, как только перейдёт в состояние Running.

VOD: воспроизведение архива

Архив воспроизводится через те же URL, что и live-вещание HLS / DASH (см. раздел OTT сервис) — отличается только query-параметр.

URL и параметры

URL для HLS, DASH и Low-Latency HLS (DASH и LL-HLS — на CMAF, требуют режима OTT/HLS/LL-HLS/LL-Dash):

Без query-параметров — обычный live с скользящим окном. При наличии параметра t — VOD режим.

Параметр

Назначение

t=<epoch>

Время начала VOD (Unix epoch, сек). t=0 — от начала архива. Наличие t (даже t=0) включает VOD-режим.

d=<sec>

Длительность VOD-окна, сек. d=0 или параметр отсутствует — «до текущего момента». Имеет смысл только вместе с t.

epg=<epoch>

EPG-aligned VOD: сервер сам находит EPG-событие, активное в указанный момент, и берёт его start и duration как границы окна. Несовместим с t и d (server-side замена). См. ниже.

a, s, m, v

Стандартные параметры live (см. OTT сервис); s и m в VOD-режиме игнорируются.

Поведение по t и d:

t

d

Окно

Условие 404

нет

—

live (скользящее окно)

—

0

нет / 0

[начало архива, сейчас]

DVR не привязан

0

> 0

[начало архива, +d]

DVR не привязан

> 0

нет / 0

[t, сейчас]

DVR не привязан

> 0

> 0

[t, t+d]

DVR не привязан

Нормализация границ:

  • t раньше начала архива — start автоматически подтягивается к первому доступному чанку (cleanup мог уже срезать). Это не 404, просто покажем то, что осталось.

  • t в будущем или окно полностью раньше архива — пустой, но валидный плейлист (HLS: только header + EXT-X-ENDLIST; DASH: @type="static", mediaPresentationDuration="PT0S").

  • Чанки выбираются строго по моменту начала чанка, попадающему в полусегмент [t, t+d) — частичных сегментов нет.

VOD на стриме без архива (или у которого хранилище в Error) — 404 сразу на master playlist / MPD. Сессия не создаётся.

Текст ошибок в теле 404 (видны в логах сервера и в HTTP body):

  • VOD: stream not running — стрим есть в конфиге, но не в Running.

  • VOD: no DVR archive — у стрима не задано хранилище или хранилище в Error.

  • VOD: DVR detached — стрим был отвязан от хранилища между запросами.

  • VOD: EPG event not found — для ?epg= событие не найдено.

VOD HLS плейлист — закрытый (плеер видит длительность и может перематывать):

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:5.000,
...
#EXT-X-ENDLIST

В live-плейлисте этих маркеров нет — это и есть единственное отличие.

VOD DASH MPD — статический: @type="static", фиксированный mediaPresentationDuration, явные <SegmentURL>. Live-DASH остаётся @type="dynamic".

Если в выбранном интервале есть разрывы в архиве (например, был перезапуск записи или cleanup срезал часть посередине), DASH MPD автоматически разбивается на несколько <Period> — по одному на каждую сплошную дорожку. Плееры (VLC, dashjs, Shaka) перематывают через границу периода без специальных настроек.

EPG-aligned VOD

Если стрим связан с EPG-источником (поля EPG Source и EPG Channel в настройках стрима), клиент может запросить архив по моменту времени, попадающему в EPG-событие:

Сервер находит EPG-событие, активное в указанный epoch, и сам подставляет его start и duration как границы VOD-окна. Это удобно для каталога передач: UI знает время события, но не обязан вычислять точные сек./мс. границы.

Если стрим к EPG не привязан или в этот момент нет события — 404 ``VOD: EPG event not found``. Параметры t и d при наличии epg игнорируются.

Адаптивные потоки

Адаптивные группы (см. HLS Adaptive Multistream) поддерживают те же VOD-параметры:

В master playlist (HLS) попадают только те варианты, у которых задано хранилище DVR. Варианты без DVR пропускаются (в логе появляется VOD: variant N has no DVR), сборка из оставшихся вариантов работает.

В DASH-варианте каждое качество становится отдельным <Representation> внутри общих <Period>; плеер может переключаться между качествами без переоткрытия манифеста.

Поведение проигрывателя

VOD HLS / DASH с архива играют стандартные плееры: VLC, hls.js, dashjs, Shaka, ffmpeg. Перемотка по таймлайну работает.

Если плеер запросил сегмент, который к этому моменту уже удалил cleanup, сервер вернёт 404 на этот сегмент (не 500). VLC, hls.js, dashjs пропускают такой сегмент и продолжают воспроизведение со следующего.

Дополнительные возможности:

  • Защита активных сессий: пока VOD-сессия открыта, cleanup не удаляет чанки в её окне (см. ниже).

  • Live-edge bridge: если клиент в VOD-сессии запросил сегмент за правой границей VOD-окна — например, при достижении конца архива пошёл вперёд по таймлайну — сервер автоматически отдаёт сегмент из live-памяти. Никаких переадресаций и повторной авторизации.

  • Кеш плейлиста: на повторные запросы того же VOD index.m3u8 / index.mpd сервер отдаёт байт в байт идентичный ответ — без повторного построения. Подходит для CDN перед PSS.

Субтитры в архиве

Если у стрима в потоке есть субтитры (DVB Subtitling, Teletext или WebVTT) и включена опция OTT WebVTT в настройках стрима, субтитры архивируются параллельно с TS-чанками — в файлы .vtt рядом с .ts.

В live-режиме master playlist содержит EXT-X-MEDIA:TYPE=SUBTITLES; в VOD-режиме сервер отдаёт VOD-плейлист субтитров (с ENDLIST) и .vtt сегменты на тех же URL.

Особенности:

  • HLS: сохраняется заголовок X-TIMESTAMP-MAP в начале каждого .vtt (обязательно по спецификации HLS).

  • DASH: заголовок X-TIMESTAMP-MAP удаляется на лету (он привязан к абсолютному PCR и конфликтует с DASH-якорем; иначе VLC показывает пустые субтитры).

  • В адаптивной группе: если активный sub-stream не пишет субтитры — VTT-сегменты вернут 404. На следующем варианте плеер может снова получить субтитры.

Отключить субтитры можно либо опцией OTT WebVTT в стриме, либо вообще не включая HLS в режиме OTT/HLS.

Очистка и retention

PSS использует четыре стратегии очистки, работающие на каждом тике задачи очистки (по умолчанию каждые 10 сек):

  1. Rolling cleanup (per-stream): для каждого стрима удаляются чанки старше Storage Hours. Работает всегда, даже если диск полупустой.

  2. Size-based cleanup (общий по хранилищу): когда Used % на диске непрерывно превышает Max Usage в течение Disk Pressure Grace секунд, начинают срезаться самые старые чанки пропорционально по всем стримам, привязанным к хранилищу. Очистка идёт порциями по Disk Pressure Cut сек видео на стрим за тик. Никогда не удаляет чанки моложе Storage Min Hour.

  3. Emergency disk-full cut: когда свободное место упало ниже Disk Emergency Bytes, очистка работает агрессивно, может удалять даже защищённые сессией чанки. Запись останавливается до тех пор, пока свободное место не восстановится с гистерезисом × 2.

  4. Orphan scan: на диске могут остаться файлы, не учтённые в индексе (после внезапной остановки PSS). Сборщик проходит по подкаталогам стримов и удаляет такие «забытые» файлы — первый прогон вскоре после старта сервиса, далее раз в час и при срабатывании disk pressure. Защита от гонки с записью — пропускаются файлы моложе 60 сек.

Замечание. Задачи очистки запускаются в фоне; пользователю обычно не нужно ничего делать. Если архив растёт быстрее, чем срезается — снижайте Storage Hours на стримах или повышайте Disk Pressure Cut.

Защита активных VOD-сессий

Когда клиент открывает VOD-сессию, на каждом хранилище, участвующем в её окне, регистрируется «защитный слот» с моментом начала окна. Rolling и size-based очистка не трогают чанки в окне открытой сессии. Слот снимается автоматически, когда сессия закрывается (FIN, таймаут).

Это значит:

  • Если клиент держит VOD-сессию долго, он гарантированно может перемотать на любой момент внутри своего окна — чанки не «исчезнут под ним».

  • Очистка по Max Usage может временно не доводить Used % до желаемого порога, пока сессия активна; как только клиент уйдёт — очистка догонит.

  • Emergency disk-full cut и Storage Min Hour обходят защиту: если диск близок к нулю свободного места, чанки удаляются, и клиент получит 404 на затронутые сегменты (плеер пропустит).

  • После перезапуска PSS защитные слоты исчезают — очистка возобновляется сразу.

Несколько хранилищ

Можно создать любое количество записей DVR Storage — каждая для своей директории / своего диска. Стримы привязываются к разным хранилищам независимо. Очистка и пороги (Max Usage, Disk Pressure) работают per-storage.

Сценарии применения:

  • Разделение по уровням ценности: хранилище на быстрой SSD для премиум-каналов с большой глубиной, хранилище на ёмком HDD для остальных.

  • Отдельный диск под архив: чтобы запись DVR не конкурировала с системным диском или с временными файлами.

  • Разделение проектов: один диск под комплект каналов №1, другой под №2 — упрощает миграцию и аудит.

Изменение привязки стрима (поле Storage в Stream / OTT) переключает запись на лету. Старые файлы остаются на прежнем диске нетронутыми — их можно либо удалить вручную, либо снова подключить запись, переставив стрим обратно.

Состояние хранилища и мониторинг

В разделе Data / DVR Storage List (а также через API GET /data/dvr-storage-list) отображаются по каждому хранилищу:

  • State — Ready / Error (плюс промежуточное Not Ready у только что добавленного хранилища).

  • Total / Free / Used Bytes — свободное и занятое место на диске.

  • Used % — текущий процент заполнения.

  • Archived Bytes — суммарный размер чанков, учтённых в индексе всех привязанных стримов (без orphan-файлов).

  • Pressure Since Sec — момент (epoch), когда Used % впервые превысил Max Usage в текущем эпизоде давления; 0 означает «давления нет сейчас».

  • Active Task — фоновая операция обслуживания, идущая прямо сейчас: gc-orphans (удаление осиротевших файлов), disk-pressure-trim (срезание под давлением диска) или none, и сколько секунд она уже выполняется. Кратковременные (< 2 сек) операции в UI не мерцают.

  • Last cleanup — сводка по последним прогонам очисток: сколько времени назад был предыдущий сбор осиротевших файлов (и сколько их удалено) и последнее срезание под давлением диска (сколько места освобождено).

  • Attached streams — список привязанных стримов; по каждому показаны имя, признак идущей записи (active), заданная глубина архива (retention-hours) и фактическое наполнение (archived-sec и archived-bytes). Сумма archived-bytes по стримам равна Archived Bytes всего хранилища.

Состояния:

  • Ready — каталог доступен, запись и очистка идут штатным образом.

  • Error — каталог хранилища недоступен (размонтирован, нет прав, ошибка файловой системы) либо на диске критически мало места; запись остановлена. Восстановление автоматическое — как только каталог снова доступен и появляется достаточно свободного места.

Если хранилище ушло в Error, проверьте, смонтирован ли каталог архива и есть ли на диске свободное место — PSS сам не выходит из этого состояния, пока проблема не устранена физически.

Заполненность архива во времени:

  • На уровне стрима (Data / Stream) доступна метрика storage-gap-percent — доля пропусков по времени в накопленном архиве: 0 % — запись сплошная, выше — в архиве есть разрывы (перезапуски источника, срезанные очисткой участки).

  • Endpoint GET /data/dvrstat отдаёт гистограмму заполнения архива по интервалам времени с разметкой событий записи (старт/останов источника, смена PMT, переключения скремблирования, перестройка индекса, прогон очистки) и активности субтитров — для отрисовки шкалы DVR-архива в админ-интерфейсе.

Защита от случайных потерь данных

Несколько операций админ-интерфейса приводят к потере записанного архива или к прекращению выдачи VOD. Перед их выполнением админский UI показывает модальное подтверждение:

  • Удаление DVR Storage — все привязанные стримы теряют доступ к VOD; файлы на диске остаются, но без записи о хранилище они недостижимы через PSS.

  • Переключение стрима на другое хранилище — VOD по старому архиву перестаёт работать.

  • Отвязка стрима от хранилища (Storage = 0) — то же самое.

  • Уменьшение Max Usage — может спровоцировать size-based очистку и удалить старые чанки.

  • Уменьшение Storage Hours / Storage Min Hour — может вывести часть архива за rolling-окно.

Это сознательное решение продукта: операция выполнима, но требует подтверждения, и удалённые файлы остаются на диске (их можно вернуть из бэкапа). Размещение архива на отдельном файловом сервере / RAID существенно снижает риск необратимой потери.

Ограничения текущей версии

  • VOD-сессия, открытая клиентом, не следит за live edge — если за время сессии в архив добавились новые чанки, клиент должен перезапросить плейлист (стандартное поведение всех HLS / DASH плееров).

  • На один диск имеет смысл назначать одно хранилище — несколько записей с разными подкаталогами на одном диске будут конкурировать за свободное место.

  • Сегменты адресуются хешем (HLS на MPEG-TS) или шаблоном $Number$.m4s с общим init.mp4 (live DASH на CMAF) / явными <SegmentURL> на .m4s (VOD DASH). Изменение размеров чанка между live и VOD не требует переоткрытия URL.

  • Orphan-scanner запускается раз в час; чтобы ускорить — достаточно перезапустить PSS.