Привет! На связи Ольга Попова, и вместе с Алексеем Гусевым @MAD_GooZe мы работаем в Yandex Infrastructure — это команда, которая создаёт и предоставляет внутреннюю инфраструктуру Яндекса — фундамент из продуктов и технологий, которые помогают разрабатывать, деплоить и эксплуатировать все основные сервисы компании. Наша команда носит название «Видеоплатформа» и занимается созданием инфраструктуры для показа видеоконтента на таких платформах, как Кинопоиск, Яндекс Музыка, Станция, Маркет и других.
Сегодня мы расскажем, как устроена архитектура CDN в нашей видеоплатформе, что мы сделали, чтобы она была удобной и контролируемой, как мы решали возникающие проблемы с помощью существующих стандартов и какое оптимальное решение в итоге нашли.
Для тех, кому удобнее смотреть, также добавили запись доклада в том самом плеере, который используется в Yandex Cloud Video и других сервисах, — и о роли которого мы тоже поговорим.
Когда начинаются проблемы с распределением пользователей
При разработке видеосервиса, вы обычно начинаете с базовой конфигурации: клиента с плеером и сервера для хранения данных.
Пока всё ограничивается одним сервером, система функционирует достаточно стабильно. Однако проблемы возникают, когда требуется, например, провести ремонтные работы. Тогда в лучшем случае пользователи увидят спиннер, а в худшем — экран смерти. Разумеется, посетитель сервиса недоволен, когда видит такое:

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

Решение простое — поставить больше серверов. Однако возникает новая задача: как распределить всех пользователей между несколькими серверами? Существует несколько подходов для решения этой проблемы, рассмотрим их по порядку.
BGP‑Anycast. Допустим, есть два дата‑центра с идентичным контентом: в Лондоне и в Нью‑Йорке. В обоих дата‑центрах над нашими серверами развёрнут L3-балансировщик, доступный в публичный интернет по одному и тому же IP‑адресу.

Когда плеер идёт за контентом, запрос пользователя может быть направлен в любой из этих дата‑центров. Решение о том, в какой дата‑центр направить запрос, принимает протокол BGP, благодаря которому работает интернет. Обычно запрос поступает в ближайший дата‑центр с точки зрения длины пути.

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

Если пользователь находится в Новосибирске, его запрос будет направлен к DNS‑серверу, который приземляет его в ближайший узел в Новосибирске.
Если же пользователь находится в регионе, где узлов рядом нет, — мы его отправим в узел по умолчанию.
Плюсы: система становится более отказоустойчивой, и у нас появляется возможность контролировать маршрутизацию трафика.
Минусы: если пользователь обращается к нашему DNS‑серверу не напрямую, а через другие сети, информация о его исходном местоположении может быть утеряна, что приводит к потере контроля над маршрутизацией.
Несколько URL. Мы можем объявить отдельные ссылки с соответствующими IP‑адресами для каждого CDN‑узла. На бэкенде у нас есть некий «магический компонент», который смотрит состояние: откуда пришёл пользователь и насколько загружены узлы. Он ранжирует доступные адреса и отдаёт плееру отсортированный список ссылок. Благодаря этому плеер может напрямую подключиться к наиболее подходящему edge‑серверу и загрузить контент непосредственно оттуда.

Плюсы:
Обеспечивается отказоустойчивость.
Мы контролируем, к какому узлу будет направлен запрос пользователя.
Это более быстрый способ по сравнению с использованием одного URL и балансировщика, который под капотом перенаправляет запросы на конкретные машины.
Минус: когда «магический компонент» отдаёт список URL, он актуален только на момент выдачи. Если пользователь смотрит фильм два часа, за это время edge‑сервер может выйти из строя или перезагрузиться. В таких случаях необходимо переключить пользователя на другой сервер, и эта задача ложится на плеер.
Нам важно не только распределять пользователей по серверам и машинам, но и контролировать стыки с операторами. Мы хотим уметь балансировать трафик, в том числе и на них. Поэтому наши edge‑сервера умеют отправлять трафик в заданных стык с конкретным оператором.
Как в этом случае у нас выглядит сама ссылка, или URL сегмента? За конкретным сегментом мы зашиваем параметры link id — в примере это lid id.
https://strm-25.strm.yandex.net/lid=310
Подробнее о том, как устроен наш бэкенд и CDN, рассказывали коллеги: Евгений Зайцев в докладе на VideoTech и Андрей Василенков в докладе на PlayButton. Они говорят про балансировку подробнее.
У нас получается такая ситуация: плеер подключается к конкретному узлу CDN и начинает воспроизведение контента. Пользователь смотрит контент с этого узла до тех пор, пока не возникает проблема с сервером. В таких случаях необходимо оперативно переключить плеер на другой сервер, чтобы обеспечить непрерывность просмотра.
Так мы переходим к проблеме отказоустойчивости на клиентах.
Как организовать управление с клиента
В описанной выше системе ответственность за отказоустойчивость лежит на «умном» плеере, который должен уметь определять проблемы и переключаться на резервные источники. Для этого существуют несколько механизмов.
HLS Redundant Streams. В мастер‑плейлисте указываются несколько ссылок на дублирующие друг друга левел‑плейлисты. Например, для качества 720p и 1080p может быть задано по две ссылки:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000, RESOLUTION=720x480
http://ALPHA.mycompany.com/lo/prog_index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000, RESOLUTION=720x480
http://BETA.mycompany.com/lo/prog_index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=500000, RESOLUTION=1920x1080
http://ALPHA.mycompany.com/md/prog_index.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=500000, RESOLUTION=1920x1080
http://BETA.mycompany.com/md/prog_index.m3u8
Впервые этот стандарт был представлен в 2017 году на WWDC17. Apple подчеркнули важность правильной обработки ошибок и корректной отправки статус‑кодов, что позволяет плееру более эффективно реагировать на неполадки.
Ключевая идея: плеер качает данные и слушает ответы сервера. По набору признаков он понимает, что что‑то идёт не так. Если он обнаруживает проблему, например, через статус‑код или длительный тайм‑аут, он предпринимает попытки повторного запроса. Если после нескольких попыток ответ так и не получен, плеер переключается на следующую ссылку.
DASH Multi‑BaseURL. В плейлисте перечисляются доступные baseURL для загрузки данных:
<?xml version=“1.0"?>
<MPD … >
<BaseURL>https://example.com/path/to/your/content/cdn1</BaseURL>
<BaseURL>https://example.com/path/to/your/content/cdn2</BaseURL>
<BaseURL>https://example.com/path/to/your/content/cdn3</BaseURL>
</MPD>
Все эти механизмы поддержаны в опенсорс‑библиотеках. Hls.js поддерживает Redundant Streams. В Shaka Player, Dash.js, Rx‑player поддержаны Multi‑BaseURL.
Однако стандарт DASH не уточняет, что делать с этими baseURL — как реагировать, что должно происходить. Так, например, в опенсорс‑библиотеке Shaka Player возможна ситуация, когда плеер пробует первую ссылку для загрузки первого сегмента и в случае неудачи переключается на вторую. При запросе за вторым сегментом он снова сначала обращается к первой ссылке, ждёт тайм‑аута или статус кода и только потом переключается на вторую. Shaka Player — stateless, он не «запоминает» предыдущие неудачные попытки запросов по определённым ссылкам.

Shaka Player не банит хосты — это нужно сделать самим. Что же в других опенсорс‑библиотеках? Dash.js банит до смены потока (до SetSource). Если текущая ссылка оказывается недоступной, её хост исключается из списка для текущего контента.
Rx‑player реализует блокирование хоста на время. Если проблемы с хостом повторяются в течение заданного времени, таймер сбрасывается.
В нашей системе мы усовершенствовали механизм блокирования, добавив возможность «разбана». Наш плеер переключается на следующий в списке хост, если возникают проблемы с текущим. Одновременно с этим существует процесс, периодически проверяющий доступность заблокированных хостов. Если хост начинает отвечать корректными статус‑кодами, он «разбанивается» и помещается в конец списка источников. Это предотвращает ситуацию, когда все хосты становятся недоступными.

Какие минусы в управлении с клиента?
Задержка в реакциях. Плеер с опозданием обнаруживает неполадки. Если сервер возвращает плохие статус‑коды вроде 500 или 403, плеер реагирует мгновенно. Однако, если данные поступают медленно или вовсе не поступают, это может быть выявлено лишь спустя длительное время, возможно, через 10 секунд. В это время у пользователя может исчерпаться буфер, и переключение на новый источник будет заметно.
Ограниченные возможности распределения нагрузки. Поскольку в начале воспроизведения плеер получает список доступных серверов без дальнейших обновлений, перераспределение нагрузки становится невозможным для VOD (Video on Demand).
Совместимость с телевизорами. На некоторых телевизорах поддерживаются стандарты MSS, HLS и DASH. Однако:
в MSS отсутствует механизм fail‑safe;
HLS отсутствует поддержка Redundant Streams;
В DASH, используемом на многих телевизорах, не поддерживаются BaseURL.
Получается, что для телевизоров у нас есть одно решение: нужно перезагружать плеер с повторным запросом данных. Это может выглядеть как перезагрузка страницы и приводит к нескольким проблемам:
Переключение не осуществляется бесшовно; пользователи могут увидеть спиннер или сообщение об ошибке.
Высока вероятность, что запросы снова попадут на проблемный хост, что вызывает недовольство у пользователей.
Как организовать управление с сервера: Content Steering
Проблемы, связанные с обеспечением стабильности на клиентской стороне, хорошо известны индустрии. В последние годы ведётся работа над новым стандартом — Content Steering, который предполагает серверный механизм управления потоками, что может значительно улучшить отказоустойчивость и опыт работы пользователей.
Основная идея: в систему вводится новый компонент — Steering Server, который мониторит производительность и доступность edge‑серверов, обеспечивая актуальную информацию об их состоянии. Когда плеер получает плейлист и идёт за данными, он параллельно запрашивает от Steering Server актуальную информацию о состоянии edge‑серверов. Если возникают проблемы с первым CDN, Steering Server оперативно предоставляет плееру обновленные рекомендации, которые позволяют бесшовно переключиться на другой, более надежный хост.

Компания Apple продвинула эту спецификацию первой и поддержала в своём стандарте HLS.
Как это реализовано? Вводится тег ContentSteering, в котором указывается ссылка на Steering Server, предоставляющий политики переключения, а также Pathway‑ID, обозначающий начальный edge‑сервер для плеера.
#EXTM3U
#EXT-X-CONTENT-STEERING:SERVER-URI="/steering?video=00012",PATHWAY-ID="CDN-A"
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AUDIO="A",PATHWAY-ID="CDN-A"
low/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7680000,AUDIO=“A",PATHWAY-ID="CDN-A"
hi/video.m3u8
Консорциум DASH пытается синхронизироваться с HLS, добавляя аналогичный тег ContentSteering, также указывающий ссылку на сервер для получения обновлений.
По ссылке есть все актуальные спецификации с последними обновлениями.
<?xml version=“1.0"?>
<MPD … >
<BaseURL serviceLocation="alpha">https://cdn1.example.com/</BaseURL>
<BaseURL serviceLocation="beta">https://cdn2.example.com/</BaseURL>
<ContentSteering
defaultServiceLocation="alpha"
queryBeforeStart="false"
>https://steeringservice.com/app/instance1234</ContentSteering>
</MPD>
У тега ContentSteering есть атрибуты:
defaultServiceLocation
— указание, с какого edge‑сервера надо начинать.queryBeforeStart
— указание предварительно запросить информацию с Steering Server до начала воспроизведения.
Что внутри Steering Manifest:
{
"VERSION": number, // REQUIRED, must be an integer
"TTL": number,
"RELOAD-URI": string,
“PATHWAY-PRIORITY”: [
…
],
“PATHWAY-CLONES”: [
…
]
}
VERSION
: версия спецификации.TTL
— срок действия данных.,RELOAD‑URI
— ссылка для перезапроса Steering Manifest, если она изменилась.PATHWAY‑PRIORITY
— определяет порядок источников загрузки. Каждый источник обозначен ID, а приоритет определяется его позицией в массиве.PATHWAY‑CLONES
— позволяет создавать новые хосты на базе существующих, используя Base ID для изменения домена хоста и добавления get‑параметров.
Pathway Priority является ключевым элементом в Steering Manifest, представляя собой список ID хостов, расположенных в порядке приоритета. Нулевой элемент массива обладает высшим приоритетом. Этот массив всегда включает по крайней мере один хост, и каждый ID в нём уникален. Важно, что клиенты игнорируют любые ID, которых не было в изначально заявленном списке. Это значит, что нельзя динамически добавлять новые хосты.
Здесь на помощь приходит дополнительная настройка Pathway Clones, которая позволяет создавать новые хосты на основе уже существующих ID. В Pathway Clones определяется Base ID — это исходный ID хоста, указанного в плейлисте, на базе которого можно создать новый. Новый хост приобретает новый ID, а URI‑REPLACEMENT задаёт правила того, как именно следует изменить исходный хост. Например, можно заменить домен и добавить дополнительные параметры. Ниже приводится пример такой настройки:
{
"VERSION": 1,
"TTL": 300,
"RELOAD-URI": "https://steeringserver.com?session=abc"
"PATHWAY-PRIORITY": ["gamma", "beta", "alpha"],
"PATHWAY-CLONES": [{
"ID": "gamma",
"BASE-ID": "alpha",
"URI-REPLACEMENT": {
"HOST": "cdn3.com",
"PARAMS": { "token-for-gamma": "tkn123456" }
}
}]
}
В этом примере gamma — это новый хост, созданный на основе alpha, с изменённым доменом и добавленными параметрами. Таким образом, хотя система ограничивает возможность добавления совершенно новых хостов, Pathway Clones предоставляет механизм для изменения существующих.
Какова поддержка в опенсорс‑библиотеках:
Dash.js: уже поддерживает Content Steering.
Shaka Player: реализовал поддержку как для DASH, так и для HLS.
Rx‑плеер: находится на стадии реализации, есть открытый пул‑реквест.
AVPlayer: поддерживает начиная с iOS версии 17.
ExoPlayer: также находится в процессе реализации с открытым пул‑реквестом.
Зачем всё это было нужно нам
Мы можем получить динамическое перераспределение нагрузки для VOD.
Оптимизация для live‑трансляций. В перспективе возможность снизить нагрузку за счёт отказа от постоянного опроса (polling) плейлистов.
Мы можем получить решение из коробки для телевизоров.
А ещё это очень модно, про это делают очень много докладов, и хочется потрогать что‑то новое и интересное.
Почему же нам не подошёл Content Steering:
Через Pathway Clones можно изменять только домен и query‑параметры, но не path. Это стало для нас преградой, так как мы не можем поменять lid‑id и указать конкретный стык с оператором.
Нет нативной поддержки SmartTV.
Content Steering — это действительно инновационная технология, которая особенно полезна, если вы переключаетесь между несколькими CDN (например, Akamai и AWS).
Но как вы понимаете, следующая часть — про наши решения.
Как организовать управление с сервера: своё решение
Что делает разработчик, когда никакое существующее решение на рынке ему не подошло? Правильно, разработчик строит велосипед.

Чтобы велосипед получился классным, удобным и применимым, а не какой‑то странной штукой, надо собрать требования к этому велосипеду. Нам нужно разработать механизм отказоустойчивой клиентской балансировки CDN‑хостов, который при этом позволяет:
работать со SmartTV,
балансировать «умных» клиентов (так я буду называть тех клиентов, где мы полностью контролируем работу плеера и формирование запросов — то есть всем сетевым взаимодействием),
более гибко управлять хостами по сравнению со спецификацией Content Steering.
Давайте разберёмся, что имеется в виду под балансировкой на телевизорах, на SmartTV и «умных» клиентах.
SmartTV. Как было упомянуто ранее, прошивочные плееры на этих устройствах не поддерживают стандартные BaseURL в Dash Manifest. Следовательно, мы не можем использовать наш обычный плейлист с несколькими BaseURL; вместо этого, мы должны предоставить плейлист, в котором указан только один конкретный хост.
Что произойдёт, если во время воспроизведения видео хост станет недоступным для клиента, например, из‑за сетевых проблем или временного сбоя самого хоста?

Плеер просто сломается. К сожалению, SmartTV‑прошивки ломаются с ошибкой, которая выглядит как «я упал». По этой ошибке очень редко можно понять: эта проблема связана непосредственно с воспроизведением видео или дело в сети, и мы просто не смогли скачать очередной сегмент. Ошибка непонятная, как на это реагировать — неизвестно.
Если ошибка сетевая, нам бы очень хотелось поменять хост, с которым прошивка работает. Тогда всё будет классно и мы продолжим воспроизведение. Какие требования мы предъявляем к нашему механизму для SmartTV:
Совместимость с существующими стриминговыми форматами. Мы не хотим тут изобретать какой‑то новый велосипед в плане формирования плейлиста, чтобы по‑прежнему иметь возможность отдавать текущие манифесты в прошивочный плеер.
Проверка доступности текущего хоста. Хотим уметь делать это из js‑рантайма, чтобы мы могли эту непонятную ошибку «я упал» разделить на две категории: сетевые проблемы и ошибка воспроизведения. И по‑разному их обработать.
Гарантия получения манифеста без недоступного BaseURL при перезапросе. Это актуально при переинициализации плеера, когда текущий хост становится недоступен и требуется гарантированно перебалансировать клиента на другой хост.
«Умные клиенты». На «умных» клиентах мы отдаём манифест с несколькими BaseURL. Но даже в этом сценарии может возникнуть ситуация, когда все три хоста становятся недоступными. И плеер при этом тоже сломается, куда деваться. Новые сегменты качать неоткуда.

Как это починить?
Нужна возможность сообщить клиенту о наличии дополнительного доступного хоста, на который можно безопасно переключиться.
Нужно обновление списка доступных BaseURL в реальном времени.
Перенос сложной логики работы с блеклистом хостов на сервер.
Как вы помните, у нас есть механизм, который регулярно проверяет статус заблокированных хостов. Представьте, что мы сообщаем плееру, что существует доступный хост № 4 и плеер переключается на него. Однако, если позднее выясняется, что хост № 1 снова стал доступным, возникает вопрос: должны ли мы оставаться на хосте № 4 или вернуться к хосту № 1?
С одной стороны, переключение может вызвать дополнительные накладные расходы на переустановление TCP‑соединения. С другой стороны, этот хост балансировщик отдал первым — значит, он с точки зрения балансировки на бэкенде более приоритетен в плане распределения нагрузки. Хочется такую механику унести целиком на сервер, чтобы сложные механизмы взаимодействия и переключения между хостами реализовать один раз на серверной стороне и не писать на всех клиентах. Особенно тяжело будет на нативных клиентах, где длинный хвост версий, которые нужно будет как‑то обновлять или отключать.
Подытожим. У нас уже появляется некая идея спецификации. А каждую большую штуку надо как‑то назвать.
Мы немножко покаламбурили, вспомнили, что мы сильно вдохновлялись Content Steering. Steering — это руль. И наш механизм обозвали Sextant в честь прикольного инструмента для морской навигации.

Сейчас я попробую объяснить эту спеку и рассказать, как мы с ней взаимодействуем. Далее буду всё объяснять на примере DASH. Довольно простым образом эта идея расширяется и на HLS, и на MSS.
Спецификация. Аналогично Content Steering у нас появляется некоторая ручка, которая называется Sextant Manifest. Но, в отличие от Content Steering, мы в эту ручку ходим POST‑запросом и передаём в неё текущий стейт, который храним на клиенте.
POST https://streaming.yandex.net/video-1234/sextant-manifest
Body {
"current_urls": [ string ],
"banned_urls": [ string ]
}
Эта ручка нам отвечает JSON-ом. В JSON есть несколько полей.
{
"ttl_seconds": number,
"base_urls": [
{
"id": string,
"ping_endpoint": string,
"base_url": string
},
…
],
}
Поле ttl_seconds — это время в секундах, когда мы считаем, что текущий Sextant Manifest актуален и можно продолжать им пользоваться. Далее у нас идёт массив base_urls — это информация о хостах. Каждый элемент массива содержит несколько полей:
id
— внутренний идентификатор, который мы в дальнейшем будем использовать для взаимодействия.ping_endpoint
— ручка на этом хосте, которую мы проверяем периодически, доступен хост или нет.base_url
— это значение, которое используется в теге BaseURL в Dash Manifest.
Как это применять на SmartTV. Для того чтобы начать взаимодействовать с Sextant, нам надо на уровне плеера определить две сущности.
CurrentBaseURL
— это значение BaseURL, прописанное в текущем манифесте, с которым работает встроенный плеер и с которого загружаются сегменты.BannedBaseURLs
— это множество заблокированных BaseURL, на которых плеер сломался и которые мы считаем недоступными после наших проверок.
Для BannedBaseURLs у нас будет некоторая задачка, запускающаяся по таймеру. Каждые 30 секунд она будет отправлять запрос на ping_url, чтобы проверить доступность каждого заблокированного хоста. Если хост становится доступным, он удаляется из списка.
Алгоритм работы для SmartTV. Перед началом воспроизведения мы запрашиваем sextant_manifest и в теле запроса за ним передаём в параметре banned_urls информацию об уже забаненных хостах. По умолчанию это пустой массив, который в дальнейшем будет заполняться в процессе воспроизведения, как описано ранее. Из полученного Sextant Manifest мы извлекаем массив base_urls и берём его нулевой элемент, чтобы сохранить его в CurrentBaseURL.
После этого запускаем воспроизведение на прошивке. Для этого в ссылку для получения манифеста, зашиваем ID того CurrentBaseURL, который до этого извлекли. Бэкенд у нас получается stateless, всю эту информацию мы храним на клиенте. За счёт механизма, который мы сделали, мы гарантируем, что серверный балансировщик ответит именно тем хостом, который мы попросили.
https://streaming.yandex.net/video-123/manifest.mpd?sextant_path={CurrentBaseURL.id}
Если в процессе что‑то ломается, мы проверяем две вещи.
Есть ли у нас на этом клиенте сейчас доступ в интернет. Для этого мы запрашиваем отдельную ручку, которая у нас висит на anycast‑е и всегда отвечает 200, — просто чтобы проверять, есть ли у нас сетевой доступ.
Доступен ли текущий хост, то есть у этого хоста проверяем ping_url.
При этом у нас возможны несколько исходов. Две ручки дернули — возможны четыре исхода.

И сеть, и BaseURL доступны, что, скорее всего, указывает на неисправность плеера прошивки. В этом случае мы его просто переинициализируем.
Сеть недоступна, но BaseURL доступен. Вообще говоря, такого быть не должно, потому что если один запрос ответил, то с интернетом всё нормально. Считаем, что это равнозначно первому исходу. Просто тоже переинициализируем плеер.
Сеть доступна, а вот наш конкретный BaseURL отвечать перестал. Это как раз тот самый кейс, который мы хотели пролечить. Хост, с которым мы работаем, перестал отвечать. В таком случае мы складываем CurrentBaseURL в множество забаненных хостов и перезапускаем воспроизведение с первого шага. То есть опять перезапрашиваем Sextant Manifest, передаём ему список забаненных хостов — и бэк в Sextant Manifest отвечает каким‑то новым множеством хостов, с которым мы можем работать. За счёт этого мы уносим работу с блеклистом на серверную сторону.
Когда обе ручки не ответили, это значит, что у клиента нет интернета. Мы показываем экран ошибки.
Что за счёт этого мы получаем? Первое: воспроизведение у нас больше не ломается полностью при сетевых проблемах. Второе: мы умеем понимать, с чем непосредственно связана ошибка воспроизведения в процессе и как её устранить. Либо это недоступность узла CDN, либо это какая‑то общая ошибка воспроизведения, которая лечится перезапуском прошивочного плеера. И третье: при переинициализации мы гарантируем, что снова не попадём в недоступный хост, за счёт того, что мы сложили этот хост в блеклист, передали бэку, и бэк нам его больше не отдаст.
Как это же самое применять на умных клиентах. Ещё раз напомню, это клиенты, где мы контролируем, сами парсим плейлист, сами пишем всю работу. В основном это веб‑ и android‑клиенты. Нам тут тоже потребуется определить несколько сущностей. В отличие от решения для SmartTV, мы будем хранить не один текущий BaseURL, с которым работаем, а очередь, которая будет называться у нас CurrentBaseUrls. И ещё будем хранить TTL — время жизни Sextant Manifest. И точно так же BannedBaseURLs мы будем раз в 30 секунд опрашивать, запрашивать ping_url и очищать хосты, которые снова стали доступны.
Алгоритм работы на умных клиентах. Перед стартом воспроизведения очищаем CurrentBaseUrls и запрашиваем Sextant Manifest. Достаём из него хосты, которые пришли, перекладываем их в очередь и запоминаем TTL.
Запускаем воспроизведение, скачиваем плейлист, начинаем играть. Если возникает сетевая ошибка и хост не отвечает, мы переносим текущий хост, который является первым в очереди, в список заблокированных. После этого продолжаем воспроизведение, используя дополнительные хосты из очереди, которые доступны для загрузки.
Через TTL секунд с момента первого запроса Sextant Manifest мы его перезапрашиваем и в теле запроса складываем ему всю необходимую информацию — очередь текущих хостов и забаненные хосты. Бэк этот запрос пережёвывает и понимает, что нам надо отдать. Формирует нам новую очередь, которую мы в дальнейшем будем использовать.
Перезаписываем TTL и запоминаем новое значение, чтобы опять через TTL секунд сделать всё то же самое. Соответственно, этот алгоритм мы повторяем раз в TTL секунд пока идёт воспроизведение текущей единицы контента
Что получаем? Во‑первых, не ломаем воспроизведение, если все хосты, полученные в изначальном манифесте, перестали быть доступными. Во‑вторых, обновляем хосты в реальном времени — можно делать ребалансировку. То есть клиенты, которые смотрят длинные фильмы, например, в процессе работы перенаправлять на другие хосты. И в‑третьих, мы реализуем работу блеклиста единообразно на серверной стороне — делаем это один раз для всех наших клиентов.
Результаты, которые мы получили. Самая важная метрика, на которую мы этим внедрением хотели повлиять, — это доля сессий с фатальными ошибками воспроизведения. И вот график той самой доли сессий на SmartTV.

Эта классная ступенька — выкатка взаимодействия Sextant Manifest на SmartTV, которую мы сделали вместе с нашими коллегами из Кинопоиска. Как видите, доля сессий упала почти в три раза. Это крутой и очень заметный результат.
Выводы
Чем сложнее архитектура на бэкенде, которая позволяет более точно управлять распределением трафика, тем сложнее логика на клиенте.
К сожалению, стандарты не всегда решают вашу задачу. Если вы делаете что‑то сложное, иногда приходится придумать что‑то своё.
Собственное решение, несмотря на сложность его разработки, может в итоге получиться гораздо более гибким и надёжным, чем то, что доступно в опенсорсе.
А ещё мы с коллегами ведём канал в Telegram «Страдания юного видеоинженера»: пишем там о новостях индустрии стриминга, видео и всего, что с этим связано. Если вы ещё не подписаны, то подписывайтесь, будем рады обсудить решения там.