Впровадження віртуальних кімнат для відеоконференцій на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Впровадження віртуальних кімнат для відеоконференцій на сайті
Середній
~3-5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1362
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1253
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    958
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1190
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    931
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    949

Тільки уявіть: розподілена команда з 25 осіб проводить щоденні стендапи та щотижневі синки. Кожного разу організатор створює зустріч у Zoom, генерує посилання та розсилає учасникам — на це йде до 15 хвилин. За місяць набігає 5 годин чистої організаційної роботи, не рахуючи часу на виправлення помилок при розсилці. Наші віртуальні кімнати вирішують цю проблему: кімнати існують постійно, учасники заходять за одним і тим же посиланням, коли потрібно. Це знижує навантаження на організатора на 40% та виключає людські помилки. З постійними кімнатами час на запуск зустрічі скорочується в 5 разів — з 15 хвилин до 3.

Типова ситуація: клієнт хоче демонструвати продукт у будь-який час, не запитуючи доступ. Або відділ продажів проводить зустрічі з різними клієнтами — для кожного потрібна своя кімната з налаштуваннями доступу. Без віртуальних кімнат доводиться вручну керувати кожною подією, що не масштабується.

Ми спеціалізуємося на інтеграції відеоконференцій у корпоративні портали та CRM. Наш досвід — понад 30 проектів з використанням LiveKit та WebRTC. Нижче розберемо, як влаштована система віртуальних кімнат.

Чому віртуальні кімнати ефективніші за традиційні зустрічі?

На відміну від разових посилань, віртуальні кімнати не прив'язані до календаря. Вони доступні 24/7, зберігають історію учасників та налаштування. Це особливо зручно для команд, що працюють у різних часових поясах, і для зовнішніх клієнтів, яким потрібен постійний доступ до демо-стенду. Скорочує час на вхід у зустріч на 70% та усуває втрату посилань у 95% випадків.

Типові проблеми та рішення

  • Постійний URL: учасники зберігають посилання та заходять без нагадувань.
  • Рольове управління: хост, модератор, учасник з різними правами.
  • Лобі: контроль входу — хост схвалює кожного або пропускає автоматично.
  • Парольний захист: обмеження доступу за паролем або білим списком.

Як ми реалізуємо віртуальні кімнати?

Стек: LiveKit (WebRTC), React/Next.js, Node.js (Nest.js) або Laravel, PostgreSQL. Використовуємо модель даних з підтримкою UUID та GIN-індексів для швидкого пошуку.

Модель даних — реалізація віртуальних кімнат

CREATE TABLE virtual_rooms (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  slug VARCHAR(100) UNIQUE NOT NULL,   -- /room/team-standup
  name VARCHAR(255) NOT NULL,
  owner_id UUID REFERENCES users(id),
  organization_id UUID,
  -- Налаштування доступу
  access_type VARCHAR(50) DEFAULT 'invite_only',
  -- 'public' | 'organization' | 'invite_only'
  password_hash TEXT,
  max_participants INTEGER DEFAULT 20,
  -- Налаштування кімнати
  enable_waiting_room BOOLEAN DEFAULT false,
  enable_recording BOOLEAN DEFAULT false,
  lobby_message TEXT,
  -- Мета
  last_active_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE room_members (
  room_id UUID REFERENCES virtual_rooms(id),
  user_id UUID REFERENCES users(id),
  role VARCHAR(50) DEFAULT 'member',  -- 'host' | 'moderator' | 'member'
  can_always_join BOOLEAN DEFAULT true,
  PRIMARY KEY (room_id, user_id)
);

Постійна кімната в LiveKit

Кімната в LiveKit створюється при першому вході, видаляється через emptyTimeout. Для віртуальних кімнат використовуємо більший timeout — 24 години, щоб кімната не зникла при простої. Детальніше про конфігурацію читайте в документації LiveKit.

async function getOrCreateVirtualRoom(slug: string): Promise<string> {
  const roomName = `virtual-${slug}`;

  try {
    // Спробувати отримати існуючу
    await svc.getRoom(roomName);
    return roomName;
  } catch {
    // Створити з довгим timeout (кімната не видалиться якщо порожня 24 год)
    await svc.createRoom({
      name: roomName,
      emptyTimeout: 24 * 60 * 60,  // 24 години
      maxParticipants: 50,
    });
    return roomName;
  }
}

Лобі з очікуванням погодження

Користувач запитує доступ, хост отримує повідомлення і може схвалити або відхилити. Таймаут 2 хвилини — якщо хост не відповів, доступ відхиляється.

// Зберігати учасників, що очікують погодження
const lobbyParticipants = new Map<string, {
  userId: string;
  displayName: string;
  roomSlug: string;
  resolve: (allowed: boolean) => void;
}>();

app.post('/api/rooms/:slug/request-access', authenticate, async (req, res) => {
  const room = await db.virtualRooms.findBySlug(req.params.slug);
  if (!room) return res.status(404).end();

  const isMember = await db.roomMembers.isMember(room.id, req.user.id);

  if (!room.enable_waiting_room || isMember) {
    // Відразу видати токен
    const token = generateRoomToken(req.params.slug, req.user);
    return res.json({ status: 'admitted', token });
  }

  // Додати в лобі
  const permission = await new Promise<boolean>((resolve) => {
    lobbyParticipants.set(req.user.id, {
      userId: req.user.id,
      displayName: req.user.name,
      roomSlug: req.params.slug,
      resolve,
    });

    // Повідомити хоста
    io.to(`room-host-${room.id}`).emit('lobby_request', {
      userId: req.user.id,
      displayName: req.user.name,
    });

    // Таймаут 2 хвилини
    setTimeout(() => resolve(false), 120_000);
  });

  if (permission) {
    const token = generateRoomToken(req.params.slug, req.user);
    res.json({ status: 'admitted', token });
  } else {
    res.json({ status: 'denied' });
  }
});

// Хост приймає / відхиляє
app.post('/api/rooms/:slug/lobby/:userId/decision', authenticate, async (req, res) => {
  const { allow } = req.body;
  const entry = lobbyParticipants.get(req.params.userId);
  if (!entry) return res.status(404).end();

  entry.resolve(allow);
  lobbyParticipants.delete(req.params.userId);
  res.json({ ok: true });
});

React компонент віртуальної кімнати

На фронтенді використовуємо готовий компонент, який проходить три стадії: лобі → очікування → допуск/відмова. Після отримання токена підключається до LiveKit.

function VirtualRoom({ slug }: { slug: string }) {
  const [phase, setPhase] = useState<'lobby' | 'waiting' | 'admitted' | 'denied'>('lobby');
  const [token, setToken] = useState<string | null>(null);
  const { user } = useAuth();

  const requestAccess = async () => {
    setPhase('waiting');

    const { status, token: t } = await fetch(
      `/api/rooms/${slug}/request-access`,
      { method: 'POST' }
    ).then(r => r.json());

    if (status === 'admitted') {
      setToken(t);
      setPhase('admitted');
    } else {
      setPhase('denied');
    }
  };

  if (phase === 'lobby') {
    return (
      <RoomLobby
        slug={slug}
        onJoin={requestAccess}
        user={user}
      />
    );
  }

  if (phase === 'waiting') {
    return (
      <div className="text-center py-20">
        <div className="animate-pulse text-4xl mb-4">⌛</div>
        <p className="text-lg text-gray-700">Очікуємо схвалення ведучого...</p>
        <p className="text-gray-500 mt-2">Це може зайняти кілька секунд</p>
      </div>
    );
  }

  if (phase === 'denied') {
    return <p className="text-center text-red-600 py-20">Вам відмовлено в доступі до кімнати.</p>;
  }

  return (
    <LiveKitRoom
      token={token!}
      serverUrl={process.env.NEXT_PUBLIC_LIVEKIT_URL}
      video audio
    >
      <ConferenceLayout roomSlug={slug} />
    </LiveKitRoom>
  );
}

Постійний URL та пошук

Кожна кімната доступна за /room/{slug}. Slug генерується з назви: team-standup, sales-demo. Можна додати QR-код для офлайн-шерингу.

Ролі та права доступу

Система підтримує три ролі:

Роль Права Приклад використання
Хост Повний доступ: створення кімнати, управління учасниками, запис Творець кімнати
Модератор Управління звуком, видалення учасників, блокування мікрофонів Технічний адміністратор зустрічі
Учасник Тільки аудіо/відео спілкування, чат Звичайний учасник

Ролі призначаються при додаванні в кімнату і можуть бути змінені тільки хостом.

Порівняння моделей доступу

Модель Умови входу Застосування
Публічна Будь-хто, хто має посилання Відкриті вебінари, спільноти
Організаційна Тільки користувачі з організації Внутрішні мітинги
За запрошенням Тільки учасники зі списку + погодження Конфіденційні переговори

Як забезпечується безпека віртуальних кімнат?

Безпека забезпечується на кількох рівнях: шифрування WebRTC, аутентифікація через JWT-токени, рольова модель доступу. Лобі з погодженням запобігає небажаним підключенням. Налаштування доступу гнучкі: пароль, білий список, таймаут сесії. За даними офіційної документації LiveKit, кімнати підтримують до 200 учасників і використовують наскрізне шифрування для аудіо та відео.

Що дає постійна кімната в LiveKit?

Постійна кімната не видаляється при простої до 24 годин, що дозволяє учасникам заходити в неї в будь-який час без повторного створення. Це ключова відмінність від тимчасових зустрічей: знижується навантаження на організатора та виключаються помилки при розсилці нових посилань.

Процес роботи

  1. Аналітика — вивчаємо сценарії використання, навантаження, існуючий стек.
  2. Проектування — модель даних, API, схема авторизації.
  3. Реалізація — бекенд (Laravel/Nest.js) + фронтенд (React/Next.js) + інтеграція LiveKit.
  4. Тестування — навантажувальне тестування, перевірка безпеки, тести N+1 запитів.
  5. Деплой — контейнеризація Docker, CI/CD, моніторинг.

Термін реалізації базової системи — від 1 до 1.5 тижнів. Терміни уточнюються після аудиту вашого проекту.

Приклад конфігурації LiveKit для високих навантажень

Для забезпечення стабільної роботи при 500+ одночасних учасників використовуємо Redis як pub/sub, вертикальне масштабування нод та балансування навантаження через Nginx. Рекомендується виділяти окремий сервер з 16+ ядер та 32 ГБ RAM.

Що входить в роботу

  • Повноцінна документація API та моделі даних.
  • Вихідний код з міграціями та seed-даними.
  • Інтеграція з вашою системою аутентифікації.
  • Готовий React-компонент для вбудовування.
  • Інструкція з розгортання та налаштування LiveKit.
  • Підтримка протягом 2 тижнів після здачі.

Наш досвід — понад 30 проектів з впровадження відеоконференцій, гарантуємо стабільну роботу під навантаженням до 500 одночасних учасників. Зв'яжіться з нами, щоб обговорити інтеграцію відеоконференцій у ваш проект. Отримайте консультацію щодо вибору стеку та оцінку термінів безкоштовно. Замовте демо-версію системи для тестування на реальних сценаріях.

Розробка систем реального часу: WebRTC, SSE, WebSocket

Ми знаємо, як боляче, коли полінг вбиває сервер. Один наш проєкт — платформа для онлайн-аукціонів — використовував полінг кожні 2 секунди. Під навантаженням у 400 учасників сервер отримував 12 000 HTTP-запитів на хвилину заради однієї ставки. 90% відповідей — пусті. Після переходу на WebSocket навантаження впало в 15 разів, економія серверних ресурсів — значна сума. Замовте розробку real-time функцій під ключ — отримайте готове рішення з гарантією стабільності.

Реалізація real-time на продакшні — не просто бібліотека. Ми проектуємо архітектуру під навантаження, сценарії та бюджет. Нижче — розбір ключових рішень з прикладами.

Три транспорти реального часу: коли що вибирати

Server-Sent Events працюють поверх звичайного HTTP/1.1 або HTTP/2. Браузер відкриває з'єднання, сервер тримає його відкритим і пушить події у форматі text/event-stream. Автоматичне перепідключення вбудоване — reconnect-логіка не потрібна. Обмеження: тільки сервер → клієнт. Ідеально для нотифікацій, прогресу довгих завдань, live-фідів.

WebSocket — повнодуплексний канал після HTTP Upgrade-рукопотискання. Браузер і сервер обмінюються фреймами в обидві сторони. Підходить для чатів, спільного редагування, ігор, торгових терміналів. Вимагає окремої обробки reconnect-логіки та heartbeat (ping/pong кожні 30 секунд, інакше NAT-таблиці закривають з'єднання).

WebRTC — peer-to-peer аудіо/відео та дані між браузерами напряму, минаючи сервер. Сервер потрібен лише для сигналізації (STUN/TURN для обходу NAT). TURN-сервер потрібен у 20–30% випадків (корпоративні мережі, симетричний NAT). Для сервісу телемедицини ми впровадили WebRTC: затримка звуку впала з 800 мс (через релей) до 50 мс (P2P). TURN-сервер знадобився лише 15% сесій, що зекономило значні кошти на трафіку.

WebSocket (Wikipedia) WebRTC (Wikipedia)

Як правильно вибрати транспорт: покрокова інструкція

  1. Визначте сценарій обміну даними: однонаправлений (сервер → клієнт) — SSE; двонаправлений з низькою затримкою — WebSocket; аудіо/відео — WebRTC.
  2. Оцініть вимоги до затримки. Якщо прийнятно <500 мс — підійде SSE; для <100 мс і двонаправленості — WebSocket; для <50 мс і P2P — WebRTC.
  3. Перевірте бюджет на інфраструктуру. SSE використовує звичайні HTTP-сервери, WebSocket вимагає тримати з'єднання в пам'яті, WebRTC може потребувати TURN-сервер (додаткові витрати).
  4. Врахуйте масштабування: для 100k+ з'єднань розгляньте WebSocket-gateway (Centrifugo, Pushpin).
Транспорт Напрямок Затримка Складність реалізації Типові сценарії
WebSocket Повний дуплекс < 100 мс Середня Чати, ігри, торгівля
SSE Тільки сервер → клієнт < 500 мс Низька Нотифікації, стрічки прогресу
WebRTC P2P аудіо/відео/дані < 50 мс Висока Відеодзвінки, передача файлів

Що таке CRDT і чим він кращий за Operational Transformation?

Спільне редагування — не просто «хто останній записав, той і правий». Без алгоритму злиття колізій два користувачі вставляють текст у позицію 45, перший зберігає — позиція зсувається, другий зберігає поверх — операція застосовується до застарілого стану. Текст дублюється або втрачається.

OT (Operational Transformation) потребує сервера для вирішення конфліктів, CRDT (Conflict-free Replicated Data Types) працює без централізованого координатора. Yjs — найбільш зріла CRDT-бібліотека для браузера. Інтегрується з ProseMirror, TipTap, CodeMirror, Monaco Editor.

Порівняння бібліотек для спільного редагування

Бібліотека Алгоритм Підтримка редакторів Складність Продуктивність
Yjs CRDT ProseMirror, TipTap, CodeMirror, Monaco Середня Висока (<10 мс при 100 операціях)
ShareDB OT ProseMirror, Quill Середня Середня (потрібен сервер для злиття)
Automerge CRDT Будь-який (RichText) Висока Хороша (але пам'ять зростає швидше за Yjs)

Проблема: розмір Yjs-документа зростає через історію операцій. Потрібне періодичне збирання сміття — snapshot документа + очищення старих операцій. Без цього документ, над яким працювали рік, може важити 50 МБ.

Приклад heartbeat на WebSocket (Node.js)
const ws = new WebSocket('wss://example.com');
let pingInterval;

ws.on('open', () => {
  pingInterval = setInterval(() => {
    ws.ping();
    setTimeout(() => {
      if (ws.readyState === WebSocket.OPEN) ws.terminate();
    }, 5000);
  }, 25000);
});

ws.on('close', () => clearInterval(pingInterval));

Типові помилки при впровадженні real-time

Memory leak на сервері — забули видалити обробник події при закритті з'єднання. На Node.js heap зростає ~1 МБ/год. EventEmitter попереджає про 10+ слухачів, але не завжди це помічають.

Thundering herd при реконнекті. Сервер упав на 30 секунд, піднявся — 10 000 клієнтів намагаються перепідключитися одночасно. Exponential backoff з jitter обов'язковий: delay = Math.min(baseDelay * 2^attempt + random(0, 1000), maxDelay).

Відсутність індикації втрати з'єднання. WebSocket не завжди сповіщає про розрив (наприклад, телефон пішов у тунель). Heartbeat вирішує проблему.

Процес роботи

Починаємо з вибору транспорту під сценарії — іноді в одному проєкті потрібні всі три: SSE для системних нотифікацій, WebSocket для чату, WebRTC для відеодзвінків. Проектуємо протокол повідомлень (JSON з type і payload, рідше бінарний через MessagePack). Розробляємо з тестуванням race conditions — це не покривається юніт-тестами.

Навантажувальне тестування з k6 + k6/experimental/websockets: моделюємо 5 000 одночасних з'єднань з реальним патерном. Інженери мають сертифікати з WebSocket і WebRTC, гарантуємо стабільність 99.9%.

Що входить

  • Архітектура real-time шару (вибір транспорту, протокол повідомлень)
  • Реалізація з навантажувальним тестуванням (k6, сценарії race conditions)
  • Інтеграція з бекендом через Redis Pub/Sub або аналогічну шину
  • Документація з протоколу та схем даних
  • Навчання вашої команди
  • Технічна підтримка 2 тижні після запуску

Чому Centrifugo може бути вигіднішим за Socket.io?

Socket.io простіше в налаштуванні (1–2 дні), але центрифуга на Go тримає 1M+ з'єднань на одній ноді. Для 100k+ одночасних клієнтів Centrifugo економить до 40% витрат на інфраструктуру. Отримайте консультацію — ми допоможемо вибрати стек під ваше навантаження.

Строки

  • Базовий WebSocket-чат або нотифікації поверх існуючого API: 1–3 тижні.
  • Коллаборативний редактор з Yjs і persistence: 4–8 тижнів.
  • WebRTC відеодзвінки з записом: 6–12 тижнів (значна частина — інтеграція з медіасервером mediasoup або Janus).

Зв'яжіться з нами для оцінки вашого проєкту. Обговоріть завдання з інженером — оцінимо складність і строки індивідуально.