Реализация комнаты ожидания (Waiting Room) для видеоконференций на сайте

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

Разработка и обслуживание любых видов сайтов:

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация комнаты ожидания (Waiting Room) для видеоконференций на сайте
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

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

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка комнаты ожидания для видеоконференций

Комната ожидания позволяет ведущему контролировать, кто войдёт в конференцию. Участник видит экран ожидания, ведущий получает уведомление и принимает решение — впустить или отклонить.

Реализация через LiveKit Permissions

LiveKit поддерживает изменение прав участника на лету. Участник из лобби получает токен с canPublish: false, а ведущий повышает права через API.

// Токен для участника лобби — только наблюдатель
function generateLobbyToken(roomName: string, userId: string, displayName: string): string {
  const at = new AccessToken(
    process.env.LIVEKIT_API_KEY!,
    process.env.LIVEKIT_API_SECRET!,
    { identity: `lobby-${userId}`, name: displayName }
  );

  at.addGrant({
    roomJoin: true,
    room: roomName,
    canPublish: false,     // не может публиковать видео/аудио
    canSubscribe: false,   // не видит участников
    canPublishData: true,  // только данные (для запроса на вход)
  });

  return at.toJwt();
}

// Впустить участника — повысить права
async function admitParticipant(roomName: string, lobbyIdentity: string): Promise<void> {
  await svc.updateParticipant(roomName, lobbyIdentity, undefined, {
    canPublish: true,
    canSubscribe: true,
  });

  // Уведомить участника через Data message
  await svc.sendData(
    roomName,
    Buffer.from(JSON.stringify({ type: 'admitted' })),
    DataPacket_Kind.RELIABLE,
    [lobbyIdentity]
  );
}

Экран ожидания для участника

function WaitingRoom({ roomName, userId, displayName, onAdmitted }) {
  const [waitTime, setWaitTime] = useState(0);

  useEffect(() => {
    // Подключиться к комнате с токеном лобби
    const room = new Room();
    room.connect(process.env.NEXT_PUBLIC_LIVEKIT_URL, lobbyToken);

    // Отправить запрос на вход
    room.on('connected', async () => {
      await room.localParticipant.publishData(
        new TextEncoder().encode(JSON.stringify({
          type: 'lobby_request',
          userId,
          displayName,
        })),
        { reliable: true }
      );
    });

    // Слушать ответ ведущего
    room.on('dataReceived', (payload) => {
      const msg = JSON.parse(new TextDecoder().decode(payload));
      if (msg.type === 'admitted') {
        onAdmitted();
      }
      if (msg.type === 'denied') {
        // Показать сообщение об отказе
      }
    });

    // Таймер ожидания
    const timer = setInterval(() => setWaitTime(t => t + 1), 1000);
    return () => { clearInterval(timer); room.disconnect(); };
  }, []);

  const minutes = Math.floor(waitTime / 60);
  const seconds = waitTime % 60;

  return (
    <div className="min-h-screen bg-gray-900 flex items-center justify-center text-white">
      <div className="text-center max-w-md px-6">
        <div className="w-20 h-20 rounded-full bg-blue-600 flex items-center justify-center mx-auto mb-6">
          <span className="text-3xl">{displayName[0].toUpperCase()}</span>
        </div>
        <h2 className="text-2xl font-semibold mb-2">Подождите немного...</h2>
        <p className="text-gray-400 mb-6">
          Ведущий скоро впустит вас. Время ожидания:{' '}
          <span className="text-white">{String(minutes).padStart(2, '0')}:{String(seconds).padStart(2, '0')}</span>
        </p>
        <div className="flex justify-center gap-1">
          {[0, 1, 2].map(i => (
            <div key={i}
              className="w-2 h-2 rounded-full bg-blue-500 animate-bounce"
              style={{ animationDelay: `${i * 150}ms` }}
            />
          ))}
        </div>
      </div>
    </div>
  );
}

Панель ведущего — управление лобби

function HostLobbyPanel({ room }: { room: Room }) {
  const [lobbyRequests, setLobbyRequests] = useState<LobbyRequest[]>([]);

  useEffect(() => {
    room.on('dataReceived', (payload, participant) => {
      const msg = JSON.parse(new TextDecoder().decode(payload));
      if (msg.type === 'lobby_request') {
        setLobbyRequests(prev => [
          ...prev,
          { identity: participant?.identity ?? '', displayName: msg.displayName, requestedAt: new Date() }
        ]);
      }
    });
  }, [room]);

  const admit = async (identity: string) => {
    await fetch(`/api/rooms/${room.name}/admit/${identity}`, { method: 'POST' });
    setLobbyRequests(prev => prev.filter(r => r.identity !== identity));
  };

  const deny = async (identity: string) => {
    await fetch(`/api/rooms/${room.name}/deny/${identity}`, { method: 'POST' });
    setLobbyRequests(prev => prev.filter(r => r.identity !== identity));
  };

  if (lobbyRequests.length === 0) return null;

  return (
    <div className="absolute top-4 right-4 w-72 bg-white rounded-xl shadow-lg p-4 z-50">
      <h3 className="font-semibold text-gray-800 mb-3">Ожидают входа ({lobbyRequests.length})</h3>
      <div className="space-y-3">
        {lobbyRequests.map(req => (
          <div key={req.identity} className="flex items-center gap-3">
            <div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-sm font-medium text-blue-700">
              {req.displayName[0]}
            </div>
            <span className="flex-1 text-sm text-gray-800 truncate">{req.displayName}</span>
            <button onClick={() => admit(req.identity)}
              className="text-xs px-2 py-1 bg-green-600 text-white rounded">
              Впустить
            </button>
            <button onClick={() => deny(req.identity)}
              className="text-xs px-2 py-1 bg-gray-200 text-gray-700 rounded">
              Отклонить
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

Сроки

Комната ожидания с экраном участника и панелью ведущего — 1–2 дня.