---
title: DVR / Архив
url: https://doc.pstreamer.tv/ru/manual/dvr.html
lang: ru
product: Perfect Streamer
version: 1.13.3.445
---

# 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*):

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

Без 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-событие**:

- [http://host:port/hls/stream/login/password/index.m3u8?epg=1778500000](http://host:port/hls/stream/login/password/index.m3u8?epg=1778500000)
- [http://host:port/dash/stream/login/password/index.mpd?epg=1778500000](http://host:port/dash/stream/login/password/index.mpd?epg=1778500000)

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

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

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

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

- [http://host:port/hls/adaptive/group/login/password/index.m3u8?t=0](http://host:port/hls/adaptive/group/login/password/index.m3u8?t=0)
- [http://host:port/dash/adaptive/group/login/password/index.mpd?t=0](http://host:port/dash/adaptive/group/login/password/index.mpd?t=0)

В 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.
