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

Об одной истории из практики компании рассказал технический директор YuSMP Group Никита Обухов. Команде надо было создать стриминговый сервис с разным уровнем доступа и при этом сохранить лучшее качество связи. О том, как все получилось, читайте в статье.

<sup>иллюстрация skinwalletcom<sup>

Как работает система

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

Всего в проекте было 3 роли: Стример, Пользователь, Администратор. Реализовали клиентскую часть и админ-панель.

Для стриминга использовалась сторонняя программа — OBS Studio или любая совместимая. Каждый стример получал в приложении адрес сервера, куда будет транслироваться его поток и секретный ключ авторизации. Он вводил их в OBS студию перед первой трансляцией. Как только стример нажимал Start в OBS, бэкенд понимал, что начался эфир и стример появлялся на главной странице в режиме «Онлайн».

Проблемы и решения

Качество и скорость трансляции

Для просмотра трансляций мы выбрали формат HLS. Видео доставляется в реальном времени в 3 вариантах качества: высокое, среднее и низкое. Поток из OBS в реальном времени транскодировался в эти три варианта. В плеере можно переключиться между качествами, а также есть режим Auto — в зависимости от скорости интернета, плеер сам выбирал подходящее разрешение. Все фрагменты видео после транскодинга попадали на CDN, чтобы дать лучшую скорость для пользователей со всего мира. 

Однако формат HLS имеет один существенный недостаток — серьезную задержку, порядка 10–15 секунд. И хотя мы внедрили последнюю надстройку протокола — Low latency HLS, всё равно разница оставалась существенной, как если бы Стример был на Луне. Такая задержка приемлема для публичных трансляций, но она очень плоха для эфиров, где стример общается напрямую со зрителем как в групповых, так и в личных чатах.

Для таких типов трансляций понадобилась другая технология — WebRTC. Она позволяет вести стриминг напрямую из браузера. Этот протокол был специально разработан для живого общения: имеет очень низкую задержку и используется в Google Meet. Ценой за скорость стало качество картинки — оно заметно хуже, чем в HLS. 

Конфликт протоколов 

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

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

Высокая нагрузка на ЦПУ

Стоит отметить, что нагрузка на ЦПУ при стриминге была довольно велика, особенно в качестве 4К, и стримерам необходима производительный ПК.

Протокол WebRTC позволяет участникам созваниваться друг с другом без использования центрального сервера (архитектура Mesh). Несмотря на очевидную привлекательность такой архитектуры, она имеет существенные недостатки — очень высокую нагрузку на ЦПУ, по причине того, что транскодинг происходит на устройстве пользователя (если с камеры идёт изображение 4К, его нужно сжать до вариантов 720p, 1080p, чтобы обеспечить низкую задержку). Кроме того, этот вариант может быть ненадежным из-за агрессивных сетевых экранов у некоторых пользователей.

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

Этим обусловлено то, что личные трансляции было решено сделать платными. Поскольку OBS продолжала работать во время личных или групповых чатов, необходимо было закрыть доступ до потока HLS (ведь кто-то мог решить сэкономить и смотреть поток с любого стороннего плеера). Поэтому в момент старта личного чата мы отключали транскодинг в HLS.


Много событий в реальном времени 

В этой системе было достаточно много событий у пользователей разных ролей.

Часто для обеспечения таких систем реального времени используется протокол Websockets. Каждый клиент (браузер или мобильное приложение) устанавливает соединение по Websockets и посылает, либо принимает сообщения. Недостатком этого протокола является то, что число соединений с сервером удваивается (1 соединение https, одно соединение wss). Кроме того, этот протокол гораздо сложнее масштабировать (разделять нагрузку между серверами, или в зависимости от региона пользователя), он уязвим к DOS-атакам.

Альтернативой ему является, как ни странно, сам HTTP. Этот протокол с незапамятных времён поддерживает т. н. Server-Side Events. Но почему-то эта заложенная мудрыми отцами-основателями WEB редко находит применение. Вторую жизнь этой технологии вдохнул проект Mercure. Благодаря мультиплексированию в HTTP/2, стало возможным использовать лишь одно соединение между клиентом и сервером. Push-события с сервера идут по тому же сокету, что и обычные POLL-запросы http!

Помимо вдвое меньшей нагрузки на сервер, SSE масштабируется гораздо легче, как и обычный HTTP, а реализация на клиенте занимает ровно две строки.

Mercure. rocks позволяет клиенту подписаться на один или несколько каналов (например, на личные или публичные каналы). Используется Cookie авторизация, через который передаётся JWT со списком доступных пользователю каналов. JWT формирует сервер Backend. Mercure. rocks предоставляет Rest API и достаточно много логов для отладки соединений. Через API всегда можно понять, кто из юзеров на какой канал подписан, в частности, это легко позволяет посчитать пользователей онлайн и кто из них в каком разделе находится. И всё это в реальном времени!

Продолжение следует

Да, насчёт реального времени. Как я уже говорил, в проекте были обычные текстовые чаты. Для них мы использовали Firebase Realtime database, но это уже совсем другая история, которую мы раскроем в следующих статьях. Следите за обновлениями!