Legan Studio
Все статьи
~ 6 мин чтения

Видеозвонки на сайте: WebRTC vs LiveKit

Как добавить видеозвонки на сайт: WebRTC напрямую, LiveKit, mediasoup, Janus, выбор архитектуры P2P или SFU, стоимость TURN-серверов.

  • веб
  • интеграции
  • архитектура

«Сделайте видеозвонки на сайте, как в Zoom». Звучит просто, но за этой фразой — несколько недель инженерной работы и постоянная боль с TURN-серверами, потерями пакетов и Safari, который ведёт себя не как все. Разбираем, что реально требуется и где готовые решения.

Из чего состоят видеозвонки

КомпонентЗачем
WebRTCбраузерный API для медиапотоков
Signaling serverобмен SDP между участниками
STUN-серверпомощь с обходом NAT
TURN-серверрелей трафика, если P2P не получился
SFUсервер-роутер медиа для 3+ участников
MCUсервер-микшер (тяжёлый, редко)

Для звонка 1-на-1 нужны WebRTC + Signaling + STUN + TURN. Для конференции 3+ человек добавляется SFU.

Базовый WebRTC: 1-на-1

// получить камеру и микрофон
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localVideo.srcObject = stream;

// создать peer connection
const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    { urls: "turn:turn.example.ru:3478", username: "u", credential: "p" },
  ],
});

stream.getTracks().forEach((t) => pc.addTrack(t, stream));

pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
  if (e.candidate) signaling.send("ice", e.candidate);
};

// инициатор
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send("offer", offer);

// получатель
signaling.on("offer", async (offer) => {
  await pc.setRemoteDescription(offer);
  const answer = await pc.createAnswer();
  await pc.setLocalDescription(answer);
  signaling.send("answer", answer);
});

Signaling — обычно WebSocket. Передаёт SDP-описания и ICE-кандидаты. Без него участники не найдут друг друга.

STUN и TURN

WebRTC старается соединить пиров напрямую. Если у обоих есть публичный IP — без проблем. Если за NAT (большинство пользователей) — STUN помогает определить внешние адреса. Если NAT симметричный или CGNAT (мобильный интернет) — нужен TURN, который ретранслирует трафик через себя.

В реальности 20-40% звонков идут через TURN. Без него ваше «P2P» работает у половины пользователей.

Бесплатных публичных TURN нет (Google STUN — только STUN). Варианты:

  1. Свой coturn: открытый софт, ставится на VPS. Трафик: 1 ГБ ≈ 6-7 минут видео качества HD. Один пользователь за час забирает 200-500 МБ.
  2. Twilio Network Traversal Service: $0.40-0.80 за ГБ, оплата с зарубежной карты.
  3. Xirsys: $33/мес стартер.
  4. Metered.ca: $0.40/ГБ, бесплатно до 50 ГБ.

Для российских проектов в 2026 — обычно coturn на собственном VPS в РФ. Каналы у российских хостингов дешёвые ($5-20 за TB).

# coturn config /etc/turnserver.conf
listening-port=3478
fingerprint
lt-cred-mech
realm=example.ru
user=clientuser:strongpass
external-ip=YOUR_VPS_IP

Конференции 3+ — нужен SFU

В P2P сетке каждый шлёт каждому. Для 3 человек — 6 потоков, для 5 — 20. Аплинк забивается у первого же участника с обычной DSL-линией.

SFU (Selective Forwarding Unit) — сервер, к которому подключаются все участники. Каждый шлёт один поток, сервер раздаёт его остальным. Нагрузка на участника фиксированная: один аплинк, N-1 даунлинков.

Готовые SFU:

SFUОсобенностьЛицензия
LiveKitсовременный, удобный SDK, Cloud + self-hostedApache 2.0
mediasouplow-level, для сложных сценариевISC
Janusстарый и проверенныйGPL
Jitsi Videobridgeopen source, у Jitsi-проектаApache 2.0
Pion (Go)low-level, для своих SFUMIT

LiveKit — самый удобный

В 2026 — стандартный выбор для SaaS-видеозвонков. Open source, на Go, есть managed cloud.

import { Room } from "livekit-client";

const room = new Room();
await room.connect("wss://livekit.example.ru", token);

// получить камеру/мик
await room.localParticipant.enableCameraAndMicrophone();

// слушать удалённых
room.on("trackSubscribed", (track, pub, participant) => {
  if (track.kind === "video") {
    document.getElementById("videos")!.appendChild(track.attach());
  }
});

Серверная часть генерирует JWT-токен с правами на комнату:

import { AccessToken } from "livekit-server-sdk";

const at = new AccessToken(API_KEY, API_SECRET, { identity: userId });
at.addGrant({ roomJoin: true, room: "meeting-42" });
const token = at.toJwt();

Self-hosted: один Docker-контейнер + Redis для координации, держит сотни одновременных участников на средней машине. Cloud — от $30/мес.

Запись звонков

Запись в браузере (MediaRecorder) работает только локально и только пока вкладка открыта. Для серверной записи нужен SFU с recording-функцией:

  • LiveKit Egress — выгружает в S3/MinIO как MP4, HLS или серию изображений.
  • Janus — recordplay плагин.
  • Jibri — компонент Jitsi для записи.

Сервер тяжёлый: запись 1080p — это ~30% CPU отдельного процесса на каждый звонок. Закладывайте мощную инфраструктуру.

Шумоподавление и фон

Современные браузеры уже умеют:

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
  },
});

Размытие фона — через MediaPipe (Google) или Krisp (платный SDK). На мобильных может тормозить, делайте кнопкой «вкл/выкл», а не по умолчанию.

Safari особенности

Главные грабли:

  • getUserMedia в Safari требует пользовательского жеста (клика). Нельзя вызвать на page load.
  • Видео не играет без playsInline.
  • Background audio в фоне — дополнительные ограничения.
  • iOS Safari — только один live-видеопоток в момент времени до 16.4.

Тестируйте обязательно в реальном Safari на iPhone, а не только в Chrome DevTools.

Сетевые проблемы

WebRTC адаптивно подстраивается под канал, но:

  • Пакетные потери > 5% — каша из квадратов.
  • Jitter > 50 мс — рассинхрон.
  • Длительный пик loss — сессия может разорваться.

Метрики на клиенте — pc.getStats(). На сервере — Prometheus-экспортер LiveKit.

В UI показывайте индикатор качества связи (зелёный/жёлтый/красный) и автоматически снижайте разрешение при потерях.

Сценарии и стоимость

СценарийАрхитектураМесячный бюджет
Звонок 1-на-1 в виджете консультацииWebRTC + coturn2-5 тыс. ₽
Видеоприём в телемед-сервисе, 50 параллельныхLiveKit self-hosted10-20 тыс. ₽
Конференции 10-50 человек, корп.SaaSLiveKit Cloud или Jitsi$200-1000
Стримы 1-к-100 (вебинар)LiveKit или собственный HLSзависит от трафика

Альтернативы

Если не хочется возиться:

  • Daily.co — managed, $9-99/мес, простой API, но платежи проблематичны.
  • Twilio Video — корп-уровень, дорого, тоже платежи.
  • Voximplant — российский, можно оплатить рублями.
  • VK Звонок API — российский, бесплатно с лимитами.

Итого

Если у вас 1-на-1 виджет «связаться с менеджером» — голый WebRTC + свой coturn. Если конференции — LiveKit (cloud или self-hosted). Если корпоративный продукт с регуляторкой — Voximplant или сразу свой mediasoup. Никогда не обещайте «как Zoom» — за полтора месяца сделаете 30% от Zoom, остальные 70% — это годы команды и десятки миллионов долларов.

Частые вопросы

Сколько пользователей выдержит свой LiveKit на одной машине?

На VPS с 8 vCPU и 16 ГБ RAM — 200-500 одновременных участников при HD-видео. С 4K или большим количеством опубликованных треков на участника — меньше. Главный ботлнек обычно сеть: 100 участников × 2 Мбит/с = 200 Мбит/с трафика на сервер. Для масштабирования — несколько SFU за региональным распределителем.

Можно ли сделать видеочат без TURN-сервера?

Можно, но 20-40% пользователей не смогут установить соединение. Это пользователи за NAT провайдеров, корпоративных файрволлов, мобильного 4G. Для серьёзного продукта без TURN — нельзя. Для внутреннего инструмента в офисе с одной локальной сетью — иногда хватает голого STUN.

LiveKit или mediasoup?

LiveKit — высокоуровневый, есть SDK для всех платформ, готовые UI-компоненты, простой деплой. mediasoup — low-level, гибче, лучше для сложных сценариев (свой формат сигналинга, миксинг с не-WebRTC потоками). 95% проектов хватает LiveKit. mediasoup — когда у вас команда WebRTC-инженеров и нестандартные требования.

Сколько стоит трафик TURN для 100 пользователей в день по 30 минут?

100 × 30 мин × ~5 Мбит/с (релейный двусторонний) ≈ 110 ГБ/день, 3.3 ТБ/месяц. На своём coturn в РФ-хостинге трафик дешёвый: $20-50/мес. На Twilio — 3300 × $0.40 = $1320/мес. Разница огромная. Свой coturn окупается мгновенно.

Как сделать запись звонка с двух камер одновременно?

Только серверной записью: LiveKit Egress / Jibri пишут все потоки в комнате в один MP4 в layout (grid или speaker view), либо отдельными файлами. Клиентский MediaRecorder может писать только локальный микс — не для прода. Закладывайте отдельный сервер под запись, сильно нагружает CPU.

Какой размер видеопотока выбрать?

Адаптивно: simulcast (LiveKit умеет) — публикуете три разрешения (180p/360p/720p), сервер раздаёт каждому участнику то, что он может принять. Без simulcast — 480p как baseline для мобильных, 720p для десктопа. 1080p в большинстве сценариев избыточен и съедает трафик.

WebRTC через корпоративный прокси работает?

Часто нет. Многие корп-прокси режут UDP, на котором работает WebRTC. Решение — TURN over TCP/443 (как HTTPS) и TURN over TLS — coturn умеет оба. Если у вашей аудитории много корп-юзеров (B2B-инструменты), обязательно настройте оба варианта, иначе процент жалоб «не работает на работе» будет высокий.