SIRINVIDEO Документация администратора и интегратора На сайт 2026.08

Просмотр и экспорт

Как запросить архив за интервал времени, отмотать поток назад (таймшифт), открыть архив во встраиваемом плеере и выгрузить фрагмент файлом.

Архив отдаётся тем же протоколом, что и живое видео: плеер получает обычный плейлист HLS, только описывающий не последние секунды, а запрошенный интервал времени. Отдельного «протокола архива» нет, дополнительных портов и служб не требуется.

Все маршруты архива начинаются с /<поток>/dvr/. Если у потока нет строки dvr, любой из них отвечает 404:

stream is not recorded

Плейлист архива#

GET /<поток>/dvr/playlist.m3u8?<интервал>

Форма интервала определяет тип плейлиста — «живой» (EVENT, догоняет текущий момент) или «закрытый» (VOD, конечная запись с EXT-X-ENDLIST):

Запрос Что отдаётся Тип
?rewind=<секунд> последние N секунд, дальше догоняет живой край EVENT
?timeshift=<unix-секунды> с указанного момента и дальше EVENT
?from=<мс> с указанного момента и дальше EVENT
?from=<мс>&to=<мс> закрытый интервал VOD
?from=<мс>&duration=<секунд> закрытый интервал VOD
?from=…&…&event=true EVENT, который превратится в VOD и получит ENDLIST, когда запись действительно остановится EVENT → VOD
(без параметров) последний час EVENT

Время во from/toмиллисекунды Unix, timeshift — секунды. Момент в будущем отклоняется.

# последние 10 минут, живой плейлист
curl -sS "http://media.example.com:8080/cam1/dvr/playlist.m3u8?rewind=600"

# закрытый интервал: 20 секунд начиная с 12:48:53 UTC
FROM=$(date -u -d '2026-08-19 12:48:53' +%s000)   # → 1787143733000
TO=$((FROM + 20000))
curl -sS "http://media.example.com:8080/cam1/dvr/playlist.m3u8?from=$FROM&to=$TO"
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-MAP:URI="/cam1/dvr/init/1787143661468.mp4"
#EXT-X-PROGRAM-DATE-TIME:2026-08-19T12:48:51.468Z
#EXTINF:6.000,
/cam1/dvr/seg/1787143661468/315-342.m4s
…
#EXT-X-ENDLIST

Обратите внимание: запрошено 12:48:53, а первый сегмент начинается в 12:48:51.468. Начало интервала всегда «притягивается» к ближайшему ключевому кадру не позже запрошенного момента — иначе воспроизведение стартовало бы с недекодируемого места.

В теле плейлиста:

  • EXT-X-PROGRAM-DATE-TIME у каждого сегмента — абсолютное время кадра, по нему плеер строит шкалу;
  • EXT-X-DISCONTINUITY на каждой границе записей — там, где непрерывность прерывалась;
  • EXT-X-MAP перевыдаётся только при реальной смене параметров кодирования.
Неизвестные параметры запроса игнорируются

Лишний параметр в URL (например, добавленный CDN или браузером обход кеша) не приводит к ошибке — он просто пропускается.

Два ограничения окна#

  • Окно плейлиста молча урезается до 6 часов, при этом сохраняется самый свежий край интервала. Запросив сутки, вы получите последние 6 часов из них, без предупреждения.
  • Запрос к календарю или таймлайну шире 31 суток — ошибка 422:
timeline requires from < to and a window no larger than 31 days

Для более длинных интервалов запрашивайте архив частями.

Таймшифт#

Таймшифт — это тот же архив, но «привязанный к живому краю»: зритель отматывает назад и продолжает смотреть, а плеер по мере накопления записи догоняет реальное время.

GET /<поток>/dvr/playlist.m3u8?rewind=300        # отмотать на 5 минут назад
GET /<поток>/dvr/playlist.m3u8?timeshift=<unix>  # начать с абсолютного момента

Оба варианта дают плейлист типа EVENT: он не заканчивается, а дописывается по мере записи. Это же поведение доступно во встраиваемом плеере — режим Timeshift.

Короткие адреса для приставок и панелей#

Часть устройств (ТВ-приставки, видеостены, простые панели) не умеет собирать URL с параметрами запроса. Для них есть эквиваленты в виде пути:

Адрес Эквивалент
/<поток>/rewind-<секунд>.m3u8 ?rewind=<секунд> — EVENT
/<поток>/timeshift-<unix-секунды>.m3u8 ?timeshift=… — EVENT
/<поток>/clip-<от-сек>-<длительность-сек>.m3u8 закрытый плейлист VOD
/<поток>/clip-<от-сек>-<длительность-сек>.mp4 выгрузка фрагмента файлом
curl -sS "http://media.example.com:8080/cam1/rewind-60.m3u8" | head -4
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-PLAYLIST-TYPE:EVENT

Служебные маршруты#

Ими пользуются плеер и панель управления; они же удобны для скриптов и мониторинга.

Маршрут Назначение
GET /<поток>/dvr/api/days сутки (UTC), за которые есть записи: {"days":["ГГГГ-ММ-ДД", …]}
GET /<поток>/dvr/api/timeline?from=<мс>&to=<мс> покрытие: непрерывные диапазоны с числом записей, объёмом и ярусом хранения
GET /<поток>/dvr/init/<мс>.mp4 заголовок инициализации записи
GET /<поток>/dvr/seg/<мс>/<a>-<b>.m4s кусок медиаданных
GET /<поток>/dvr/thumb/<мс>.mp4 кадр-превью для наведения на шкалу: ближайший ключевой кадр
GET /<поток>/dvr/clip?from=<мс>&to=<мс> выгрузка фрагмента файлом
curl -sS "http://media.example.com:8080/cam1/dvr/api/timeline"
{"from":1787139386390,"to":1787142986390,
 "ranges":[{"start_ms":1787139151495,"end_ms":1787142986427,
            "segment_count":5,"bytes":4348353497,"tier":"local"}]}

Диапазоны склеены по ярусам: "tier":"local" — данные на диске, "tier":"s3" — в объектном хранилище. Смешанного диапазона не бывает, поэтому по таймлайну сразу видно, какая часть архива холодная. Разрыв между диапазонами — реальный пропуск в записи.

Сутки в календаре считаются по UTC

api/days возвращает UTC-сутки. В часовом поясе UTC+3 вечерние записи с 21:00 попадут в следующие сутки календаря. Плеер учитывает смещение сервера сам; в собственных интерфейсах приводите время явно.

Отдельного маршрута «список записей» нет — обнаружение строится из api/days (какие сутки есть) и api/timeline (что внутри суток).

Встраиваемый плеер#

У пишущего потока во встраиваемом плеере автоматически появляются две дополнительные вкладки — DVR и Timeshift. У потока без записи их нет: набор вкладок выводится из того, что сервер реально умеет отдать для этого потока.

http://media.example.com:8080/cam1/embed.html?proto=dvr
http://media.example.com:8080/cam1/embed.html?proto=timeshift
Параметр запроса Действие
proto=dvr открыть режим просмотра архива
proto=timeshift тот же режим, но с сохранённой ссылкой на живой край
dvr=1 то же, что proto=dvr
from=<мс> и to=<мс> открыть архив сразу на этом интервале (миллисекунды Unix)

Режим DVR — это не другой медиапротокол, а другой интерфейс над теми же маршрутами: выбор суток из api/days, масштабируемая шкала из api/timeline, превью при наведении из dvr/thumb и воспроизведение конечным плейлистом dvr/playlist.m3u8?from=&to=.

Ссылку с интервалом можно передавать как есть — она открывает нужный момент:

http://media.example.com:8080/cam1/embed.html?from=1787142800000&to=1787142860000

Остальные параметры плеера (compact, chrome, autostart, token) действуют и в режиме архива — см. Встраиваемый плеер.

Плеер в режиме архива: шкала суток с закрашенными интервалами записи и управление воспроизведением
Плеер в режиме архива: шкала суток с закрашенными интервалами записи и управление воспроизведением

Экспорт фрагмента файлом#

GET /<поток>/dvr/clip?from=<мс>&to=<мс>
GET /<поток>/clip-<от-сек>-<длительность-сек>.mp4

Ответ — готовый video/mp4 с заголовком выгрузки:

FROM=$(date -u -d '2026-08-19 10:00:00' +%s000)   # → 1787133600000
TO=$((FROM + 10000))                              # 10 секунд
# -OJ сохраняет файл под именем, которое предложил сервер; -D - печатает заголовки
curl -sS -OJ -D - "http://media.example.com:8080/cam1/dvr/clip?from=$FROM&to=$TO"
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 13031529
Cache-Control: no-store
Content-Disposition: attachment; filename="hastreamer-clip-1787133600000-1787133610000.mp4"
X-HAS-Source: local

Заголовок X-HAS-Source показывает, откуда взялись данные: local — с диска, s3 — из объектного хранилища.

Файл собирается без перекодирования: заголовок записи отдаётся как есть, к нему приставляется нужный отрезок медиаданных. Отсчёт времени в файле переносится в ноль по каждой дорожке отдельно, поэтому файл начинается с начала и синхронность звука с видео сохраняется; на диске архив остаётся с абсолютными метками времени. Выгружать можно и ещё не закрытую запись — вплоть до того момента, который уже надёжно записан.

Интервал может пересекать границы файлов записи. Хранилище режет архив на файлы по segment_secs (15, 30 или 60 минут), и запрошенный час обычно состоит из нескольких таких файлов — сервер склеивает их в один .mp4 со сквозной шкалой времени: скачка на стыке нет, звук от видео не разъезжается. Живой хвост участвует в склейке наравне с закрытыми записями.

Файл отдаётся потоком, а не собирается в памяти целиком, поэтому длина клипа не ограничена объёмом. Content-Length известна заранее, работает Range (докачка) и HEAD.

Экспорт отдаёт полный звук, даже когда просмотр — без звука

Запись с телефонным звуком G.711 браузер декодировать не умеет, поэтому при просмотре архива сервер честно отдаёт только видео. В экспортированный файл звук попадает целиком. Это частый вопрос: «в браузере тихо, а в скачанном файле звук есть» — так и задумано.

Пропуски в записи#

Если внутри интервала камера была недоступна, этих секунд в архиве нет — и взять их неоткуда. Сервер склеивает записи встык, сжимая паузу: файл получается короче запрошенного интервала ровно на невыписанное время, а шкала остаётся непрерывной. Так честнее, чем растягивать время: иначе плеер стоял бы на замершем кадре всю длительность аварии.

Считать длительность файла по to − from поэтому нельзя. Сколько записи реально есть в интервале, показывает api/timeline (ranges): суммарная длительность его диапазонов и есть ожидаемая длительность файла (плюс притяжка к ключевому кадру в начале).

Смена параметров кодирования — отказ#

Склеить в один файл можно только записи с одинаковыми параметрами: разрешение, кодек и набор дорожек описываются одним заголовком moov. Если камеру переключили на другое разрешение, архив хранит записи «до» и «после» с разными заголовками, и один файл из них не собрать. Сервер не отдаёт ни обрезанный, ни битый результат, а отказывает с указанием момента смены:

422 clip crosses an encoding change at 1787227785573
    (different resolution/codec/track set — export each side separately)

Выгрузите две части — до и после указанной метки.

Ограничения экспорта#

# Ограничение Величина Сообщение
1 Одинаковые параметры кодирования на всём интервале init_hash записи clip crosses an encoding change at <мс> …
2 Предел длительности интервала 24 часа clip exceeds MAX_CLIP_MS (1440 min) (маршрут dvr/clip; алиас clip-…mp4 молча урезает)
3 Число склеиваемых записей 512 clip spans too many recording segments …
4 Окно плейлиста — относится к запросу интервала, а не к самому файлу 6 часов урезается молча
Ограничения по объёму больше нет — следите за размером файла сами

Экспорт отдаётся потоком и стоит серверу одно окно памяти (несколько МиБ) независимо от длины клипа и числа склеенных записей. Зато размер файла ничем не прикрыт: при 4 Мбит/с час записи — это около 1.8 ГБ, сутки — около 43 ГБ. Оценивайте по bytes из api/timeline и предупреждайте пользователей.

Одновременно выполняется не более шести выгрузок на весь сервер. Сверх этого:

503 clip export busy - retry shortly

В панели управления экспорт доступен на карточке потока, вкладка DVR, блок Export a clip: два поля выбора времени и кнопка с оценкой размера. Панель держит выделение внутри непрерывного диапазона архива — границы файлов записи внутри такого диапазона ей знать не нужно, их склеивает сервер.

Права доступа#

Когда авторизация просмотра включена, маршруты архива требуют разных прав:

Маршрут Требуемое право
dvr/playlist.m3u8, dvr/api/*, dvr/init, dvr/seg, dvr/thumb, clip-….m3u8 dvr
dvr/clip, clip-….mp4 export

Право export не входит в dvr. Зритель, который может листать и смотреть архив, не сможет скачать фрагмент, пока выгрузка не разрешена ему явно. Это позволяет открыть просмотр архива широкой группе, оставив вынос материала за пределы системы узкой. Подробности — Авторизация просмотра.

Учёт зрителей архива#

Плейлист и все запросы сегментов одного зрителя объединяются в одну сессию; сессия закрывается через 30 секунд без запросов. Зрители архива считаются отдельно от живых:

curl -sS "http://media.example.com:8080/api/v1/totals" \
  -H "Authorization: Bearer $TOKEN"
{"bytes_in":7771909210,"bytes_out":578696551,"dvr_bytes_out":32833724,"dvr_sessions":1,
 "live_bytes_out":545862827,"live_sessions":3,"sessions":4,"streams":10}

На карточке потока те же величины называются dvr_viewers и dvr_bytes_out.

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

Что вернулось Что это значит
404 stream is not recorded у потока нет строки dvr — запись не настроена
404 no such recording запрошенного момента в архиве нет: он вытеснен по ретенции или не записывался
422 timeline requires from < to … окно шире 31 суток либо границы перепутаны местами
422 clip crosses an encoding change at … внутри интервала менялись разрешение/кодек/набор дорожек: выгрузите части отдельно
422 clip spans too many recording segments … интервал накрывает больше 512 файлов записи — сузьте его
404 no recording in range в интервале нет ни одной записи (весь интервал попал в пропуск)
503 архив временно недоступен: том не смонтирован, объектное хранилище не отвечает — повторите позже
Пустой плейлист в запрошенном интервале записи нет; проверьте по api/timeline
Архив отдаётся зрителю
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' \
  "http://media.example.com:8080/cam1/dvr/playlist.m3u8?rewind=60"
200 application/vnd.apple.mpegurl

Если код 404, а тело — stream is not recorded, вернитесь к странице Запись архива (DVR): в конфигурации потока нет строки dvr.

Куда двигаться дальше#