WebRTC — набор протоколов и API, задуманный для прямого общения браузеров, peer-to-peer, без плагинов: голос, видео и данные с небольшой задержкой. У звонилки есть как минимум две разные задачи: договориться, кто кому звонит, и доставить медиа между участниками. Первую решает приложение через signaling, вторую — WebRTC с ICE, STUN/TURN и защищённым медиатранспортом. Для групповых встреч часто добавляют SFU. Зелёный статус соединения ещё не означает, что люди слышат друг друга.
«Добавить видеозвонок» выглядит как задача на три кнопки: позвонить, выключить микрофон, завершить. Пока не появляется пользователь, у которого камера работает, коллегу видно, а звука нет. Или дома звонок проходит, а из офиса — бесконечное «подключаемся». Или два участника разговаривают нормально, но на шестом ноутбук начинает взлетать.
Я бы разбирал такую систему от пути одного звонка. Где договорились о встрече, где нашли сетевой маршрут, где пошли пакеты и где приложение решило, что разговор действительно начался. На каждом участке свой контракт и свой способ сломаться.
WebRTC — это один протокол или целая пачка?
WebRTC — Web Real-Time Communication — это набор согласованных протоколов и API. Можно по-простому сказать «пачка технологий»: одни помогают найти сетевой путь, другие согласуют параметры, третьи доставляют и защищают медиа. JavaScript API даёт приложению доступ к этим возможностям, а основную работу с пакетами выполняет браузерный медиастек.
Откуда это взялось: звонок прямо из браузера
Исходная идея — дать двум браузерам общаться голосом и видео напрямую, peer-to-peer, без отдельно устанавливаемого плагина. Приложение на странице управляет звонком через стандартные API, браузеры используют совместимые сетевые механизмы. В WebRTC собрали и согласовали в том числе уже существовавшие протоколы; их не придумали все с нуля специально для браузера. Это назначение изложено в обзоре IETF RFC 8825.
Peer-to-peer — про узлы и путь медиа, а не обещание «серверов вообще нет». Даже два браузера нужно познакомить: доставить приглашение, обменять параметры и найти маршрут через NAT. Прямой путь не всегда доступен, поэтому нужен relay. А для больших встреч часто выгоднее медиасервер. Совместимый WebRTC-узел сегодня может быть и браузером, и нативным приложением, и сервером.
| Часть набора | Её задача |
|---|---|
| Браузерные API: getUserMedia, RTCPeerConnection | Получить локальные треки и управлять соединением из приложения |
| SDP и offer/answer | Описать и согласовать параметры сессии; сами сообщения доставляет signaling |
| ICE, STUN, TURN | Найти и проверить сетевой путь, при необходимости с ретранслятором |
| RTP/RTCP и SRTP/SRTCP | Передавать медиа и обратную связь о доставке с защитой трафика |
| DTLS-SRTP | Согласовать ключи защиты медиатранспорта |
| SCTP поверх DTLS | Передавать сообщения RTCDataChannel, если приложению нужен канал данных |
Это карта обязанностей, а не один линейный стек: аудио/видео и data channel идут разными ветками. В частности, голос не упаковывают в SCTP data channel. Последний отдельно описан в RFC 8831. HTTP или WebSocket, которыми приложение доставляет signaling, тоже не становятся от этого медиапротоколом WebRTC.
Какие компоненты архитектурно нужны звонилке?
Для продуктовой звонилки я бы рисовал следующие логические роли. Это не требование поднять по микросервису на каждую строку: несколько ролей могут жить в одном приложении, а STUN и TURN — обслуживаться одним сервером.
| Компонент | Ответственность | Когда нужен |
|---|---|---|
| Клиенты участников | Интерфейс звонка, устройства, кодирование/декодирование, воспроизведение, WebRTC-стек | Всегда: это конечные точки общения |
| Бэкенд продукта и управление звонками | Пользователи, доступ в комнаты, приглашения, допустимые переходы состояния | Для управляемого продукта; не обязательно отдельный сервис |
| Signaling | Доставка offer/answer, ICE-кандидатов и управляющих событий нужным участникам | Способ обмена нужен для согласования; обычно его обслуживает бэкенд |
| STUN | Помощь в обнаружении адресов для прямой связи | Типичный интернет-сценарий; не обязательный отдельный узел во всех топологиях |
| TURN | Relay-путь, когда прямой маршрут недоступен или запрещён политикой | Закладываем для поддерживаемых сетей и проверяем доступность; не каждый звонок пойдёт через него |
| SFU или MCU | Серверная обработка распределения медиа между участниками | По топологии и масштабу: для маленького P2P-звонка не обязателен |
| Запись, транскрибация, SIP-шлюз, push-доставка | Дополнительные продуктовые функции | Только если они есть в требованиях |
Это C4 Container, уровень 2: граница системы, исполняемые части и их обязанности. Контейнер в C4 — приложение или сервис, а не обязательно Docker-контейнер. Веб-клиент показан один раз как тип приложения; у каждого участника работает свой экземпляр. При P2P экземпляры обмениваются медиа напрямую, при relay — через TURN. Прямая связь между экземплярами одного контейнера здесь не нарисована: эта схема показывает устройство системы, а не конкретный сетевой маршрут. STUN не переносит разговор; SFU для групповой топологии разберём ниже.
По этой схеме сразу можно разделить зоны отказа и ответственность команд. Не доходит приглашение — смотрим управление и доставку signaling. Приглашение принято, но нет пути для медиа — проверяем ICE и сетевую инфраструктуру. Пакеты приходят, но звука нет — разбираем клиентское устройство и воспроизведение. Это полезнее требования «сервис звонков должен работать».
Почему у звонка два разных контура?
Signaling — управление. «Позвать пользователя», «принять», «отклонить», «вступить в комнату», «вот мои параметры подключения». Это сообщения нашего приложения, например через WebSocket или HTTP. WebRTC не задаёт универсальный протокол доставки этих сообщений и не предоставляет готовую адресную книгу, занятость и историю звонков. Разделение показано в руководстве MDN по signaling.
Media — сам разговор. Звуковые и видеоданные передаются между WebRTC-узлами: двумя клиентами либо клиентом и медиасервером. Маршрут не обязан совпадать с соединением, по которому пришло приглашение.
Две независимые поломки
В нашей звонилке может работать приглашение, но не находиться путь для медиа. И наоборот: уже установленный разговор некоторое время продолжается при потере signaling, но новые участники и команды перестают работать. Поэтому один флаг «интернет есть» плохо описывает состояние звонка.
Не стоит пересылать кадры камеры как обычные JSON-сообщения только потому, что WebSocket уже поднят. У разговорного медиа свои требования к задержке, потерям и адаптации качества. Для выбора транспорта данных есть отдельная глава обновления в реальном времени; здесь разбираем именно звонок.
Что происходит после кнопки «позвонить»?
Сначала проверяем право на звонок и получаем нужные локальные устройства. В браузере getUserMedia() запрашивает доступ к микрофону и камере в защищённом контексте, обычно HTTPS. Пользователь может отказать, устройство может отсутствовать или оказаться недоступным. Человек может вообще не ответить на запрос разрешения: ожидание нельзя выдавать за уже начавшийся звонок. Это поведение описано в документации getUserMedia.
const local = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
const pc = new RTCPeerConnection(iceConfig);
for (const track of local.getTracks()) {
pc.addTrack(track, local);
}
// iceConfig выдаёт наш бэкенд; это только подготовка локальных треков.
// Для звонка ещё нужны signaling, offer/answer и обмен ICE-кандидатами.
Это фрагмент, а не работающая звонилка. В продукте перед ним есть экран выбора устройств, а вокруг — обработка ошибок. Если камеры нет, решаем, разрешён ли вход только с аудио. Если микрофон запрещён — можно ли слушать встречу. Нельзя автоматически считать любой отказ пользователя технической неисправностью.
Превью собственной камеры подтверждает только локальный захват. Оно ничего не говорит о передаче, декодировании и воспроизведении у собеседника. В критериях приёмки нужны обе стороны.
Как участники договариваются о медиа?
Стороны обмениваются offer и answer — предложением параметров сессии и ответом. Для этого используется SDP: описание медиа, кодеков, направлений передачи и транспортных параметров. Это не видеопоток и не бизнес-событие «пользователь согласился разговаривать». Доставку описаний организует signaling-сервис. Модель API RTCPeerConnection задаёт спецификация W3C WebRTC.
sequenceDiagram participant A as Алиса participant S as Signaling participant B as Борис A->>S: Приглашение в звонок call-7 S->>B: Входящий звонок B->>S: Принять приглашение S->>A: Приглашение принято A->>S: SDP offer S->>B: SDP offer B->>S: SDP answer S->>A: SDP answer A->>S: ICE candidates S->>B: ICE candidates Алисы B->>S: ICE candidates S->>A: ICE candidates Бориса Note over A,B: Проверки маршрута и защищённый транспорт A-->>B: Аудио и видео по выбранному пути B-->>A: Аудио и видео по выбранному пути
В нашем учебном сценарии Борис сначала принимает приглашение, потом стороны согласуют параметры. В реальном приложении часть подготовки может идти заранее. ICE-кандидаты часто передают по мере появления — это trickle ICE, поэтому все стрелки не обязаны исполняться строго одним блоком за другим. Стрелки медиа обозначают логический обмен: физический путь может идти через TURN.
Signaling должен связывать сообщения с callId, участником и попыткой согласования. Старый кандидат от предыдущего соединения не должен попадать в новое. Если кандидат пришёл раньше удалённого описания, клиенту нужно корректно дождаться описания, а не терять кандидата. Отдельно продумываем одновременные предложения от обеих сторон: переключение камеры не должно создавать гонку переговоров.
Зачем ICE, STUN и TURN, если известен IP?
Участник обычно сидит за NAT, домашним роутером, корпоративным межсетевым экраном или мобильной сетью. Адрес устройства в локальной сети не обещает доступности снаружи.
| Механизм | За что отвечает | Чего не обещает |
|---|---|---|
| STUN | Помогает обнаружить отображённый внешний адрес; STUN-сообщения используются и в проверках связности ICE | Не ретранслирует сам разговор и не делает любой NAT проходимым |
| TURN | Выделяет relay-адрес и пересылает трафик через сервер | Не заменяет управление комнатой и не выбирает видео для участников |
| ICE | Собирает кандидаты, проверяет пары адресов и выбирает рабочую пару по процедуре протокола | Не гарантирует доступность при любых ограничениях сети |
Кандидат — возможная точка связи: локальная, обнаруженная через STUN или выделенная TURN. Это не проверенный маршрут. Процедура описана в RFC 8445 (ICE), а работа ретранслятора — в RFC 8656 (TURN).
Не представляйте это как обязательные три последовательных попытки «сначала STUN, потом ICE, потом TURN». TURN-кандидаты тоже участвуют в ICE; выбор зависит от политик и результатов проверок. Relay можно предпочесть намеренно, например чтобы участники не соединялись напрямую. Сам ICE не является отдельным сервером-посредником для голоса.
TURN — часть стоимости продукта
Если мы обещаем звонки из корпоративных сетей, проверяем доступность TURN и нужных транспортов в целевой среде. Поддержка TURN через TCP/TLS может помочь там, где UDP ограничен, но не обходит любой firewall. Серверу нужны ёмкость, наблюдаемость и ограниченные по сроку учётные данные; публичный пароль в клиентском коде не годится.
Для оценки затрат считаем долю relay-сессий, битрейт, длительность и направление тарифицируемого трафика. В нашем примере поток 1 Мбит/с за час — примерно 450 МБ полезных данных в одном направлении, без накладных расходов. Сколько оплачиваемого трафика это создаст на TURN, зависит от схемы передачи и тарифа, а не от одного слова «звонок».
Как голос превращается в пакеты?
Микрофон даёт сигнал, кодек его сжимает, медиастек пакетирует и отправляет. Для разговорного аудио часто используется Opus. Медиа WebRTC передаётся с защитой SRTP; ключи согласуются через DTLS-SRTP. Обычно выгоден UDP: слишком поздний звук уже не помогает диалогу. Но фраза «WebRTC работает только по UDP» неверна — существуют relay-сценарии с другим транспортом на участке до TURN. Стек и требования к медиа описаны в RFC 8834.
На принимающей стороне есть jitter buffer — небольшой запас пакетов для сглаживания неравномерного прихода. Больше запас — плавнее воспроизведение, но выше задержка. Потери могут компенсироваться средствами кодека, избыточностью и подходящими повторными передачами, если это ещё укладывается во время. Не нужно переносить на разговор правило загрузки файла «дождаться каждого байта любой ценой».
В нашем продукте при ухудшении сети сначала сохраняем разборчивое аудио, затем снижаем качество видео или отключаем его по согласованным правилам. Для демонстрации мелкого текста приоритет может отличаться от камеры говорящего. Это продуктовое решение, которое стоит проверить на реальных сценариях.
Почему групповой звонок — уже другая архитектура?
В mesh каждый участник связан с каждым. У четырёх участников шесть попарных связей, каждому нужно отправлять медиа трём другим. При росте группы растут исходящий трафик и нагрузка клиентов. Даже если все связи проходят через TURN, топология от этого не превращается в медиаконференцию с серверным выбором потоков.
SFU (Selective Forwarding Unit) принимает потоки и выборочно пересылает их подписчикам. Клиент отправляет медиа в SFU, а сервер решает, какие потоки или слои доставить каждому получателю. В типичной схеме SFU не собирает все видео в один перекодированный кадр. При simulcast отправитель может посылать несколько вариантов качества, при SVC — масштабируемый поток со слоями. Поэтому «клиент всегда отправляет ровно одну копию видео» — слишком грубое обещание.
MCU/микшер может декодировать, смешивать и заново кодировать медиа. Клиент получает подготовленный результат, а сервер платит вычислениями и дополнительной задержкой. Базовые топологии и различия селективной пересылки и смешивания описаны в RFC 7667.
Это второй вид того же уровня C4: показаны только контейнеры, участвующие в доставке группового медиа. Каждый экземпляр клиента публикует свои треки в SFU и получает выбранные треки остальных. Прямой путь до SFU и путь через TURN — альтернативы, а не требование дублировать каждый пакет. Управление звонками из первой схемы остаётся нужно, но здесь скрыто для ясности. SFU отвечает за распределение медиа, TURN — за его сетевую достижимость.
«Зашифровано» означает, что сервер не слышит разговор?
Не обязательно. В обычной SFU-схеме защищённое WebRTC-соединение заканчивается на SFU. Защита на транспортном участке не равна дополнительному сквозному шифрованию полезного медиа между участниками поверх медиасервера. Для последнего нужны отдельная схема защиты кадров, управление ключами и совместимость клиентов. Архитектура безопасности WebRTC разобрана в RFC 8827.
Для нашей встречи аналитик должен зафиксировать, кто может получить незашифрованное содержимое: клиенты, медиасервер, сервис записи, бот расшифровки. Если обещаем скрыть разговор от сервера, надо отдельно решить, как работают запись и транскрибация: кому выдаются ключи и как участники узнают об этом. Нельзя одновременно оставить серверу обычную запись и объявить его технически неспособным прочитать медиа.
Включение записи, допуск бота и доступ к готовому файлу — разные права. Вопросы модели доступа связаны с RBAC/ABAC, а срок хранения и минимизация данных — с безопасностью данных.
Как описать состояния звонка?
Я бы разделил три оси: бизнес-состояние звонка, транспорт и локальные устройства. Встреча может продолжаться, пока у одного человека переподключается сеть. Выключенный микрофон не означает разрыв. А принятый вызов ещё не означает, что пришёл первый звук.
Учебная модель звонка:
created → ringing → accepted → connecting → active → ended
Из ringing: rejected | cancelled | missed
Из connecting: failed | ended
Во время active: отдельное состояние участника reconnecting
Причина завершения хранится отдельно:
user_hangup | no_answer | media_timeout | permission_denied
Для участника отдельно:
microphone: enabled | muted | unavailable
camera: enabled | disabled | unavailable
Это модель нашего продукта, а не имена состояний браузерного API. Для active задаём наблюдаемый критерий: например, транспорт подключён и подтверждён приём медиа для данного сценария. При отключённых треках критерий будет другим. connectionState=connected не доказывает, что выбран правильный динамик, громкость не нулевая и браузер разрешил воспроизведение.
Разберите гонки: Алиса нажала «отмена» в момент, когда Борис ответил; пользователь принял звонок на телефоне, но ноутбук тоже успел отправить accept. Сервер должен определять допустимый переход, победившую сессию и ответ проигравшей попытке. Здесь полезны диаграммы состояний и идемпотентные команды, а не только happy path sequence.
Что происходит при переходе с Wi-Fi на мобильную сеть?
Старый маршрут может перестать работать. ICE restart позволяет заново договориться о параметрах ICE и проверить пути; для него требуется обмен новой информацией через signaling. Это не то же самое, что переподключить WebSocket. Восстановление signaling и восстановление медиатранспорта — два связанных, но разных процесса.
В нашей звонилке краткое disconnected не завершает встречу немедленно. Показываем «восстанавливаем связь», даём ограниченное окно, затем либо возвращаем участника, либо явно завершаем его попытку. Старые события от прошлой сессии отбрасываем. Если права уже отозваны, reconnect не должен их вернуть.
При выходе закрываем peer connection и останавливаем принадлежащие звонку локальные треки. Одного закрытия сетевого соединения недостаточно, чтобы считать камеру освобождённой. Если поток используется другими частями приложения, правила владения и освобождения ресурсов задаём отдельно.
Что измерять вместо «качество хорошее»?
getStats() позволяет получить статистику транспорта и RTP. В спецификации WebRTC Stats описаны, среди прочего, потери, jitter, параметры candidate pair, счётчики байтов и кадров. Не все показатели доступны одинаково для каждого потока и реализации. Счётчик байтов за всё время не является текущим битрейтом: нужна разница двух измерений, делённая на интервал.
| Вопрос | Что наблюдать |
|---|---|
| Удалось ли поговорить? | Доля успешных установлений с явно заданным критерием, причины отказов |
| Сколько ждали? | От accept до первого воспроизводимого медиа; отдельно ожидание ответа человека |
| Почему заикается звук? | Потери, jitter, задержки, маршрут, загрузка устройства |
| Почему замерло видео? | Динамика принятых и декодированных кадров, bitrate, ограничения CPU/сети |
| Где дорого или нестабильно? | Доля TURN, регион медиасервера, число переподключений, длительность |
Учебный критерий: «В согласованной сетевой среде для 95% принятых аудиозвонков первый звук доступен не позже трёх секунд». Это целевой пример, не характеристика WebRTC. Определяем выборку, поддерживаемые устройства, метод измерения и исключения. Привязываем диагностику к callId и сессии участника, не складывая в обычный лог SDP, адреса и учётные данные без разбора. Общий подход — в главе логи, метрики и трейсы.
Что обязательно прогнать на приёмке?
- Отказ в доступе к камере, отсутствие микрофона, ожидание разрешения без ответа: экран объясняет ситуацию и предлагает допустимый путь.
- Звонок в одной сети, между разными сетями и принудительно через relay; отдельно сценарий с ограничением UDP.
- Односторонний звук, смена устройства вывода, подключение гарнитуры, выключение микрофона и повторное включение.
- Одновременные accept/cancel, ответ с двух устройств, повтор команды завершения.
- Смена сети во время разговора, разрыв signaling, перезапуск медиасервера и истечение окна восстановления.
- Группа ожидаемого размера, слабый ноутбук, демонстрация экрана и ухудшение канала: проверяем приоритет аудио и понятную деградацию.
- Отзыв доступа во время встречи, попытка подписаться на чужую комнату, корректный индикатор записи и правила доступа к ней.
- После завершения камера и микрофон освобождены, а повторный вход не создаёт лишние треки и подписки.
Эта матрица — часть нефункциональных требований. «Работает между двумя вкладками на машине разработчика» проверяет только самый удобный маршрут.
Какие реальные звонилки используют WebRTC?
DION — пример корпоративной платформы звонков и видеоконференций. В официальном описании защиты данных DION прямо указаны WebRTC для аудио/видео, signaling через WebSocket, DTLS для соединения с медиасерверами и SRTP для медиапотоков. Это живой пример разделения, которое мы только что разобрали: сообщения управления идут своим каналом, медиа — своим. Из этого описания не следует обещание прямого P2P между участниками или сквозного шифрования от медиасервера.
Jitsi Meet — открытое приложение видеовстреч, у которого можно изучать не только интерфейс, но и устройство системы. В описании архитектуры Jitsi видны клиент, signaling на XMPP, координатор конференций Jicofo и медиакомпонент Jitsi Videobridge. Для встречи один на один предусмотрен P2P-режим, а групповая конференция использует видеомост. Хороший пример того, как исходная браузерная P2P-идея сосуществует с серверной топологией.
LiveKit Meet — открытое приложение видеоконференций на LiveKit. Его исходники опубликованы разработчиками; сам LiveKit предоставляет инфраструктуру и SDK для общения в реальном времени. На этом примере удобно различать готовую звонилку и платформу, на которой её собрали: комнаты, участники и треки относятся к модели приложения, а WebRTC обеспечивает обмен медиа.
Эти примеры не означают, что любой видеосервис устроен одинаково. Даже внутри одного продукта веб-клиент, нативное приложение, телефонный шлюз и трансляция могут использовать разные участки стека. Аналитику полезнее выяснить путь конкретного сценария, чем приклеить ко всему продукту одну аббревиатуру.
Частые вопросы
Можно сделать WebRTC-звонок вообще без сервера?
Для демо можно вручную обменяться описаниями и кандидатами. Продукту обычно нужны идентификация, доставка приглашений и signaling, а для достижимости — STUN/TURN. Возможность прямой передачи медиа не означает отсутствие серверной части.
TURN и SFU — одно и то же?
Нет. TURN пересылает трафик как сетевой relay. SFU управляет пересылкой медиапотоков подписчикам конференции. Путь от клиента до SFU тоже может проходить через TURN.
Можно через WebRTC позвонить на обычный номер?
Сам по себе браузерный peer connection не подключён к телефонной сети. Нужны телефония и шлюз, который согласует сигнализацию и медиа, например с SIP-инфраструктурой. Нумерация, маршрутизация, тарификация и запись становятся отдельной частью системы.
WebRTC подходит только для голоса и видео?
Нет, есть RTCDataChannel для данных. Его режимы надёжности и порядка задаются отдельно; это не обязательный канал signaling для первоначального установления того же соединения. Не стоит строить циклическую зависимость «чтобы договориться, сначала надо уже соединиться».