Реализация WebRTC для видеозвонков на сайте

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация WebRTC для видеозвонков на сайте
Сложная
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация WebRTC для видеозвонков на сайте

WebRTC — это не просто «добавить видеозвонки». Это комплекс протоколов: ICE, STUN, TURN, SDP-negotiation, DTLS-SRTP и медиапайплайн через getUserMedia. Без понимания каждого из этих компонентов получаются звонки, которые работают только в локальной сети или падают за NAT.

Из чего состоит WebRTC-стек

Браузер предоставляет RTCPeerConnection — центральный объект, который управляет всем: ICE-кандидатами, медиапотоками, шифрованием. Поверх него нужен сигнальный сервер — WebRTC сам по себе не определяет, как два клиента обмениваются SDP-офферами. Это отдельная задача.

Типичный стек для продакшена:

Компонент Варианты
Сигнальный сервер Socket.IO, WebSocket (Go/Node), Phoenix Channels
ICE/STUN coturn, Twilio STUN, Google STUN
TURN-сервер coturn на выделенном VPS, Twilio TURN, Xirsys
Медиасервер (SFU) mediasoup, Janus, LiveKit, Jitsi Videobridge
Клиентская библиотека нативный RTCPeerConnection или simple-peer, mediasoup-client

Для P2P-звонков (до 4 участников) SFU не нужен. При 5+ участниках mesh-топология создаёт n(n-1)/2 соединений на каждого — это неприемлемо. Нужен SFU.

Архитектура P2P-звонка

Alice                Signal Server              Bob
  |------ offer SDP -------->|                   |
  |                          |------ offer SDP ->|
  |<----- answer SDP --------|                   |
  |                          |<---- answer SDP --|
  |<========= ICE candidates exchange =========>|
  |<============== DTLS handshake ==============>|
  |<======= encrypted RTP/RTCP media stream ====>|

Каждый браузер собирает ICE-кандидатов: host (локальный IP), srflx (публичный IP через STUN), relay (через TURN). TURN-ретрансляция нужна примерно в 15–20% соединений — корпоративные файрволы, симметричный NAT.

ICE и TURN: почему без этого ничего не работает

STUN-сервер отвечает на вопрос «какой у меня внешний IP?». TURN-сервер проксирует медиатрафик, когда прямое соединение невозможно. coturn — стандартный выбор для self-hosted:

# /etc/turnserver.conf
listening-port=3478
tls-listening-port=5349
realm=yourdomain.com
server-name=yourdomain.com
lt-cred-mech
use-auth-secret
static-auth-secret=YOUR_SECRET
total-quota=100
bps-capacity=0
stale-nonce=600
cert=/etc/letsencrypt/live/yourdomain.com/fullchain.pem
pkey=/etc/letsencrypt/live/yourdomain.com/privkey.pem

TURN через TLS на 443 порту обходит большинство корпоративных ограничений.

Сигнальный сервер на Node.js + Socket.IO

// server.js
io.on('connection', (socket) => {
  socket.on('join-room', (roomId, userId) => {
    socket.join(roomId);
    socket.to(roomId).emit('user-connected', userId);

    socket.on('offer', (offer, targetId) => {
      io.to(targetId).emit('offer', offer, socket.id);
    });

    socket.on('answer', (answer, targetId) => {
      io.to(targetId).emit('answer', answer, socket.id);
    });

    socket.on('ice-candidate', (candidate, targetId) => {
      io.to(targetId).emit('ice-candidate', candidate, socket.id);
    });

    socket.on('disconnect', () => {
      socket.to(roomId).emit('user-disconnected', userId);
    });
  });
});

RTCPeerConnection на клиенте

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.yourdomain.com:3478' },
    {
      urls: 'turn:turn.yourdomain.com:3478',
      username: generateTurnUsername(ttl),
      credential: generateTurnCredential(username, secret),
    },
  ],
  iceTransportPolicy: 'all', // 'relay' для принудительного TURN
});

// Добавляем медиа
const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: { ideal: 30 } },
  audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 48000 },
});

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

// Negotiation
pc.onicecandidate = ({ candidate }) => {
  if (candidate) socket.emit('ice-candidate', candidate, targetId);
};

pc.onnegotiationneeded = async () => {
  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);
  socket.emit('offer', offer, targetId);
};

Кодеки и качество

По умолчанию браузеры договариваются о кодеках через SDP. Для видео это VP8, VP9 или H.264; для аудио — Opus. Принудительная установка предпочтительного кодека через манипуляцию SDP:

function preferCodec(sdp, codecName) {
  const lines = sdp.split('\n');
  // Находим PT кодека и переставляем в начало m= секции
  // ...
  return lines.join('\n');
}

const offer = await pc.createOffer();
offer.sdp = preferCodec(offer.sdp, 'VP9'); // VP9 — лучшее качество при той же пропускной способности
await pc.setLocalDescription(offer);

Адаптивный битрейт через RTCRtpSender.setParameters:

const sender = pc.getSenders().find(s => s.track.kind === 'video');
const params = sender.getParameters();
params.encodings[0].maxBitrate = 800000; // 800 kbps
await sender.setParameters(params);

Simulcast для масштабируемых конференций

При работе с SFU (mediasoup, LiveKit) используется Simulcast — клиент отправляет несколько потоков с разным разрешением:

pc.addTransceiver(videoTrack, {
  direction: 'sendonly',
  sendEncodings: [
    { rid: 'low',  maxBitrate: 150000, scaleResolutionDownBy: 4 },
    { rid: 'mid',  maxBitrate: 500000, scaleResolutionDownBy: 2 },
    { rid: 'high', maxBitrate: 1500000 },
  ],
});

SFU выбирает нужный слой для каждого получателя в зависимости от его пропускной способности.

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

Серверная запись через SFU предпочтительнее клиентской. Если нужна клиентская:

const recorder = new MediaRecorder(stream, {
  mimeType: 'video/webm;codecs=vp9,opus',
  videoBitsPerSecond: 2500000,
});

const chunks = [];
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = () => {
  const blob = new Blob(chunks, { type: 'video/webm' });
  uploadToServer(blob);
};

recorder.start(1000); // chunk каждую секунду для надёжности

Диагностика и мониторинг

getStats() — основной инструмент отладки:

setInterval(async () => {
  const stats = await pc.getStats();
  stats.forEach(report => {
    if (report.type === 'inbound-rtp' && report.kind === 'video') {
      console.log({
        packetsLost: report.packetsLost,
        jitter: report.jitter,
        framesDecoded: report.framesDecoded,
        framesPerSecond: report.framesPerSecond,
      });
    }
  });
}, 2000);

Для продакшен-мониторинга — интеграция с Datadog WebRTC или открытый webrtc-internals (chrome://webrtc-internals) в период отладки.

Сроки и трудозатраты

  • P2P видеозвонок с сигнальным сервером — 3–5 дней (два участника, базовые контролы)
  • Групповые звонки через SFU (mediasoup/LiveKit) — 2–3 недели (настройка сервера, масштабирование, комнаты)
  • Запись + постобработка — плюс 1 неделя
  • Полноценная конференц-платформа (комнаты, чат, screenshare, запись) — 6–10 недель

Совместимость

WebRTC поддерживается во всех современных браузерах. Safari поддерживает с версии 11, но имеет ограничения: нет поддержки Insertable Streams в старых версиях, проблемы с renegotiation. iOS требует нативного приложения или PWA — браузерный WebRTC на iOS Safari работает с версии 14.5.