Инструментирование QoE на стороне плеера

Автор: Николай СапуновОбновлено: август 202613 мин чтения
Содержание статьи +

Коротко

Качество восприятия (quality of experience, QoE) – это то, что реально чувствует зритель: как быстро началось видео, как часто оно замирало, насколько чётким было и не упало ли вовсе. Эти числа честно может измерить только плеер, потому что лишь он знает, когда опустел его собственный буфер и когда переключилось качество. Инструментирование QoE на стороне плеера – это инженерная работа по захвату таких событий внутри плеера и отправке их с устройства небольшими сообщениями (биконами) в аналитический бэкенд. Эта статья объясняет, что измерять, как плеер измеряет это по стандартным событиям браузера и платформ и как надёжно и дёшево вытащить данные наружу – включая стандарты, которые держат смысл слова «ребуферинг» одинаковым у всех вендоров; дашборды живут в блоке аналитики, на который мы ссылаемся.

Почему это важно

Если вы запускаете OTT-сервис (over-the-top – видео по открытому интернету, а не по кабелю или спутнику), вы не сможете улучшить тот опыт, который не видите. Основатель медиапродукта или продакт-менеджер, выпустивший плеер без инструментирования, летит вслепую: каждая жалоба анекдотична, каждое «часто буферит» нельзя опровергнуть, и нет числа, к которому можно привязать инженерное исправление. Хуже того, цена плохого QoE измерима и велика – зрители бросают потоки, которые медленно стартуют или замирают, а связь между ребуферингом и потерянным временем просмотра – одно из самых воспроизводимых наблюдений в исследованиях стриминга. Эта статья даёт вам словарь и модель, чтобы поставить задачу на инструментирование QoE, оценить аналитический SDK вендора и точно понимать, какие числа ваши инженеры должны выводить на дашборд.

Четыре числа, которые обязан слать плеер

Сведите QoE к сути – и получите четыре числа. Это позвоночник любого продукта стриминговой аналитики; плеер, который не отдаёт их честно, не инструментирован.

Первое – время старта (time-to-first-frame): сколько зритель ждёт от нажатия play до первого кадра видео. Оно измеряется в миллисекундах или секундах и формирует самое сильное первое впечатление о сервисе. Второе – ребуферинг: как часто и как надолго картинка замирала посреди воспроизведения, потому что у плеера кончилось буферизованное видео. Буфер – это те несколько секунд уже скачанного видео, что плеер держит впереди зрителя; когда он пустеет, экран замирает, и этот момент называют ребуфером (или stall). Третье – фактически доставленный битрейт: среднее качество, которое получил зритель, в килобитах или мегабитах в секунду, потому что сервис может стартовать быстро и ни разу не замереть, но всё это время отдавать мягкое, низкое качество. Четвёртое – ошибки: сбои, которые зритель действительно увидел, от так и не начавшегося видео до упавшего на середине.

Рядом с ними едет пятое число – потерянные кадры: кадры, которые устройство декодировало, но не успело вывести вовремя; так вы ловите поток, который воспроизводится без замираний, но дёргается на слабом устройстве. Вместе эти пять чисел говорят, был ли опыт сессии хорошим.

Рис. 1. Числа, которые обязан слать плеер, и стандартный сигнал для каждого. Старт и ребуферинг – из событий media element; битрейт – из активного rendition; ошибки – из события error; потерянные кадры – из API качества воспроизведения.

Точные формальные определения этих метрик – крайние случаи, правила подсчёта, академическая база – это тема статьи метрики QoE видео в нашем разделе Video Streaming, и мы применяем их в масштабе в QoE: время старта и ребуферинг. Эта статья остаётся на стороне плеера: как плеер захватывает каждое число и отправляет наружу.

Почему инструментирование должно жить в плеере

Заманчиво думать, что QoE можно мерить с сервера – считать запросы, смотреть логи CDN, остальное вывести. Нельзя, и причина проста: сервер видит байты, уходящие из кэша, но только плеер знает, что испытал зритель. CDN не скажет, что буфер опустел на 47-й секунде и картинка замерла на две секунды; плеер скажет, потому что это его буфер иссяк. CDN не скажет, что зритель нажал play и ждал четыре секунды; плеер скажет, потому что это он запустил секундомер. QoE – это правда на стороне клиента, а инструментирование – то, как эта правда покидает устройство.

Подставим число – зачем эта инженерия. Самое цитируемое исследование стриминга – анализ данных Conviva, представленный Добрианом с коллегами на ACM SIGCOMM в 2011 году – показал, что доля ребуферинга (часть времени сессии, проведённая в заморозке) – это метрика, сильнее всего связанная с тем, как долго люди смотрели, и что даже небольшой рост буферизации стоит заметного времени просмотра. Широко цитируемая формулировка: рост доли буферизации примерно на один процентный пункт может стоить нескольких минут просмотра за сессию.

Пройдём арифметику. Пусть рост доли ребуферинга на один пункт стоит трёх минут просмотра за сессию, а вы отдаёте миллион сессий в месяц. Это 1 000 000 × 3 = 3 000 000 минут, то есть около 50 000 часов просмотра, потерянных за месяц из-за одного пункта ребуферинга. Для рекламного сервиса, который продаёт эти минуты, или для подписочного, чья удерживаемость идёт за вовлечением, это прямая линия к выручке. Инструментирование – это то, что превращает «как-то лагает» в «ребуферинг вырос на 1,2 пункта на Android TV в прошлый вторник» – проблему, которую инженер может реально починить.

Как плеер измеряет каждое число

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

Время старта – это секундомер. Плеер записывает метку в момент, когда зритель запросил воспроизведение, ещё одну – на первом отрисованном кадре, и отдаёт разницу. В вебе конец секундомера – первое событие playing у media element (или более точный сигнал кадра, где платформа его даёт); начало – клик пользователя или триггер autoplay. Правило, на котором спотыкаются команды: если плеер показывает рекламу до контента, осознанно решите, входит ли загрузка рекламы во время старта, и меряйте старт контента отдельно, чтобы медлительность рекламного сервера не пряталась в вашем числе.

Ребуферинг ограничен двумя событиями, которые точно определяет стандарт HTML. Когда у плеера посреди воспроизведения кончаются буферизованные данные, media element шлёт событие waiting, и воспроизведение встаёт; когда данных снова хватает, он шлёт playing, и оно возобновляется. Промежуток между waiting и следующим playing – это один stall. Сложите эти промежутки, поделите на общее время просмотра – и получите долю ребуферинга. Хитрость в том, чтобы исключить замирания, которые зритель вызвал намеренно: waiting после перемотки (зритель прыгнул в новое место) – не сбой качества и не должен считаться таковым.

Минимальный таймер stall показывает форму этого. Код иллюстративный, не продакшен:

// Измеряем ребуферинг по стандартным событиям media element.
const video = document.querySelector('video');
let stallStart = null;
let rebufferMs = 0;
let seeking = false;

video.addEventListener('seeking', () => { seeking = true; });
video.addEventListener('seeked',  () => { seeking = false; });

video.addEventListener('waiting', () => {
  // Замирание, которое зритель не вызвал перемоткой.
  if (!seeking && stallStart === null) stallStart = performance.now();
});

video.addEventListener('playing', () => {
  if (stallStart !== null) {
    rebufferMs += performance.now() - stallStart; // один stall измерен
    stallStart = null;
  }
});

Доставленный битрейт отслеживают, а не ловят из одного события. Плеер в каждый момент знает, какая ступень лестницы кодирования (набора уровней качества, из которых он выбирает) активна, поэтому записывает каждое переключение качества с меткой времени и считает средневзвешенное по времени за сессию. Сессия, проведшая 90 секунд на 5 Мбит/с и 10 секунд на 1 Мбит/с, доставила в среднем (90 × 5 + 10 × 1) ÷ 100 = 4,6 Мбит/с, а не простую середину. Глубокая логика того, почему плеер переключил качество – алгоритм адаптивного битрейта – разобрана в инженерии видеоплеера; для инструментирования вам нужно лишь логировать каждое переключение.

Ошибки приходят из события error у media element и объекта MediaError, который оно несёт: его код примерно говорит, что пошло не так (прервано, сеть, декодирование или неподдерживаемый источник). Задача инструментирования – классифицировать каждую ошибку по двум осям: не дала ли она зрителю увидеть видео вообще (сбой старта) или прервала уже идущий поток (сбой воспроизведения), и была ли она фатальной (сессия закончилась) или восстановимой (плеер повторил и продолжил). Mux Data, вендор стриминговой аналитики, проводит ровно эту черту – его Video Startup Failure считает ошибки до первого кадра, а Playback Failure – фатальные ошибки во время воспроизведения – и различие важно, потому что у них разные причины и разные исправления.

Потерянные кадры приходят из небольшого API браузера: вызов getVideoPlaybackQuality() у video element возвращает объект VideoPlaybackQuality, чьё значение droppedVideoFrames – число кадров, которые устройство отбросило до декодирования или из-за того, что они не успели к сроку показа. Периодический опрос этого счётчика – то, как меряют плавность: метрика ловит 4K-поток, дёргающийся на ТВ, чей декодер не справляется, хотя формально он не замирает.

Рис. 2. Одна сессия под инструментированием. Время старта – промежуток от play до первого кадра; каждый промежуток waiting→playing – ребуфер; битрейт отслеживается по переключениям; событие error с кодом MediaError классифицируется и логируется.

От событий к биконам: как вытащить данные с устройства

Измерить внутри плеера – половина дела; данные должны покинуть устройство, чтобы быть полезными. Уходит наружу бикон – небольшое сообщение, обычно JSON, которое плеер постит на эндпойнт-сборщик. Наивный подход – обычный сетевой запрос в момент закрытия страницы – это ровно то место, где команды теряют важнейшие данные: браузеры рутинно отменяют запросы «на лету» при выгрузке страницы, поэтому закрывающий бикон сессии – несущий итоговые суммы – так и не доходит.

Веб-платформа решила это специальным инструментом. Метод navigator.sendBeacon() из Beacon API ставит в очередь небольшой POST, который браузер обещает отправить, даже когда страница уходит, не задерживая следующую навигацию. Спецификация W3C ограничивает поставленную в очередь нагрузку 64 кибибайтами, так что биконы по дизайну остаются маленькими. Надёжный триггер – событие visibilitychange: когда видимость страницы переходит в hidden (а это событие гарантированно срабатывает, когда мобильный пользователь сворачивает приложение), плеер сбрасывает финальный бикон.

// Сбрасываем сводку QoE сессии, когда страница скрыта — переживает сворачивание.
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    const summary = JSON.stringify({
      sessionId, startupMs, rebufferMs, avgBitrateKbps, errorCount, droppedFrames
    });
    navigator.sendBeacon('/qoe/collect', summary); // в очереди, отправится даже при выгрузке
  }
});

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

«Частая ошибка: потерять закрывающий бикон сессии. Самый частый баг инструментирования, который мы видим, – отправка финальной сводки QoE обычным запросом на событии unload. Браузеры его отменяют, мобильное сворачивание его убивает, и сессии, закончившиеся плохо, – ровно те, что нужнее всего видеть, – пропадают, тихо смещая дашборды к счастливому пути. Всегда сбрасывайте закрывающий бикон через sendBeacon (или очередь фоновой выгрузки платформы) на visibilitychange → hidden, никогда – отменяемым запросом на unload.»

CMCD: стандарт, на котором плеер говорит с CDN

Биконы шлют QoE в вашу аналитику. Есть второй, дополняющий канал, который шлёт срез той же клиентской правды тем, кто доставляет ваши байты, – в сеть доставки контента (CDN), сеть пограничных серверов, что кэширует и отдаёт ваше видео. Стандарт для этого – Common Media Client Data (CMCD), опубликованный Consumer Technology Association как CTA-5004. С CMCD плеер прикрепляет небольшой набор полей к каждому запросу сегмента – как HTTP-заголовок или параметр строки запроса – чтобы CDN мог сопоставить свои логи с тем, что испытывал клиент.

Поля – это короткие двухбуквенные ключи: bl – длина буфера (сколько миллисекунд видео буферизовано), br – кодированный битрейт запрашиваемого сегмента, mtp – измеренная пропускная способность, sid – идентификатор сессии, cid – идентификатор контента, bs – флаг голодания буфера, который плеер ставит, только что замерев. Поскольку CDN теперь видит, запрос за запросом, какие клиенты были близки к голоданию, он может заметить проблемы доставки, скрытые в чистых серверных логах, – региональный edge, в среднем нормальный, но рутинно дающий буферам просесть. CMCD поддержан в основных плеерах: dash.js и hls.js в вебе и ExoPlayer/Media3 на Android – все его реализуют.

CMCD – ещё и живая, датированная деталь, которую стоит отслеживать. Вторая версия спецификации вышла в феврале 2026 года как CTA-5004-A; она добавляет режим отчётов по событиям (клиент может слать отчёт-бикон, привязанный к событию, а не только ехать на запросе сегмента), новые ключи и структурированную кодировку значений полей. Как с любой развивающейся спекой, перед тем как полагаться на ключ только из v2, сверьте версию, которую поддерживают ваши плееры и CDN.

Рис. 3. Два канала с устройства. Биконы сессии (sendBeacon) несут сводку QoE в вашу аналитику; поля CMCD едут на каждом запросе сегмента в CDN. Оба питают дашборды блока аналитики.

Одно определение «ребуферинга»: CTA-2066

Вот проблема, кусающая любую мульти-вендорную платформу. Спросите два аналитических инструмента про ваш «ребуферинг» – и можете получить два разных ответа, потому что каждый посчитал его по чуть иным правилам: считается ли stall во время перемотки, является ли ожидание старта ребуфером, где кончается один stall и начинается следующий. Когда определения расходятся, дашборды перестают совпадать, и числу никто не верит.

Стандарт Consumer Technology Association CTA-2066, Streaming Quality of Experience Events, Properties and Metrics, существует ровно для того, чтобы это починить. Он стандартизирует набор событий плеера, свойств и метрик QoE – и, что важно, как каждую метрику следует вычислять – так, чтобы «ребуферинг» и «время старта» значили одно и то же у разных плееров и аналитических вендоров. Инструментировать плеер под общее определение вроде CTA-2066 (или выровняться по вендору, который это делает) – то, что позволяет сравнивать число из вашего iOS-приложения с тем же числом из веб-плеера и ТВ-приложения без звёздочки.

Таблица делает ответственность за захват конкретной.

Метрика QoEЧто захватывает плеерСтандартный сигнал / определениеЗахват в плеере?
Время стартаЗапрос play → метка первого кадраПервый playing / сигнал первого кадраДа – знает только плеер
Доля ребуферингаΣ(waiting→playing) ÷ время просмотраСобытия HTML media; правила CTA-2066Да – знает только плеер
Доставленный битрейтВзвешенный по времени активный renditionСтупень лестницы кодирования во времениДа
Ошибки старта / воспроизв.Событие error + код MediaErrorФатальная vs восстановимая; до/после кадраДа
Потерянные кадрыСчётчик droppedVideoFramesW3C Media Playback Quality APIДа (веб) / API платформы
Доставка с привязкой к edgebl, br, mtp, bs на запросCMCD (CTA-5004) заголовок/запросДа – в CDN

Своё или покупное: решение про QoE SDK

Почти ни один OTT-сервис не пишет всё это вручную на каждой платформе. Логика захвата выше несложна в одном плеере; сделать её согласованной на вебе, iOS, Android и четырёх видах ТВ – и поднять за ней конвейер сбора и дашборды – вот настоящая работа. Поэтому большинство команд берут QoE-аналитический SDK: Mux Data, Conviva и Datazoom – частые варианты, каждый поставляет покадровый сборщик под платформу, захватывающий метрики выше, и бэкенд, который их агрегирует. Что вы получаете от них – широту (один согласованный набор метрик на всех клиентах) и дашборды; что отдаёте – часть контроля и цену за сессию.

Решение той же формы, что и выбор «своё или покупное» для плеера в плеер на каждом экране: написать захват редко самое трудное, трудна интеграция и согласованность. Аналитический бэкенд, который кормят эти SDK, – дашборды, алерты и виды аудитории и вовлечения – это тема статьи стек измерения QoE и более широкой карты аналитики OTT. Каким бы путём вы ни пошли, принципы инструментирования из этой статьи – это контракт, которому SDK обязан соответствовать: захват в плеере, надёжный бикон наружу и выравнивание определений по стандарту.

Где здесь Фора Софт

Фора Софт делает софт для видеостриминга, OTT и Internet-TV, конференцсвязи, e-learning, телемедицины и видеонаблюдения с 2005 года – более 250 выпущенных проектов для 400+ клиентов. Инструментирование QoE на всей матрице устройств – ровно та задача масштаба, под которую мы построены: сервису, который должен доказывать быстрый старт и низкую долю замираний для сотен тысяч одновременных зрителей, на экранах от дешёвого smart TV до новейшего телефона, нужны захват событий, транспорт биконов, разводка CMCD и стандартизация метрик, описанные здесь, реализованные одинаково на каждом клиенте. Мы вендор-нейтральны – мы инструментируем под стандарты и подключаем Mux, Conviva или Datazoom под задачу, а не продаём один инструмент – и ведём со скейл- и стоимостного требования, прежде чем перейти к списку фич.

Главное

  • QoE – это четыре числа (время старта, ребуферинг, доставленный битрейт, ошибки) плюс потерянные кадры; честный источник – только плеер.
  • Браузер уже шлёт события: waiting/playing ограничивают stall, error несёт сбой, API качества считает потерянные кадры.
  • Исключайте перемотку из ребуферинга и решите, входит ли загрузка рекламы в старт, иначе числа врут.
  • Шлите закрывающий бикон через sendBeacon на visibilitychange → hidden – никогда отменяемым запросом на unload.
  • CMCD (CTA-5004) шлёт клиентские данные в CDN; CTA-2066 стандартизирует смысл метрик у разных вендоров.
  • Большинство команд берут QoE SDK (Mux, Conviva, Datazoom) ради кросс-платформенности; принципы здесь – контракт для него.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.