Ещё одна история изучения и использования технологии WebRTC (Web Real-time Communication). Краткое описание создания готовой библиотеки для её переиспользования в разных проектах.

Введение

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

Почему именно SFU?

SFU (Selective Forwarding Unit) - это такой способ реализации технологии WebRTC где от каждого участника конференции  один раз отправляется на север его исходящий поток с видео и аудио треками, и затем уже каждый клиент конференции получает от сервера треки каждого пользователя.

Для организации большого количества участников конференции такой подход необходим, так как например если использовать MCU (Multi Control Unit) - это способ реализации WebRTC при котором исходящие потоки клиентов составляются в один объединенный поток и отправляются клиентам как один видео элемент, то для такого метода реализации требуется очень мощное железо сервера, так как серверу постоянно придется высчитывать результирующий поток в режиме онлайн.

Кому это нужно?

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

Зачем изобретать велосипед?

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

С чего начать?

Прежде чем начать, сначала нужно было определиться каким ПО будет оперировать сервер для общения с клиентами. Нужна была библиотека в которой реализовано API WebRTC. Ограничением стека было Node.js. Изначально был сделан выбор в пользу этой библиотеки https://github.com/node-webrtc/node-webrtc которая по своей сути является оберткой библиотеки WebRTC от Google Chromium в виде нативного Node.js модуля.

Как всё устроено?

Схема реализации SFU сервера следующая. Вот у нас две абстракции:  

  1. WebRTC на сервере в виде специальной библиотеки

  2. WebRTC в браузере пользователей

Для реализации SFU работает следующая логика. Так как изначально WebRTC разрабатывалась для peer-to-peer подключений, а именно браузер-к-браузеру, сервер служил только для обмена служебными сообщениями главными из которых являются сообщения передающие SDP (Session Description Protocol) это сообщения которые в специальном формате описывают особенность подключения каждого конкретного устройства и адреса для связи с ним. В случае с SFU мы делаем следующим образом, чтобы не изобретать велосипед и не уходить от стандарта WebRTC на каждое подключения клиента к серверу, будь то первичное подключение для трансляции своих аудио и видео треков или вторичные подключения, которые служат для получения треков других пользователей. Со стороны сервера создаем по виртуальному клиенту на каждое подключение, который также как и браузер подключается к веб сокету для сигналинга и далее уже реальный клиент (браузер пользователя) и виртуальный клиент сервера создают между собой т.н. RTCPeerConnection. Разница между первичным подключением для исходящего трафика и остальными для входящих лишь в способе работы с медиа треками пользователя. Если это первичное подключение, то мы запоминаем треки пользователя в оперативную память сервера. А если это соединения для входящего трафика, то в тот момент когда виртуальный клиент на сервере должен отправить в ответ медиа треки мы шлем медиа треки того клиента соединение с которым симулирует данное клиент-сервер соединение.

Что может пойти не так?

Все проблемы можно решить,если для их решения есть всё необходимое. В данном случае, когда библиотека была написана уже процентов на 50 от нынешнего состояния было принято решение написать автоматические тесты, которые будут в веб драйвере открывать комнату, присоединять к ней несколько клиентов и проверять все ли видео воспроизводятся. Это было очень своевременным решением, потому что на том этапе позволило выявить множество ошибок. Но главное это то, что оказалось, что серверная реализация WebRTC (npm wrtc), которая использовалась на тот момент не справляется со своей задачей. А именно, менялось поведение скрипта между первым и последующими запусками теста. Если при первом запуске тесты отрабатывали без ошибок и без предупреждений, то результаты вторых и следующих запусков оставляли желать лучшего. Только перезапуск сервера устранял проблему и то только для первого запуска, а далее всё повторялось. На тот момент работа над проектом продолжалась уже больше трех месяцев и в репозитории было более 300 комитов. И выяснить на данном этапе, что ключевая библиотека глобально хранит какие-то привязанные к процессу данные, которые нарушают работу приложения между запусками было весомым намеком бросить это дело и заняться чем-нибудь попроще. Однако в поисках проблемы неоднократно оставлял запросы в Issue ветках репозитория node-webrtc и однажды, спустя пару недель бесполезных попыток выровнять ситуацию в одной из веток один пользователь нелестно высказался по поводу нехорошего кода WebRTC от Google и что сам он использует только https://github.com/shinyoshiaki/werift-webrtc это имплементация WebRTC написанная на Typescript программистом из Японии. Это был шанс. Практически без головной боли серверная часть проекта была перенесена на werift и всё заработало, без этих флешбеков между запусками.

На локальном сервере работает, а на удаленном?

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

На локальном компьютере нас ограничивает вебсокет, который отказывается работать на отличном от localhost хосте без зашифрованного протокола (https|wss). Таким образом на локальной машине внутри локальной сети мы не можем открыть приложения с другого устройства например на хосте 192.160.0.2, чтобы протестировать, так как на таком хосте вебсокет не подключится без защищенного шифрования, а ограничения самоподписанного сертификата мы не сможем убрать на браузере мобильного. Однако если вы знаете способ как это сделать, то пожалуйста напишите в комментариях. Также мы не можем одновременно запустить два экземпляра приложения в разных браузерах, так как операционная система может дать управление камерой и микрофоном только одному приложению одновременно. Таким образом, для разработки и тестирования, приходится симулировать разных клиентов в разных вкладках одного браузера.

На удаленном сервере у технологии WebRTC начинаются серьёзные проблемы с соединением между клиентами. Это связано с тем, что обычные пользователи интернета, как правило имеют только серые IP, то есть их реальный IP адрес скрыт за NAT (Network Address Translators). И без специальных STUN/TURN серверов наше приложение работать не будет. STUN (Session Traversal Utilities for NAT) - выполняет сигналинг между клиентами, и только выстраивает цепочку обратных адресов для обхода NAT. TURN (Traversal Using Relays around NAT) - обычно используются когда STUN не может выстроить цепочку адресов для связи между клиентами, TURN осуществляет непосредственное соединение клиентов между собой для прямой передачи пакетов данных между клиентами через себя.

Фатальный недостаток библиотеки.

В предыдущем пункте было описано об особенностях запуска WebRTC приложения на удаленном сервере. Эта особенность может говорить в пользу того, что в отличие от большинства обычных веб приложений которые вы можете разрабатывать практически до полной готовности на локальном компьютере и почти перед самим продакшеном развернуть его на удаленном сервере и всё по идее пойдет хорошо, если конечно приложение написано с учетом предыдущего опыта доведения приложений до продакшена, то приложение WebRTC, ввиду его особенностей, желательно запускать и тестировать на удаленном сервере практически с самого начала. Приведу пример как в нашем приложении изначально был заложен один фатальный недостаток, который удалось выявить только при первых запусках на удаленном сервере, а так как это было, когда архитектура и концепция приложения были уже сформированы, то этот недостаток и оказался фатальным то есть неустранимым в сложившейся картине приложения. Изначально приложение планировалось с неограниченным количеством видеопотоков, которые должны  были располагаться плиткой по всему экрану, экран не должен был иметь скролла, а при клике по одному из видео оно должно было открываться на весь экран. Чтобы видеопотоки не грузили интернет пользователя, планировалось масштабировать видео треки на лету методом MediaStreamTrack.applyConstraints() и на локальном компьютере всё чудесным образом масштабировалось на клиенте-получателе. То что видео трек действительно масштабировался в этом нет никаких  сомнений, так как это было проверено несколькими способами. Но как только приложение было запущено на удаленном компьютере, выявилось, что на локальном компьютере это срабатывало как исключение, а не как правило. А на удаленном сервере такое изменение параметров видео на лету возможно только на клиенте-отправителе. Как вариант, есть идея решения этой проблемы, если каждый клиент будет отправлять на сервер не один поток в исходном качестве,  а кроме этого ещё несколько потоков в разных размерах, а каждому клиенту будет выдаваться поток с близким к необходимому размером. Но это всё равно полностью не решает проблему с излишним расходом интернет трафика из-за разниц требуемых и предоставляемых размеров, а также ещё и перманентно увеличивает исходящий трафик каждому клиенту, поэтому было принято решение сделать грубое ограничение количества одновременно активных видео потоков. Пока жестко ограничено до 4, но в скором будущем возможно эта настройка будет выведена  в меню администратора. Если бы этот метод был сразу протестирован на удаленном и сервере и проблема была выявлена на тот момент, то возможно было бы найдено более утонченное решение и эта проблема бы не носила фатальный характер, однако тогда возможно и концепция и вид самого приложения могли оказаться другими.

Что самое сложное?

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

  1. Первичное подключение - при загрузке конференции, браузер клиента должен подключаться к серверу для отправки серверу своих исходящих медиа треков.

  2. Взаимные подключения - клиент-серверные подключения для получения каждым клиентом входящих медиа треков остальных клиентов.

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

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

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

Что имеем сейчас

На момент написания этого поста, приложение имеет следующие возможности:

  • управление микрофоном и камерой (включение/отключение)

  • чат (цитата, удаление, редактирование)

  • администрирование (отключение звука, отключение камеры, бан, отключение чата, назначение админом)

  • демонстрация экрана (если поддерживается браузером)

  • регулировка звука (возможность уменьшения звука конкретного пользователя)

  • смена языка интерфейса (русский/английский)

  • смена темы (светлая/темная)

Что планируется

На момент написание поста у автора библиотеки такие перспективы развития, со временем эти цели дополнятся или возможно изменятся:

  • запись видео встречи - на сервере при помощи werift.MediaRecorder записываются части от каждого клиента, а затем в конце записи эти части собираются в один видеофайл при помощи ffmpeg (в настоящее время уже ведутся работы по этому пункту).

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

  • улучшение дизайна - в настоящий момент дизайн сделан не дизайнером, поэтому в будущем планируется привлечение профессионалов для отрисовки интерфейса (возможно если удастся найти дизайнера, который захочет участвовать в Open Source, как и ваш покорный слуга, на энтузиазме, то эти работы начнуться уже очень скоро).

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

  • поддержка пользователей - отслеживание опыта пользователей в использовании программы, исправление багов найденных пользователями, развитие совместного вклада в приложение (на постоянной основе).

Что не планируется

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

  1. Запуск сервера через консоль - в настоящее время запуск сервера через консоль работает процентов на 60, но он будет помечен как deprecated в ближайшее время. Так как при запуске сервера из Node.js скрипта имеется возможность передачи в аргументах колбек функций на ключевые события, такие как создание/удаление комнаты или подключение/отключение пользователя. Передавать такие аргументы в консоли и работать с ними будет крайне неудобно, поэтому принято решение не поддерживать запуск сервера из консоли. 

  2. Клиент на фреймворках кроме ReactJS - не планируется создавать копии клиентов для других популярных фреймворков, например таких как Angular или Vue. На первый взгляд было бы круто, чтобы можно было выбирать клиента под свою любимую библиотеку. Однако в данном случае писать большие объемы дублирующего кода совсем не оправдано. В сборку библиотеки включена NonJSX ReactJS версия, которую можно использовать в любом окружении, хоть непосредственно в браузере. Ну а для сборщиков из среды Node.js отличных от ReactJS возможно нужно будет сообразить какие-нибудь плагины для более удобной интеграции.

Ключевая зависимость

Как упоминалось выше главной зависимостью библиотеки является библиотека werift (WebRTC implementation for Typescript). Главным преимуществом которой, по мнению многих пользователей, является отсутствие каких-либо C/C++ зависимостей. Библиотека написана на Typescript, что также радует опытных Node.js разработчиков. Автор библиотеки Shin Yoshiaki довольно активно её поддерживает и судя по тому, что эта библиотека является его главным детищем на данный момент - не собирается её забрасывать. Ваш покорный слуга неоднократно получал от него поддержку в issue, а также несколько моих коммитов с небольшими правками были им приняты в основную ветку проекта, в будущем постараюсь нарастить свой вклад в эту замечательную библиотеку. 

Сколько времени занимает разработка? 

Если у вас есть в планах написать свое подобное приложение, то сколько времени теоретически может на это понадобиться. В самом начале необходимо четко определится какой тип приложения нужно использовать для реализации вашей идеи: SFU, MCU или P2P. Если ваш выбор P2P, то для вас нет необходимости использования серверной реализации RTCPeerConnection, так как все RTC подключения будут происходить между браузерами, а сервер будет нужен лишь для созвона между клиентами по вебсокетам для обмена сообщениями описывающими соединения. Тогда у вас может пойти всё довольно быстро и через пару-тройку месяцев если брать объем примерно  моей библиотеки у вас уже будет что-то работающее. У меня на написание на данный момент ушло примерно 8 месяцев, в среднем по 4 часа в день, но это из-за того, что я не сразу нашел подходящую библиотеку и изначально много раз менял концепцию, что приводило к “топтанию на месте”. Если же ваш выбор это SFU, то вам нужно понимать, что без библиотеки, которая на сервере будет эмулировать RTCPeerConnection вам не обойтись и если вы не найдете подходящую библиотеку с таким функционалом для вашего ЯП, то на разработку могут уйти годы, пока вы своими силами сможете реализовать стандарт WebRTC и как следует отладить его работу, зато вы хорошенько разберетесь в технологии, а также если оформите это в виде библиотеки, то получите контролируемый вами инструмент для создания более высокоуровневых приложений.

Почему Open Source?

Как мы видим в реальности. Успешные компании, от стартапов до крупных корпораций, очень часто скрывают код своих разработок. К этому можно относиться по разному, но мне хотелось бы взглянуть на это с точки зрения моей библиотеки. Вот например, у меня получилось создать приложение условно говоря аналог Zoom, зачем мне открывать его исходный код, ведь грамотней было бы скрыть исходный код и владеть возможностью запуска этого приложения единолично!? Так то оно так, но посудите сами, разве мое приложение сможет конкурировать с тем же Zoom или веб версией Discord например? Конечно же нет, в отличие от владельцев этих инструментов я не имею таких ресурсов. Однако, как только я открыл свой код для всех пользователей и тем самым дал возможность любому желающему не только установить на свой сайт этот функционал, но и по своему усмотрению доработать его, то есть сделал то, чего те компании по понятным причинам предоставить не могут, таким образом мой продукт уже занимает свою нишу, на которую те компании претендовать не смогут пока их исходный код скрыт. 

Проблемы 

При разработке WebRTC приложения проблемы встречаются часто, особенно с зависимостями. Но если вы соберетесь работать с WebRTC, то с самого начала рекомендую привыкать к проблемам иначе они заберут слишком много энергии. Как говорит Саидмурод Давлатов  “проблемы - это нормально”.

Ссылка на библиотеку 

Ознакомиться с исходным кодом библиотеки можно на Github

Если когда-то поменяется главная зависимость и название репозитория и вдруг ссылка Github будет неактивна, то адрес репозитория сможете найти в npm

Заключение

Благодарю за внимание! Удачи тебе, дорогой друг!

Комментарии (2)


  1. dimitrii_z
    26.01.2023 23:12
    +2

    "MCU (Multi Control Unit) - это способ реализации WebRTC при котором исходящий поток от каждого клиента идет непосредственно к каждому клиенту."

    Автор не разобрался в технологиях. В MCU проблема не в трафике, а в том, что требуется мощный сервер, и нагрузка растет прямо пропорционально количеству точек подключения. Вот описание с картинками доступное https://trueconf.ru/blog/wiki/mcu-arhitektura-videokonferenczij То что описано похоже просто на Mesh.

    И по задаче: чем не устроили готовые решения типа широко используемого для небольших нагрузок jitsi?


    1. kolserdav Автор
      28.01.2023 21:41

      Да действительно, я не разобрался MCU - это совсем не то что я описал. Попробую это исправить. Благодарю за комментарий.

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