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

Диагностика неисправностей

Симптом → вероятная причина → проверочная команда → способ починить. Страница рассчитана на дежурного инженера, которому нужно решить задачу сейчас.

Страница построена так, чтобы её можно было читать с середины. Найдите свой симптом в общей таблице, перейдите в его раздел, выполните проверочную команду — она отвечает на вопрос «в чём дело», а не «работает ли вообще».

Во всех примерах media.example.com — ваш сервер, cam1 — имя потока, 10.0.0.10 — камера, $TOKEN — токен доступа к API. Получить токен:

TOKEN=$(curl -sS -X POST https://media.example.com/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"password":"<пароль-администратора>"}' | sed -E 's/.*"token":"([^"]+)".*/\1/')

Первые пять минут#

Три команды, которые нужно выполнить до любой гипотезы. Они отделяют «сервер не работает» от «не работает один поток» и от «не работает у одного зрителя».

# 1. Жив ли сервис и не перезапускается ли он по кругу.
systemctl is-active hastreamer; systemctl show -p NRestarts --value hastreamer
active
0
# 2. Что сервер сказал последним.
sudo journalctl -u hastreamer -n 50 --no-pager
# 3. Что с потоками и ресурсами.
curl -sS "https://media.example.com/api/v1/streams?fields=id,health,health_reasons,source_error,viewers&limit=200" \
  -H "Authorization: Bearer $TOKEN"
curl -sS https://media.example.com/api/v1/system -H "Authorization: Bearer $TOKEN"

Растущее NRestarts — ошибка в конфигурации: сервер не смог загрузиться. Все потоки в failing — сеть или общий доступ к источникам. Один поток в failing — этот источник. Все ok, но зритель не видит видео — сторона клиента: кодек, авторизация или протокол.

Общая таблица#

Симптом Вероятная причина Что проверить Чем починить
Сервис не стартует после правки конфигурации опечатка в директиве, лишняя или пропущенная ; hastreamer --validate — печатает номер строки исправить строку; либо вернуться к предыдущей версии из истории
Сервис стартует, но перезапускается по кругу ошибка конфигурации при загрузке journalctl -u hastreamer -n 50 то же
Поток failing, source_error: Timeout источник недоступен по сети curl к камере с самого сервера маршрут, файрвол, адрес и порт камеры
Поток failing, source_error: Status(401, "Unauthorized") неверные логин или пароль в input строка input в конфигурации исправить учётные данные
Поток поднимается, но кадры не идут камера не отдаёт по выбранному транспорту source_transport и no_data_secs переключить транспорт на tcp
Поток то поднимается, то падает камера «замерзает» при живом сокете reconnects и записи media stall это штатное лечение; чинить на камере
В браузере чёрный экран, звука нет звук в кодеке, который браузер не декодирует audio_codec у потока перевести камеру на AAC или смотреть по WebRTC
В браузере ничего не грузится, в консоли ошибка о смешанном содержимом страница по HTTPS, медиа по HTTP адрес в <iframe> или в плеере привести схему к HTTPS с обеих сторон
401 unauthorized на плейлисте учётные данные не предъявлены или отвергнуты предъявлен ли ?token= выдать зрителю действительный токен
401 unauthorized на сегменте, плейлист открывается сегменту передали ?token= вместо ?ssid= какой параметр в URL сегмента ходить по ссылкам из плейлиста, не подставлять свои
403 forbidden на медиа подпись верна, но прав не хватает; либо билет предъявлен из другого браузера или с другого адреса права в токене, User-Agent, bind_ip расширить права; не переносить ссылку между клиентами
403 license restricted лицензия не действует GET /api/v1/license активировать лицензию
429 throttled серия неудачных попыток с одного адреса общий ли это адрес NAT подождать Retry-After; исправить источник неверных запросов
/metrics отвечает 401 эндпоинт закрыт учётными данными заголовок Authorization у сборщика завести api_read_token и передавать его заголовком
Архив не пишется нет места, каталог недоступен, хранилище не смонтировано GET /api/v1/storages освободить место, исправить права, смонтировать диск
Архив не пишется, в журнале DVR storage is unavailable каталог хранилища недоступен на запись права на каталог, identity_error в /api/v1/storages выдать права; восстановится само на следующем проходе обслуживания
Высокая задержка плеер на обычном HLS, длинные сегменты, длинный GOP какой URL запрашивает плеер перейти на LL-HLS или MSE-WS, укоротить GOP на камере
Сессии рвутся у всех сразу после перезапуска session_secret не задан строка note auth.session_secret is unset в журнале задать session_secret
Сессии рвутся выборочно медленный клиент, истёк билет, достигнут max_sessions lag_events у сессии, 503 session-limit канал клиента, ttl билета, max_sessions

Сервис не стартует после правки конфигурации#

Опечатка никогда не «деградирует мягко»: неизвестная директива, неверное значение перечисления и выход за диапазон — это жёсткие ошибки, которые прерывают загрузку. Процесс завершается с кодом 1, systemd перезапускает его каждые 2 секунды — получается цикл перезапусков.

Проверка. Разберите файл, ничего не запуская:

sudo hastreamer --validate /etc/hastreamer/hastreamer.conf

Ответ на исправном файле:

/etc/hastreamer/hastreamer.conf: OK (12 streams)

Типичные ошибки и их точные сообщения:

parse /etc/hastreamer/hastreamer.conf: line 6: directive "input" not terminated with ';'
parse /etc/hastreamer/hastreamer.conf: line 2: unknown directive "cors"
parse /etc/hastreamer/hastreamer.conf: line 5: unknown stream directive "inpt"
parse /etc/hastreamer/hastreamer.conf: unknown variant `trace`, expected one of `debug`, `info`, `warn`, `error`
invalid config /etc/hastreamer/hastreamer.conf: udp_ingest_shards 99 out of range (1..=16)

Номер строки в сообщении — это номер строки в файле. Откройте её и исправьте.

Что делать. Исправьте строку и повторите --validate. Если правка была неудачной и вы не понимаете, что сломали, — вернитесь к предыдущей версии:

sudo ls /etc/hastreamer/hastreamer.conf.history/
sudo cp /etc/hastreamer/hastreamer.conf.history/7.conf /etc/hastreamer/hastreamer.conf
sudo hastreamer --validate /etc/hastreamer/hastreamer.conf
sudo systemctl restart hastreamer
Проверяйте изменения через reload, а не через restart

systemctl reload hastreamer разбирает новый файл и, если тот неверен, отклоняет его, продолжая работать на старой конфигурации — ни один зритель не пострадает:

config reload REJECTED: invalid config: parse: line 5: unknown stream directive "inpt" (running config unchanged)

restart такой защиты не даёт: процесс уже завершился, и неверный файл приведёт к циклу перезапусков. Правило: сначала --validate, потом reload, и только для директив, требующих перезапуска, — restart. См. Применение изменений.

Поток не поднимается#

Начните со снимка потока — в нём есть и вердикт, и причина:

curl -sS "https://media.example.com/api/v1/streams/cam1" \
  -H "Authorization: Bearer $TOKEN"

Значимые поля ответа:

{"id":"cam1","alive":false,"health":"failing","health_reasons":["source_error"],
 "source_error":"Timeout","reconnects":0,"input_errors":1,"no_data_secs":0,
 "stop_events":[["error: Timeout",1787143451015]]}
Идентификатор потока в API кодируется одним сегментом

Если путь потока содержит / (например live/cam1), в адресах /api/v1/… его пишут percent-кодированием: /api/v1/streams/live%2Fcam1. В медиа-адресах — наоборот, с обычными слэшами: /live/cam1/master.m3u8.

Источник недоступен#

"source_error": "Timeout"

В журнале это выглядит так (учётные данные в журнал не попадают — они заменены на ***):

[cam1] source stopped: error: Timeout
[cam1] reconnecting → rtsp://***@10.0.0.10:554/Streaming/Channels/101

Проверка. Проверяйте связь с самого сервера, а не со своего рабочего места:

# порт открыт?
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/10.0.0.10/554' && echo "порт 554 открыт" || echo "порт 554 недоступен"
порт 554 открыт

Если порт открыт, а поток всё равно не поднимается — проверьте путь в адресе: у разных моделей камер он разный, и неверный путь даёт не таймаут, а другой код ответа.

Что делать. Маршрут до камеры, правила файрвола, адрес и порт в директиве input.

Неверные учётные данные#

"source_error": "Status(401, \"Unauthorized\")"

Камера ответила «неавторизован». Это точный признак неправильного логина или пароля в адресе источника — не сетевая проблема.

Проверка. Посмотрите строку input в конфигурации:

sudo grep -n 'input ' /etc/hastreamer/hastreamer.conf

Что делать. Исправьте учётные данные и всегда берите адрес источника в кавычки:

stream cam1 {
  input "rtsp://admin:p%2Fssw0rd@10.0.0.10:554/Streaming/Channels/101";
}

Два спецсимвола в пароле требуют внимания, остальные работают как есть:

Символ в пароле Что происходит Как записать
/ адрес разбирается неверно, поток не поднимается %2F
; без кавычек обрывает директиву на середине взять значение в кавычки
@, :, ? разбираются правильно, кодировать не нужно как есть

Признак пароля со слэшем — не 401, а другая ошибка:

{"id":"cam1","source_error":"Io(\"invalid port value\")"}

Признак незакавыченного значения с ; — ошибка при проверке конфигурации, причём в сообщении будет кусок самого пароля:

parse /etc/hastreamer/hastreamer.conf: line 2: unknown stream directive "ss@10.0.0.10:554/s"

Неверный транспорт#

Симптом отличается от предыдущих: сессия устанавливается, но кадры не идут — растёт no_data_secs, bitrate остаётся нулевым.

Транспорт забора камер задаётся глобально:

Параметр Тип и допустимые значения По умолчанию Применение
source_transport tcp | udp | udp-then-tcp tcp рестарт
  • tcp — RTP внутри управляющего соединения. Проходит через NAT и файрволы, подходит почти всегда.
  • udp — отдельные UDP-порты. Отката на TCP нет: если UDP не проходит, поток останется без кадров.
  • udp-then-tcp — сначала UDP, при отказе камеры — переход на TCP для всей сессии.

Проверка.

sudo grep -n 'source_transport' /etc/hastreamer/hastreamer.conf
curl -sS "https://media.example.com/api/v1/streams?fields=id,no_data_secs,bitrate,alive&limit=200" \
  -H "Authorization: Bearer $TOKEN"

Что делать. Если стоит udp и кадров нет — верните tcp и перезапустите сервис. При приёме по UDP дополнительно проверьте потери на уровне ядра:

curl -sS https://media.example.com/api/v1/system -H "Authorization: Bearer $TOKEN"

Растущие udp_ingest_kernel_drops — тема раздела о производительности.

Не путайте source_transport и rtsp_transports

source_transport — как сервер забирает камеру (глобально, задаётся один раз на сервер). rtsp_transports — какой транспорт разрешено запросить клиенту, который смотрит поток с сервера по RTSP (задаётся у потока). Это самая частая путаница в этой области.

Поток то поднимается, то падает#

Худший вид отказа камеры: сокет открыт и отвечает, а кадры идти перестали. За это отвечает сторожевой таймер.

Параметр Тип и допустимые значения По умолчанию Применение
media_timeout секунды, целое; 0 — сторож выключен 20 рестарт

Если кадры не приходят media_timeout секунд, сервер принудительно переподключается. В снимке потока это видно как растущий reconnects, а причина — как media stall — source frozen (socket alive, no fragments).

Проверка.

curl -sS "https://media.example.com/api/v1/streams?fields=id,reconnects,no_data_secs,health_reasons&limit=200" \
  -H "Authorization: Bearer $TOKEN"

Что делать. Переподключение — это лечение симптома, а не причины: зрителей при нём не отключают, кольца и сессии сохраняются. Причину ищите на камере (перегрев, устаревшая прошивка, слишком много одновременных подключений к ней). Отключать сторож (media_timeout 0) не следует — без него поток «зависнет» навсегда.

Видео не играет в браузере#

Сначала отделите «сервер не отдаёт» от «браузер не может воспроизвести»:

curl -sS -o /dev/null -w '%{http_code}\n' https://media.example.com/cam1/master.m3u8

Код 200 означает, что сервер отдаёт и проблема на стороне клиента. Любой другой код — переходите к разделу об ошибках 401 и 403.

Звука нет, видео идёт#

Это самый частый случай, и это не сбор.

Большинство IP-камер передают звук в G.711, а браузеры не умеют декодировать G.711 штатными средствами воспроизведения. Сервер поступает честно: он не создаёт звуковую дорожку вовсе — иначе плеер «завис» бы на несуществующей дорожке и не показал бы даже видео. Перекодирования в продукте нет, поэтому исправить это на сервере нельзя.

Проверка. Посмотрите кодек звука у потока:

curl -sS "https://media.example.com/api/v1/streams?fields=id,audio_codec&limit=200" \
  -H "Authorization: Bearer $TOKEN"
{"items":[{"audio_codec":"ulaw","id":"cam1"},{"audio_codec":"mp4a.40.2","id":"cam2"}]}
Значение audio_codec Что это Слышно в браузере
mp4a.40.2 AAC да (кроме WebRTC)
opus Opus да
ulaw / alaw G.711 только по WebRTC
null у потока нет звука нечего слышать

Второй, независимый признак — мастер-плейлист потока с G.711 не содержит ни строки EXT-X-MEDIA, ни звука в CODECS:

curl -sS https://media.example.com/cam1/master.m3u8
#EXTM3U
#EXT-X-VERSION:9
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=1899364,CODECS="avc1.64000C",RESOLUTION=1920x1080,FRAME-RATE=25.000
v0/playlist.fmp4.m3u8?sid=2814749767106561

Это сделано намеренно: объявленная, но отсутствующая звуковая дорожка «подвешивает» плеер целиком, и зритель не увидел бы даже видео.

Что делать — один из трёх вариантов:

  1. Перевести камеру на AAC, если модель это умеет. Правильное решение: звук будет во всех протоколах.
  2. Смотреть по WebRTC. Это единственный браузерный транспорт, который передаёт G.711: https://media.example.com/cam1/embed.html?proto=webrtc. Требует включённого порта WebRTC.
  3. Смотреть не в браузере. По RTSP звук G.711 передаётся как есть; в скачанном файле экспорта архива звук тоже сохраняется полностью.
Обратный случай: WebRTC не передаёт AAC

Если у потока звук в AAC, зритель по WebRTC не получит звука вообще — AAC не согласуется в WebRTC. Для потоков с AAC используйте HLS или MSE-WS.

Полное соответствие «кодек ↔ протокол» приведено в Протоколах раздачи.

Не играет вовсе, в консоли браузера — ошибка о смешанном содержимом#

Браузер блокирует загрузку по http:// со страницы, открытой по https://. Плеер при этом показывает пустой кадр, а в консоли видно сообщение о блокировке смешанного содержимого.

Проверка. Откройте консоль браузера (F12) на странице со встроенным плеером и посмотрите, по какой схеме уходят запросы. Затем убедитесь, что сервер вообще слушает HTTPS:

sudo grep -nE '^\s*(http|https)\s' /etc/hastreamer/hastreamer.conf
3:http 8080;
4:https 8443;

Что делать. Схемы должны совпадать: страница по HTTPS — и плеер по HTTPS. Настройте https <порт>; и сертификат (TLS и сертификаты), после чего встраивайте плеер по адресу https://media.example.com/cam1/embed.html.

Сервер сам перенаправляет с HTTP на HTTPS

Если TLS терминирует сам сервер, обычный HTTP-запрос получает 307 на HTTPS-слушатель. Это не устраняет блокировку смешанного содержимого: браузер блокирует запрос до перенаправления. Указывайте https:// в адресе плеера явно.

Не играет один протокол, остальные работают#

Проверьте по очереди, подставляя ?proto=:

# hls · hls-ll · mse-ws · dash · webrtc
for p in hls hls-ll mse-ws dash webrtc; do
  echo "https://media.example.com/cam1/embed.html?proto=$p"
done

Откройте каждый адрес в браузере. Если работают все, кроме WebRTC, — не открыт UDP-порт WebRTC или не задан внешний адрес. Если не работает DASH или MSE-WS при рабочем HLS — почти всегда дело в промежуточном прокси, который не пропускает вебсокеты или переписывает адреса.

401 и 403 на медиа#

Правило, которое экономит время:

  • 401 — «предъявите учётные данные или они не приняты».
  • 403 — «данные приняты, но этого вам не положено».

Различие в том, где предъявляются учётные данные. Их два уровня, и путать их нельзя:

Уровень Что это Где предъявляется
Грант — токен зрителя JWT, выпущенный вашей системой на плейлисте: ?token=<JWT>
Билет?ssid= короткая метка, которую выпускает сам сервер на сегментах; сервер сам вписывает её в ссылки внутри плейлиста

Тело ответа — короткий текст, и по нему причина определяется однозначно:

Код Тело Что произошло
401 unauthorized учётные данные не предъявлены — или предъявлены и отвергнуты (подпись, секрет, iss, aud)
401 token expired истёк срок exp у токена либо истёк билет
401 session ticket required запрошено продолжение VoD без билета
403 forbidden подпись верна, но прав не хватает; либо билет чужой, из другого браузера, с другого адреса или не того класса
403 license restricted лицензия — не авторизация
429 throttled временная блокировка адреса после серии неудач
503 session-limit достигнут max_sessionsне авторизация

Ни один ответ не повторяет предъявленный токен: тело короткое, а из адреса перенаправления значения token, jwt и ssid вычищаются.

401 unauthorized на сегменте, а плейлист открывается#

Классическая ошибка интеграции: зрителю выдали прямую ссылку на сегмент и приписали к ней ?token=.

Проверка. Воспроизводится за два запроса:

# 1. Плейлист с токеном — сервер вписывает ssid в ссылки внутри.
curl -sS "https://media.example.com/cam1/master.m3u8?token=$VIEWER_TOKEN" | head -6
#EXTM3U
#EXT-X-VERSION:9
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=1606360,CODECS="avc1.64000C,mp4a.40.2",RESOLUTION=1920x1080
v0/playlist.fmp4.m3u8?sid=1688849860263936&ssid=MADOYRsj4r4fZQHGfrsBAAAAAGqFs
# 2. Тот же сегмент с токеном вместо билета.
curl -sS "https://media.example.com/cam1/v0/1787143404.m4s?token=$VIEWER_TOKEN"
unauthorized

Что делать. Плеер обязан ходить по ссылкам из плейлиста, а не строить адреса сегментов сам. Токен предъявляется один раз — на плейлисте; дальше работает билет, который сервер вписал в ссылки.

403 forbidden при верном токене#

Три причины, в порядке частоты.

1. Прав в токене не хватает. Разрешение без пути и без меток не даёт ничего: {"action":"read"} — это не «все потоки». «Все» пишется явно:

{"action":"read","path":"*"}

2. Ссылку перенесли между клиентами. Билет ?ssid= связан с браузером: в его подпись входит User-Agent. Ссылка, скопированная из браузера и открытая другой программой, даст 403.

Проверяется одной парой запросов:

curl -sS -o /dev/null -w '%{http_code}\n' "https://media.example.com/cam1/v0/1787143404.m4s?ssid=$SSID"
curl -sS -A 'other-agent/1.0' -o /dev/null -w '%{http_code}\n' "https://media.example.com/cam1/v0/1787143404.m4s?ssid=$SSID"
200
403

3. Включена привязка к адресу. При session_keys { bind_ip on; } в подпись билета входит ещё и IP клиента. Смена адреса — переход с Wi-Fi на мобильную сеть, работа через CG-NAT — даёт 403 в середине просмотра. Проверьте настройку:

sudo grep -nA4 'session_keys' /etc/hastreamer/hastreamer.conf

Если зрители мобильные, bind_ip включать не следует.

За обратным прокси обязателен trusted_proxies

Без него сервер не читает заголовки с адресом клиента и видит адрес прокси. Привязка токенов и билетов к IP при этом либо ломается целиком, либо перестаёт что-либо означать, а в учёте сессий у всех зрителей окажется один и тот же адрес.

403 license restricted#

Это не авторизация. Лицензия не действует, и сервер не отдаёт ни одного кадра — при том что панель и API работают нормально.

curl -sS https://media.example.com/api/v1/license -H "Authorization: Bearer $TOKEN"
{"state":"licensed","customer":"example-customer","expires":"2027-01-31","max_streams":50}

Значение state, отличное от licensed, — см. Лицензирование.

429 throttled#

Серия неудачных попыток авторизации с одного адреса приводит к временной блокировке этого адреса; в ответе есть заголовок Retry-After. За общим NAT чужие ошибки бьют по всем.

Ищите источник неверных запросов: обычно это плеер или интеграция со старым, уже недействительным токеном, повторяющая запрос в цикле.

Нет метрик#

Симптом: система мониторинга перестала собирать метрики, в её журнале — 401.

curl -sS -o /dev/null -w '%{http_code}\n' https://media.example.com/metrics
401

Причина. Эндпоинт /metrics закрыт учётными данными. Он принимает те же токены, что и /api/v1, и без них отвечает 401 — даже если на сервере вообще не настроен доступ. Тело экспозиции перечисляет все потоки, их состояние и число зрителей, то есть раскрывает ровно то, ради чего закрыт /api/v1.

Сервер говорит об этом при каждом старте — ровно одной строкой:

sudo journalctl -u hastreamer --no-pager | grep '/metrics is' | tail -1
hastreamer: WARNING /metrics is CLOSED and no credential is configured — EVERY scrape now gets 401 (it was public before 2026.08). Set auth.api_read_token "<random-token>" and have the scraper send `Authorization: Bearer <random-token>`.

Что делать. Заведите отдельный токен только на чтение — сборщику метрик не нужен пароль администратора:

auth {
  admin_password "<пароль-администратора>";
  api_read_token "<случайный-токен-не-короче-16-символов>";
}
sudo hastreamer --validate /etc/hastreamer/hastreamer.conf && sudo systemctl reload hastreamer
Метрики отдаются
curl -sS https://media.example.com/metrics \
  -H "Authorization: Bearer <случайный-токен-не-короче-16-символов>" | head -3
# HELP hastreamer_process_cpu_cores Process CPU usage in cores (cputime-delta / cores).
# TYPE hastreamer_process_cpu_cores gauge
hastreamer_process_cpu_cores 0.0900
Токен передаётся только заголовком

https://media.example.com/metrics?token=… вернёт 401. Статические токены принимаются исключительно в заголовке Authorization: долгоживущий токен в адресе оседает в журналах всех прокси на пути. Настройте передачу заголовка в своей системе мониторинга.

Требования к токену: не короче 16 символов, api_read_token и api_write_token не должны совпадать — иначе конфигурация не пройдёт проверку. Токен чтения открывает только GET, HEAD и OPTIONS; попытка записи возвращает 403 с полем reason, а не 401.

Архив не пишется#

Порядок разбора: сначала хранилище, потом место, потом настройка потока.

Шаг 1. Хранилище доступно?

curl -sS https://media.example.com/api/v1/storages -H "Authorization: Bearer $TOKEN"

Исправное хранилище:

[{"name":"local","path":"/var/lib/hastreamer/dvr","identity_healthy":true,
  "maintenance_healthy":true,"total_bytes":84444717056,"used_bytes":31733628928,
  "avail_bytes":52711088128,"evict_at_percent":90,
  "streams":[["cam1",21,21012998324]]}]

Неисправное — обратите внимание на два текстовых поля с причиной:

[{"name":"arch","path":"/srv/archive","identity_healthy":false,
  "identity_error":"DVR recovery journal/root is unavailable; recording and maintenance are blocked",
  "maintenance_healthy":false,
  "maintenance_error":"DVR maintenance is unavailable until storage identity recovers",
  "total_bytes":0,"used_bytes":0,"avail_bytes":0,"streams":[]}]

Нулевые размеры и identity_healthy: false означают, что каталог недоступен: не смонтирован, не существует или закрыт правами. Пустой список [] — то же самое на этапе загрузки.

В журнале это выглядит так:

sudo journalctl -u hastreamer --no-pager | grep -iE 'DVR storage|DVR volume' | tail -3
DVR storage /srv/archive recovery journal/root is unavailable; only this storage remains blocked and will be retried by maintenance
[cam1] DVR storage is unavailable: startup recovery is incomplete; waiting fail-closed

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

Что делать. Проверьте каталог с самого сервера:

ls -ld /srv/archive && sudo test -w /srv/archive && echo "запись разрешена"
df -h /srv/archive
drwxr-xr-x 3 root root 4096 авг 19 12:00 /srv/archive
запись разрешена
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1       1,8T  620G  1,1T  36% /srv/archive

Столбец Mounted on — главное в этом выводе. Если там /, а не сам каталог хранилища, значит отдельный диск не смонтирован и архив пишется на системный раздел. Это отдельная неисправность: системный раздел заполнится, и сервер потянет за собой весь хост.

После исправления прав reload бесполезен — но и не нужен

reload на неизменённом файле отвечает config reload: no change и ничего не переоценивает.

Ждать при этом не обязательно: хранилище проверяется заново на каждом проходе обслуживания (dvr_cleanup_secs, по умолчанию 600 секунд), и восстанавливается само. В журнале появится:

[cam1] DVR volume recovered; recording resumed

Проверено: после исправления прав без reload и без restart хранилище вернулось в identity_healthy: true и запись пошла. Если ждать до десяти минут не хочется — выполните sudo systemctl restart hastreamer, эффект тот же.

Шаг 2. Место есть?

df -h /var/lib/hastreamer/dvr

Сверх ограничений потока работает клапан давления на диск: при заполнении файловой системы выше evict_at_percent (по умолчанию 90 %) удаляются самые старые записи по всем потокам. Если used_bytes / total_bytes устойчиво держится около порога и записи исчезают быстрее ожидаемого — диску не хватает объёма под заданную глубину архива.

Ожидаемое время до заполнения показывает поле fill_eta_secs в ответе /api/v1/storages.

Шаг 3. Запись вообще включена и настроена верно?

curl -sS https://media.example.com/api/v1/config/streams -H "Authorization: Bearer $TOKEN"
[{"disabled":false,"labels":[],"path":"cam1","record":true,"retention":"24h · 40G"}]

"record": false означает, что у потока нет директивы dvr.

Частые ошибки в самой директиве — они не дают серверу загрузиться и видны в --validate:

invalid config: stream "cam1": record needs a known local DISK storage (got ""); use copy= for an s3 cold tier
invalid config: stream "cam1": record needs a known local DISK storage (got "archive"); use copy= for an s3 cold tier
invalid config: storage "local": segment_secs must be one of 900, 1800 or 3600 seconds

Первая — не указано хранилище. Вторая — указано хранилище, которого нет (опечатка в имени).

Ловушка единиц: m — это минуты, а не месяцы

dvr local 30m; хранит тридцать минут видео, а не тридцать месяцев. Месяцы пишутся mo: dvr local 30mo;.

Размер пишется только заглавными: K, M, G, T. Запись 500g размером не считается — она будет разобрана как имя хранилища, и сервер откажется загружаться с сообщением record needs a known local DISK storage (got "500g").

Полное описание — Запись архива.

Высокая задержка#

Сначала определите, где именно копится задержка.

curl -sS "https://media.example.com/api/v1/streams?fields=id,ingest_latency_ms,live_latency_target_ms,fps&limit=200" \
  -H "Authorization: Bearer $TOKEN"

ingest_latency_ms — задержка на приёме, до упаковки. Если она велика, дело в сети до камеры или в самой камере, и настройками раздачи это не лечится.

Если приём в порядке, задержку определяют четыре величины — в порядке влияния:

1. Протокол. Обычный HLS по своей природе даёт задержку в несколько сегментов. Разница между режимами существенная: на одном и том же материале измеренная доставка до плеера составила 317 мс на LL-HLS против 2605 мс на обычном HLS — примерно восьмикратная разница. Итог у зрителя задаёт буфер плеера: целевая задержка встроенного плеера — ≈4 с на LL-HLS против ≈8 с на обычном HLS.

Режим выбирается адресом, а не параметром

Низкая задержка включается другим URL, а не переключателем в запросе:

  • https://media.example.com/cam1/master.m3u8 — обычный HLS;
  • https://media.example.com/cam1/master.ll.m3u8 — LL-HLS.

Убедитесь, что плеер запрашивает именно .ll.m3u8. Проверить это можно в панели плеера (?proto=hls-ll) либо в журнале доступа своего прокси.

Глобальный переключатель:

Параметр Тип и допустимые значения По умолчанию Применение
low_latency on | off on рестарт

При low_latency off адреса .ll.m3u8 отвечают 404, а плеер откатывается на обычный HLS. Проверьте, что режим не выключен.

2. Длина GOP на камере. Сегмент закрывается только на опорном кадре, поэтому сегмент не может быть короче GOP. Камера с интервалом опорных кадров в 4 секунды не даст сегментов короче четырёх секунд, какие бы значения ни стояли в конфигурации. Это самая частая причина, по которой «настройки не помогают». Ставьте на камере интервал опорных кадров в 1–2 секунды.

3. Длина и число сегментов.

Параметр Тип и допустимые значения По умолчанию Применение
segment_seconds секунды, дробное; 0 — значение по умолчанию 2.0 горячее
segments_count целое; 0 — значение по умолчанию 5 горячее
prepush секунды, дробное; 0 — умолчание протокола 0 горячее

Все три задаются внутри блока stream и применяются по reload, без перезапуска сервиса. Но это директивы самого потока: изменение любой из них пересоздаёт этот поток, и его зрители переподключаются. Остальные потоки не затрагиваются.

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

4. Выбор транспорта. Если задержка критична, сравните на своём материале: MSE-WS и WebRTC обычно дают меньшую задержку, чем даже LL-HLS. Сравнение не требует изменения конфигурации — откройте один и тот же поток в четырёх режимах и посмотрите на часы в кадре:

https://media.example.com/cam1/embed.html?proto=hls
https://media.example.com/cam1/embed.html?proto=hls-ll
https://media.example.com/cam1/embed.html?proto=mse-ws
https://media.example.com/cam1/embed.html?proto=webrtc
Задержка «уехала» у всех сразу и ресурсы заняты

Если задержка растёт одновременно на всех потоках, а proc_cpu_cores близко к worker_cores — это не настройка раздачи, а нехватка процессора. См. Производительность.

Рвутся сессии#

У всех сразу, после перезапуска сервиса#

Если session_secret не задан, сервер при каждом запуске берёт случайный секрет. После перезапуска все ранее выданные токены администратора и все билеты зрителей становятся недействительными — все сессии рвутся одновременно.

Проверка.

sudo journalctl -u hastreamer --no-pager | grep 'session_secret' | tail -1
hastreamer: note auth.session_secret is unset — using a random per-boot secret; admin tokens will not survive a restart and are not valid across nodes (set it fleet-wide to fix both).

Что делать. Задайте постоянный секрет:

auth {
  admin_password "<пароль-администратора>";
  session_secret "<длинная-случайная-строка>";
}

У части зрителей: истекает билет#

Билет ?ssid= живёт session_keys.ttl секунд (по умолчанию 3600) и продлевается скользяще, пока плеер опрашивает плейлист. Но продлить его дольше срока действия исходного токена нельзя: когда истекает exp гранта, сессия прекращается.

Параметр Тип и допустимые значения По умолчанию Применение
session_keys.ttl секунды, > 0 3600 горячее
session_keys.bind_ip on | off off горячее

Ответ при истёкшем билете или гранте — 401 token expired.

Что делать. Выпускайте зрителям токены со сроком, покрывающим ожидаемую длительность просмотра, либо предусмотрите в плеере получение нового токена.

У одного зрителя: медленный канал#

Отдельных очередей на зрителя нет, поэтому медленный клиент никогда не мешает остальным — он отстаёт только сам. Для непрерывных протоколов есть предел ожидания записи: если запись в сокет не проходит 10 секунд, сессия закрывается. Для MSE-WS и RTMP это основной механизм отключения медленных клиентов.

Проверка. Посмотрите показатели качества конкретной сессии:

curl -sS "https://media.example.com/api/v1/sessions?stream=cam1" \
  -H "Authorization: Bearer $TOKEN"

Значимые поля — lag_events (накопительный счётчик отставаний), rr_fraction_lost, rr_jitter_us, rr_rtt_us. Растущий lag_events — канал зрителя не тянет битрейт потока.

Что делать. Это сторона клиента. На стороне сервера помогает увеличение prepush — новый зритель стартует дальше от живого края и получает запас на неровный канал ценой задержки.

Новые зрители получают отказ#

При достижении общего потолка сессий новые подключения отклоняются, а уже подключённые продолжают работать.

Параметр Тип и допустимые значения По умолчанию Применение
max_sessions целое; 0 — без ограничения 0 рестарт

Признак: 503 session-limit по HTTP и код 453 в ответ на SETUP по RTSP.

curl -sS https://media.example.com/api/v1/totals -H "Authorization: Bearer $TOKEN"

Сравните число активных сессий со значением max_sessions в конфигурации.

Сессию отключили из панели#

Отключение зрителя из панели или через API — мягкое: сессия сразу исчезает из учёта, зритель по запросным протоколам заново проходит авторизацию на следующем запросе, а постоянное соединение закрывается при следующей отдаче. Принудительного разрыва сокета не происходит, поэтому «зритель ещё виден несколько секунд» — это норма. Действие записывается в журнал аудита:

curl -sS "https://media.example.com/api/v1/audit?action=kick&limit=20" \
  -H "Authorization: Bearer $TOKEN"

Что собрать перед обращением в поддержку#

# 1. Версия, состояние сервиса и последние 200 строк журнала.
systemctl status hastreamer --no-pager
sudo journalctl -u hastreamer -n 200 --no-pager > /tmp/hs-journal.txt

# 2. Ресурсы и лицензия.
curl -sS https://media.example.com/api/v1/system  -H "Authorization: Bearer $TOKEN" > /tmp/hs-system.json
curl -sS https://media.example.com/api/v1/license -H "Authorization: Bearer $TOKEN" > /tmp/hs-license.json

# 3. Состояние потоков и хранилищ.
curl -sS "https://media.example.com/api/v1/streams?limit=500" -H "Authorization: Bearer $TOKEN" > /tmp/hs-streams.json
curl -sS https://media.example.com/api/v1/storages -H "Authorization: Bearer $TOKEN" > /tmp/hs-storages.json

# 4. Внутренний журнал сервера (последние записи).
curl -sS "https://media.example.com/api/v1/logs?limit=500" -H "Authorization: Bearer $TOKEN" > /tmp/hs-logs.json
Не прикладывайте конфигурацию как есть

hastreamer.conf содержит пароль администратора, токены API, логины и пароли камер и ключи S3. Перед отправкой уберите все значения секретов. По той же причине не прикладывайте ответ GET /api/v1/config — он возвращает файл целиком, вместе с секретами.

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