Диагностика неисправностей
Симптом → вероятная причина → проверочная команда → способ починить. Страница рассчитана на дежурного инженера, которому нужно решить задачу сейчас.
Страница построена так, чтобы её можно было читать с середины. Найдите свой симптом в общей таблице, перейдите в его раздел, выполните проверочную команду — она отвечает на вопрос «в чём дело», а не «работает ли вообще».
Во всех примерах 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, а не через restartsystemctl 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]]}
Если путь потока содержит / (например 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_transportssource_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
Это сделано намеренно: объявленная, но отсутствующая звуковая дорожка «подвешивает» плеер целиком, и зритель не увидел бы даже видео.
Что делать — один из трёх вариантов:
- Перевести камеру на AAC, если модель это умеет. Правильное решение: звук будет во всех протоколах.
- Смотреть по WebRTC. Это единственный браузерный транспорт, который передаёт G.711:
https://media.example.com/cam1/embed.html?proto=webrtc. Требует включённого порта WebRTC. - Смотреть не в браузере. По RTSP звук G.711 передаётся как есть; в скачанном файле экспорта архива звук тоже сохраняется полностью.
Если у потока звук в 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.
Если 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 — он возвращает файл целиком, вместе с секретами.
Куда двигаться дальше#
- Производительность — если причина в нехватке ресурсов.
- Применение изменений — безопасный порядок правки конфигурации.
- Мониторинг и метрики — как заметить проблему до звонка пользователя.
- Модель доступа — устройство обоих уровней авторизации целиком.