Skip to content

Захват потока по HLS

Sapsan следует за чужим HLS-плейлистом в режиме клиента (pull): опрашивает плейлист, забирает сегменты и отдаёт кадры в тракт наравне с RTSP, SRT или MPEG-TS. Так отдаётся почти всякий агрегатор, всякая чужая CDN и всякий партнёр, которому не разрешили открыть UDP или SRT.

Настройка

streams:
  - name: chan1
    inputs:
    - hls:
        url: https://cdn.example.com/live/index.m3u8
Параметр Умолчание Описание
url адрес плейлиста ровно в том виде, как его записал оператор
headers нет заголовки ко всем запросам к источнику: плейлист, сегменты, ключи
variant_mode single single — один вариант мастера, ladder — вся лесенка
variant_bandwidth нет к какому битрейту подбирать вариант (ближайший снизу); без него берётся старший по BANDWIDTH
skew_threshold_ms 500 с какого расхождения рендишенов лесенка считается несинхронной
skew_policy report что делать при вердикте skewed: report — продолжать и показывать, fallback_to_single — откатиться на один вариант
connect_timeout_ms 5000 таймаут установления соединения
read_timeout_ms 15000 таймаут получения тела: плейлиста, сегмента, ключа
max_body_bytes 64 МиБ потолок размера одного ответа
peer_timeout_ms общий сколько ждать кадров, прежде чем считать источник потерянным

Незаданное поле означает умолчание сервера, а не «выключено».

Схему допустимо записать тремя способами, все они равнозначны:

  • https://host/path.m3u8 — распознаётся по расширению пути, query не мешает;
  • hls://host/path.m3u8 — то же, что http://;
  • hlss://host/path.m3u8 — то же, что https://.

Как добавить источник в консоли

Откройте стрим, вкладка Источники, кнопка + Добавить источник, протокол — HLS. Обязательное поле одно: URL — адрес плейлиста.

Добавление HLS-входа

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

Если источник требует токен, нажмите Добавить заголовок и заполните имя, затем значение. Строка становится настоящим заголовком в тот момент, когда у неё появляется имя: заголовок без имени сервер не примет и на провод он не уйдёт.

Заголовки запросов к источнику

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

Приём лесенки и порог расхождения

Не забудьте Сохранить: правка входа уезжает на сервер только по кнопке.

Дальше состояние источника видно на этой же вкладке. Карточка в блоке Здоровье источников показывает вердикт лесенки и худшее расхождение:

Вердикт лесенки на карточке источника

А построчная разбивка по рендишенам — заявленное и фактическое расхождение, тренд, непарные сегменты и расхождение по ключевым кадрам — лежит ниже, в блоке Расхождение рендишенов:

Разбивка расхождения по рендишенам

Правило старта

Всё, что лежало в плейлисте на момент подключения, — история. Захват начинается с первого сегмента, появившегося после подключения; так же он ведёт себя и на реконнекте.

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

Аутентификация

Два способа, их можно сочетать:

  • заголовкиheaders, например Authorization: Bearer <token>; уходят с плейлистом, вариантами, init-сегментами, сегментами и ключами того же origin и не уходят на чужой origin после редиректа;
  • учётные данные в самом URL — обычная Basic-аутентификация средствами HTTP-клиента.

Значения заголовков в логи не попадают: печатаются только имена. Адрес входа пишется в логи целиком и в ответах управляющего API отдаётся как есть — где именно в строке адреса секрет, знает только оператор, и маскировка наугад либо оставила бы токен в query, либо срезала бы то, по чему вход опознают в логе.

Лесенка и расхождение рендишенов

При variant_mode: ladder захватываются все варианты мастера. Опорным берётся старший по BANDWIDTH вариант, остальные сравниваются с ним посегментно — по номеру медиа-последовательности. Вердикт виден в статистике входа, поле hls_ladder:

  • synced — рендишены идут вместе, переключение между ними безопасно;
  • skewed — расхождение превысило порог либо сломана структура: переключение даст рывок или рассинхрон;
  • unmeasurable — сравнивать не с чем, нет пар сегментов с общим номером.

Расхождение меряется двумя способами сразу. Заявленное — разница EXT-X-PROGRAM-DATE-TIME в разметке, фактическое — разница медиавремени первых кадров. Разошлись они между собой — болен не поток, а разметка, и чинить это владельцу источника.

Note

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

Политика fallback_to_single откатывает захват на один вариант при вердикте skewed — для части источников это единственный рабочий режим. Откат живёт до перезапуска входа.

Шифрование

Поддержаны AES-128 (сегмент целиком) и SAMPLE-AES (покадрово, для H.264 и AAC). Ключ забирается тем же клиентом и с теми же заголовками, что и остальное, и кэшируется по адресу, поэтому ротация ключа посреди плейлиста работает сама собой — у нового ключа другой адрес.

Молчащий сервер ключей — это ошибка входа с адресом ключа и кодом ответа; тело сегмента при этом не качается вовсе, расшифровать его всё равно нечем.

CENC/DRM (Widevine, PlayReady, FairPlay) не поддерживается и отклоняется по имени KEYFORMAT.

Что поддержано

Возможность Состояние
Медиа-плейлист: EXT-X-MEDIA-SEQUENCE, EXTINF, EXT-X-ENDLIST есть
Мастер-плейлист: выбор варианта, рендишены EXT-X-MEDIA есть
Сегменты fMP4 (EXT-X-MAP) и MPEG-TS есть
EXT-X-BYTERANGE есть
EXT-X-DISCONTINUITY, пересоздание плейлиста, отставание от окна есть
EXT-X-GAP есть, пропуск без ошибки
EXT-X-PROGRAM-DATE-TIME — привязка медиавремени к UTC есть
Шифрование AES-128 и SAMPLE-AES (H.264, AAC) есть
Лесенка: приём всех вариантов, измерение расхождения есть
LL-HLS: EXT-X-PART, blocking reload нет, части игнорируются
CENC/DRM нет
«Упакованный звук»: голый ADTS или ID3+AAC вместо контейнера нет, формат называется в ошибке
Субтитры TTML и WebVTT-сегменты нет, дорожка распознаётся и пропускается
Захват MPEG-DASH нет

Применение изменений

Перезапуска входа требует только смена url, headers и HTTP-настроек (connect_timeout_ms, read_timeout_ms, max_body_bytes): у нового источника своя нумерация дорожек, свой init и своя позиция в плейлисте. Остальное — режим варианта, порог и политика расхождения, peer_timeout_ms — применяется на лету, без разрыва вещания.

Проверка

Откройте http://server/streaming/v/chan1/index.m3u8 в плеере или запросите скриншот http://server/streaming/live-preview-jpeg/chan1.

Смотреть стоит не только «кадры пошли», а три вещи:

  • отставание выдачи от края плейлиста — без него жалоба «поток отстаёт» неотличима от «источник отстаёт», а лечатся они в разных местах;
  • пропуски сегментов и их разбивку: вымыло из окна, отменён на лету, объявлен EXT-X-GAP. Последнее — не ошибка источника, а его честное заявление;
  • стык соседних фрагментов — дефект тихий: сегменты скачаны все, ошибок разбора нет, а медиа внутри них не стыкуется, и в архиве останется провал.

Что дальше