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

Что плеер делает до первого кадра

HLS устроен как набор обычных HTTP-запросов. По описанию в RFC 8216, Media Playlist перечисляет сегменты, которые клиент получает по HTTP, а Master Playlist содержит варианты потока, между которыми клиент может выбирать в зависимости от доступной полосы. Отсюда и вытекает последовательность: плеер не может сразу просить видео, ему сначала нужно узнать, что именно просить.

Возьмём учебный пример. Цифры условные, они нужны только для того, чтобы увидеть структуру задержки. Допустим, на мобильной сети время одного обмена с сервером (с учётом установления соединения и TLS) составляет примерно 150 мс, а после установленного соединения последующие запросы идут по 80 мс до первого байта.

Условная цепочка запросов до первого кадра
ШагЧто происходитУсловное время
1Разрешение имени и соединение с сервером, TLS300 мс
2Запрос Master Playlist100 мс
3Запрос Media Playlist выбранного варианта100 мс
4Запрос первого сегмента: до первого байта плюс сама загрузка80 мс + загрузка
5Декодирование и показ первого кадракороткая пауза

Заметьте: к моменту, когда начинает скачиваться первый сегмент, прошло уже около полусекунды, а видеоданных ещё нет. Эти шаги идут строго друг за другом: пока плеер не получил Master Playlist, он не знает адрес Media Playlist, а пока нет Media Playlist, он не знает имя сегмента. Если на каждый шаг добавится ещё сотня-другая миллисекунд из-за плохого радиоканала, старт уже заметно растянется, даже если пропускная способность в целом хорошая. Поэтому «скорость» и «время старта» нельзя путать: первое про объём в секунду, второе про число последовательных обращений и их задержку.

Теперь сама загрузка первого сегмента. Если сегмент содержит 6 секунд видео при условных 2 Мбит/с, это около 12 Мбит, то есть примерно 1,5 МБ. При реальной скорости загрузки 10 Мбит/с он придёт за 1,2 секунды. При 3 Мбит/с уже за 4 секунды. Мобильная скорость в моменте может быть и выше, и ниже, чем показывал тест минуту назад. Так что к полусекунде подготовки прибавляется время, нужное, чтобы получить достаточно данных для начала показа. Разные плееры ведут себя здесь по-разному: одни начинают показ после первого сегмента, другие ждут нескольких, третьи могут стартовать с более лёгкого варианта и потом повышать качество. Единого правила для всех нет, и обещать одинаковое поведение на iOS, Android и в браузерном плеере не стоит.

Мастер-плейлист и выбор варианта

Master Playlist интересен тем, что с него начинается самое первое решение: какой вариант качества брать. Если плеер ещё не знает пропускной способности канала, он вынужден угадывать. Начать с тяжёлого варианта опасно: первый сегмент будет качаться долго. Начать с лёгкого безопасно, но картинка поначалу будет некрасивой. Что именно делает конкретный плеер, зависит от его настроек и реализации, и это стоит проверять на своём плеере, а не принимать на веру.

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

Длина сегмента и количество обращений

Длина сегмента задаёт ритм сетевой жизни. Сравним два условных потока с одинаковым битрейтом, 2 Мбит/с, но с разной длиной сегмента: 2 секунды и 10 секунд.

Условное сравнение коротких и длинных сегментов при 2 Мбит/с
ПараметрСегмент 2 сСегмент 10 с
Размер сегментаоколо 0,5 МБоколо 2,5 МБ
Запросов сегментов за минуту просмотра306
Объём данных до первого сегментамало, старт быстреебольше, старт медленнее при слабом канале
Влияние одного неудачного запросатеряется 2 секунды видеотеряется 10 секунд видео
Накладные расходы на заголовки и ожидание ответавышениже

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

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

Золотой середины, годной на все случаи, нет. Для ролика по запросу, который пользователь будет смотреть несколько минут, часто разумны средние значения, а для живой трансляции с требованием небольшой задержки сегменты придётся делать короче. Но выбирать надо по замерам на реальной аудитории, а не по чужим таблицам, включая эту.

Буфер как страховочный запас

Пока видео играет, плеер скачивает сегменты с опережением. Накопленные секунды готового к показу видео и есть буфер. Он нужен, чтобы пережить моменты, когда сеть не успевает. Мобильная сеть как раз славится неравномерностью: человек вошёл в лифт, проехал под мостом, переключился между вышками, и канал на несколько секунд проседает или вовсе пропадает.

Представим условный просмотр. Буфер заполнен на 20 секунд, видео идёт с битрейтом 2 Мбит/с. Сеть пропадает на 8 секунд. Плеер продолжает показывать из буфера, и зритель ничего не заметит: останется 12 секунд запаса, а потом канал вернётся и докачка пойдёт с повышенной скоростью. Теперь тот же провал на 8 секунд при буфере в 4 секунды. Через 4 секунды картинка замрёт, и зритель увидит кружок на оставшиеся 4 секунды плюс время на докачку сегмента.

Отсюда несколько следствий, которые не очевидны на первый взгляд.

  • Большой буфер защищает от кратких провалов, но для живой трансляции он означает большую задержку относительно эфира: чем больше запас, тем дальше зритель от реального времени.
  • Начало показа и устойчивость противоречат друг другу. Если плеер стартует сразу, как только получил один сегмент, старт быстрый, но буфер почти пуст. Если он ждёт накопления нескольких сегментов, старт медленнее, зато дальше просмотр надёжнее.
  • Сегмент должен скачиваться быстрее, чем длится. Если сегмент на 6 секунд видео качается 7 секунд, буфер будет постоянно таять, и рано или поздно настанет остановка, сколько бы ни было запаса вначале. Средняя скорость сети, большая битрейта, ещё не гарантия: важна скорость именно в моменты загрузки, а она на мобильной сети скачет.

Поэтому фраза «у меня 30 Мбит/с, а видео тормозит» не обязательно парадокс. Средняя цифра скрывает провалы. Для HLS важны не только среднее значение, но и худшие участки: сколько секунд длилась самая долгая пауза в ответах и успевал ли плеер за это время сохранить запас.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Что можно кешировать, а что нельзя

Теперь про кеширование. Здесь легко ошибиться, потому что файлы HLS выглядят однотипно, а ведут себя по-разному.

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

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

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

Как выглядит устаревший плейлист на практике

Возьмём условный сценарий: сегменты по 4 секунды, плейлист обновляется каждые 4 секунды, а кеш держит его 20 секунд. Зритель, пришедший в неудачный момент, получает плейлист, который на 15–20 секунд старше реального. Плеер начнёт с него, и зритель окажется на то же время позади эфира. Затем плеер запросит плейлист снова, а кеш продолжает выдавать ту же копию, пока не истечёт срок. На экране это выглядит как рывки: несколько сегментов проигрались, дальше пауза, потом сразу порция новых строк.

Диагностика простая по идее: сравнить плейлист, полученный напрямую с источника, и плейлист, полученный через кеш или CDN. Если номера последних сегментов различаются больше, чем на одну-две позиции, кеширование плейлиста слишком агрессивно.

Куда смотреть, когда виновник неизвестен

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

Как отличить причины задержки
ПодозрениеЧто видноЧто проверить
Долгий старт из-за цепочки запросовПауза до первого кадра, потом всё идёт ровноВремя каждого запроса: плейлисты и первый сегмент
Тяжёлый вариант качестваОстановки при хорошей средней скоростиРазмер сегментов выбранного варианта против фактической скорости загрузки
Устаревший плейлистОтставание от эфира, рывки, ошибки на удалённые сегментыСравнение плейлиста у источника и через кеш
Недоступный сегментОшибка ответа на конкретный запрос, повторыКоды ответов и наличие файла на диске
Слабый маршрут до сервераВысокая задержка и нестабильность у части зрителейСравнение разных сетей и расположения сервера относительно аудитории
Нехватка ресурсов сервераМедленные ответы у всех, в том числе на проводной сетиЗагрузка процессора, диска и сети на сервере

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

Что покажет статистика плеера и сервера

Хорошо, когда есть данные от самих зрителей. В документации Owncast о метриках перечислено, что показатели Stream Performance включают скорость загрузки видеосегментов, ошибки, переключения качества и задержку зрителя, при этом не все проигрыватели передают эти сведения. Для нас это полезный список того, что вообще стоит наблюдать, независимо от того, какое ПО вы используете: скорость скачивания сегментов, число ошибок, как часто плеер меняет качество и насколько зритель отстаёт от эфира.

Оговорка про плееры важна практически. Если ваш плеер не сообщает статистику, придётся собирать данные иначе: смотреть логи веб-сервера, где видны запросы и коды ответов, и проверять на конкретных устройствах вручную. Серверные логи не покажут остановок на экране, но покажут паттерн запросов: если зритель запросил сегмент номер 120, а следующий запрос пришёл через 15 секунд при длине сегмента 4 секунды, значит, где-то был провал.

Старт и устойчивый просмотр: две разные цели

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

  • Чтобы ускорить старт: меньше последовательных запросов (например, чтобы плейлисты отдавались быстро и с того же соединения), лёгкий первый вариант, короткие первые сегменты, соединение к ближайшему узлу.
  • Чтобы уменьшить остановки: запас буфера, разумная лесенка качеств с лёгкой нижней ступенью, стабильный ответ сервера на запросы сегментов, корректное кеширование.

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

Какое место занимает VPS

Ресурсы сервера всё же имеют значение, просто не там, где их обычно ищут. VPS отвечает за три вещи: за раздачу файлов (сеть и диск), за кодирование (процессор, если вы делаете варианты на лету) и за расположение относительно зрителей. Для раздачи готовых сегментов обычно хватает скромных ресурсов, а кодирование нескольких вариантов в реальном времени прожорливо: как отмечает руководство Owncast, число вариантов, разрешение, частота кадров и битрейт влияют на затраты ресурсов.

На VPS MAATRIX доступны конфигурации от Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) до Djed (16 vCPU, 32 ГБ, 320 ГБ), с NVMe-накопителями, процессорами AMD EPYC и root-доступом. Среди локаций в публичном предложении названы UK, US, RU и FR, что позволяет подобрать расположение ближе к аудитории. Эти параметры стоит считать отправной точкой для теста, а не обещанием, сколько зрителей выдержит тот или иной тариф: ёмкость определяется вашим битрейтом, числом вариантов и тем, кодируете ли вы видео на этом же сервере. Разумный путь: начать с одного варианта, как советует руководство Owncast, замерить, потом добавлять ступени и смотреть, как меняется загрузка процессора и сети.

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

Как измерить, а не угадать

Всё сказанное выше складывается в простой эксперимент, который можно провести без специальных инструментов. Возьмите телефон, выйдите на мобильную сеть, не на Wi-Fi, и несколько раз запустите один и тот же поток. Записывайте две величины: время от нажатия до первого кадра и число остановок за, скажем, пять минут просмотра. Повторите в разное время суток и в разных местах: дома, на улице, в транспорте. Одного запуска мало: мобильная сеть слишком изменчива, поэтому смотрите на разброс, а не на лучший результат.

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

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