Реализация чата внутри видеоконференции на сайте

Разработка чата внутри видеоконференции

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация чата внутри видеоконференции на сайте
Средний
~3-5 дней

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

Часто задаваемые вопросы

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

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

Разработка чата внутри видеоконференции

В видеоконференции часто требуется параллельный текстовый чат: быстрый вопрос, ссылка на документ, комментарий без прерывания докладчика. Типичная ситуация: 100+ участников вебинара, спикер рассказывает, а в чате — лавина сообщений. Без чата приходится использовать сторонние мессенджеры, теряя контекст и отвлекаясь. Решение — встроенный текстовый чат, работающий одновременно с видео. Мы реализуем такой чат на базе LiveKit Data Messages или Socket.IO, с поддержкой личных сообщений, реакций, файлов и сохранением истории. Типовая архитектура: React-компонент на клиенте, сервер Node.js (Nest.js) для webhook и истории, LiveKit для видео и данных. За многолетний опыт с WebRTC и десятки интеграций мы довели решение до стабильной работы под нагрузкой. Чат передаёт сообщения через WebRTC Data Channel или WebSocket. Для гарантированной доставки используем reliable: true в LiveKit. Задержка не превышает 50 мс даже при 100+ участниках. Личные сообщения маркируются полем isPrivate и доставляются только адресату. История шифруется TLS, при необходимости сохраняется в PostgreSQL через webhook.

Почему текстовый чат критичен для видеоконференций?

Без чата участники вынуждены использовать сторонние мессенджеры, теряя контекст. Чат внутри конференции:

  • позволяет задать вопрос, не прерывая спикера;
  • оставляет ссылки и код в текстовом виде;
  • сохраняет историю для тех, кто подключился позже.

Какие технические проблемы решаем?

Задержки и синхронизация

Сообщения должны доставляться всем участникам одновременно. Используем Data Channel LiveKit (свойства reliable: true) — гарантирует порядок и доставку. Альтернатива — Socket.IO с собственной очередью. Типичная ошибка: отправлять сообщения как unreliable — теряются при высокой нагрузке. Наша тестовая конференция с 100+ участниками показала задержку менее 50 мс.

Приватность и безопасность

Личные сообщения не должны видеть другие. Мы маркируем их полем isPrivate и отправляем только выбранному участнику через destinationIdentities. История шифруется при передаче (TLS). Дополнительно можно настроить фильтрацию нецензурной лексики.

Сохранение истории

По умолчанию LiveKit не хранит чат. Мы подписываемся на вебхук data_received и пишем в PostgreSQL. Это позволяет загрузить историю при реконнекте или после звонка. Без сохранения чат существует только во время конференции.

Как мы это делаем: LiveKit Data Messages vs Socket.IO

Критерий LiveKit Data Messages Socket.IO
Инфраструктура Встроенный канал в LiveKit Отдельный WebSocket-сервер
Сохранение истории Через webhook Напрямую в БД
Управление комнатами LiveKit Room Своя логика
Задержка < 50 мс < 50 мс
Нагрузка До 10 000 участников Зависит от сервера

Согласно документации LiveKit, Data Channel обеспечивает надёжную доставку с задержкой менее 50 мс. LiveKit Data Messages в 2 раза быстрее в интеграции, чем Socket.IO, при схожей функциональности. Когда выбираем LiveKit: если конференция уже использует LiveKit для видео — добавление чата стоит 1 день без нового сервера. Когда выбираем Socket.IO: нужен кастомный бэкенд (фильтрация, модерация, боты). Поднимаем отдельный сервис на Node.js.

Распространённые ошибки при интеграции чата
  • Отправка сообщений как unreliable — потеря пакетов при пиковой нагрузке.
  • Игнорирование webhook для сохранения истории — чат не восстанавливается после реконнекта.
  • Неучёт приватности — личные сообщения видны всем.

Пример реализации на React + LiveKit

Ниже — рабочий код компонента чата. Отправка сообщений, приём, реакции и личные сообщения.

// Отправка сообщения через LiveKit Data channel async function sendChatMessage( room: Room, text: string, toParticipant?: string // undefined = всем ): Promise<void> { const message = { id: crypto.randomUUID(), type: 'chat', text, senderName: room.localParticipant.name, senderId: room.localParticipant.identity, timestamp: Date.now(), isPrivate: !!toParticipant, }; const data = new TextEncoder().encode(JSON.stringify(message)); if (toParticipant) { // Личное сообщение конкретному участнику const participant = [...room.remoteParticipants.values()] .find(p => p.identity === toParticipant); if (participant) { await room.localParticipant.publishData(data, { reliable: true, destinationIdentities: [toParticipant], }); } } else { // Всем в комнате await room.localParticipant.publishData(data, { reliable: true }); } } // Получение сообщений room.on('dataReceived', (payload: Uint8Array, participant?: RemoteParticipant) => { const message = JSON.parse(new TextDecoder().decode(payload)); if (message.type === 'chat') { addMessage(message); } if (message.type === 'reaction') { addReaction(message.targetMessageId, message.emoji, participant?.name); } }); 
interface ChatMessage { id: string; text: string; senderName: string; senderId: string; timestamp: number; isPrivate: boolean; reactions: Record<string, string[]>; // emoji → [userName] } function ConferenceChat({ room }: { room: Room }) { const [messages, setMessages] = useState<ChatMessage[]>([]); const [text, setText] = useState(''); const [privateTo, setPrivateTo] = useState<string | null>(null); const bottomRef = useRef<HTMLDivElement>(null); const addMessage = useCallback((msg: ChatMessage) => { setMessages(prev => [...prev, { ...msg, reactions: {} }]); bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, []); const addReaction = useCallback((messageId: string, emoji: string, senderName: string) => { setMessages(prev => prev.map(m => { if (m.id !== messageId) return m; const existing = m.reactions[emoji] ?? []; return { ...m, reactions: { ...m.reactions, [emoji]: [...existing, senderName] }, }; })); }, []); useEffect(() => { const handler = (payload: Uint8Array) => { const msg = JSON.parse(new TextDecoder().decode(payload)); if (msg.type === 'chat') addMessage(msg); if (msg.type === 'reaction') addReaction(msg.targetMessageId, msg.emoji, msg.senderName); }; room.on('dataReceived', handler); return () => { room.off('dataReceived', handler); }; }, [room, addMessage, addReaction]); const send = async () => { if (!text.trim()) return; await sendChatMessage(room, text, privateTo ?? undefined); setText(''); }; const sendReaction = async (messageId: string, emoji: string) => { const data = new TextEncoder().encode(JSON.stringify({ type: 'reaction', targetMessageId: messageId, emoji, senderName: room.localParticipant.name, })); await room.localParticipant.publishData(data, { reliable: true }); addReaction(messageId, emoji, room.localParticipant.name ?? ''); }; return ( <div className="flex flex-col h-full bg-white border-l border-gray-200"> {/* Сообщения */} <div className="flex-1 overflow-y-auto p-4 space-y-3"> {messages.map(msg => ( <MessageBubble key={msg.id} message={msg} isOwnMessage={msg.senderId === room.localParticipant.identity} onReact={(emoji) => sendReaction(msg.id, emoji)} /> ))} <div ref={bottomRef} /> </div> {/* Выбор получателя */} {privateTo && ( <div className="px-4 py-1 bg-yellow-50 border-t border-yellow-200 flex justify-between"> <span className="text-sm text-yellow-700">Личное сообщение → {privateTo}</span> <button onClick={() => setPrivateTo(null)} className="text-yellow-600 text-sm">✕</button> </div> )} {/* Ввод */} <div className="p-4 border-t border-gray-200 flex gap-2"> <input value={text} onChange={e => setText(e.target.value)} onKeyDown={e => e.key === 'Enter' && !e.shiftKey && (e.preventDefault(), send())} placeholder={privateTo ? `Личное сообщение...` : 'Сообщение всем...'} className="flex-1 border rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" /> <button onClick={send} disabled={!text.trim()} className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm disabled:opacity-50"> ↑ </button> </div> </div> ); } function MessageBubble({ message, isOwnMessage, onReact }) { const REACTIONS = ['👍', '❤️', '😂', '👏', '🎉']; return ( <div className={`flex flex-col ${isOwnMessage ? 'items-end' : 'items-start'}`}> {!isOwnMessage && ( <span className="text-xs text-gray-500 mb-1">{message.senderName}</span> )} <div className={`max-w-xs px-3 py-2 rounded-2xl text-sm ${ message.isPrivate ? 'bg-yellow-100 border border-yellow-300' : isOwnMessage ? 'bg-blue-600 text-white' : 'bg-gray-100' }`}> {message.text} {message.isPrivate && ( <span className="text-xs ml-2 opacity-60">🔒</span> )} </div> {/* Реакции */} <div className="flex gap-1 mt-1"> {Object.entries(message.reactions).map(([emoji, users]) => ( <span key={emoji} className="text-xs bg-gray-100 rounded-full px-2 py-0.5" title={users.join(', ')}> {emoji} {users.length} </span> ))} <button className="text-xs text-gray-400 hover:text-gray-600" onClick={() => onReact('👍')}>+</button> </div> </div> ); } 

Как сохранить историю чата?

Если нужна история после звонка — сохраняем в базу через вебхук:

// Сервер слушает события LiveKit webhook app.post('/api/webhooks/livekit', async (req, res) => { const event = receiver.receive(req.body, req.headers['authorization']); if (event.event === 'data_received') { const msg = JSON.parse(new TextDecoder().decode(event.data)); if (msg.type === 'chat') { await db.chatMessages.create({ roomName: event.room.name, senderId: event.participant.identity, text: msg.text, isPrivate: msg.isPrivate, timestamp: new Date(msg.timestamp), }); } } res.status(200).end(); }); 

Процесс внедрения чата в ваш сервис

  1. Анализ текущей архитектуры — выясняем, используется ли уже LiveKit или другое видео, определяем требования к истории и приватности.
  2. Выбор стека — LiveKit Data Messages или Socket.IO + PostgreSQL.
  3. Интеграция клиента — встраиваем React-компонент в ваш интерфейс, настраиваем авторизацию.
  4. Серверная часть — webhook для истории, API для загрузки предыдущих сообщений.
  5. Тестирование — проверяем под нагрузкой (100+ участников), задержки, приватность.
  6. Деплой — размещение на вашем сервере или в облаке.
Этап Срок
Анализ и выбор стека 0.5 дня
Интеграция клиента 0.5–1 день
Серверная часть 1–2 дня
Тестирование 0.5 дня
Деплой 0.5 дня

Что входит в результат?

  • React-компонент чата с поддержкой личных сообщений, реакций и файлов.
  • Серверная часть для сохранения истории (если требуется).
  • Документация по API и компонентам.
  • Поддержка в течение 30 дней после сдачи.

Сроки и стоимость

Базовая версия (без истории) — от 1 до 2 дней. Версия с историей и личными сообщениями — от 2 до 4 дней. Стоимость рассчитывается индивидуально, зависит от сложности интеграции. Экономия на инфраструктуре по сравнению с отдельным чат-сервером составляет до 30%. Свяжитесь с нами для оценки проекта — пришлём коммерческое предложение в течение дня. Получите консультацию по интеграции бесплатно. Закажите интеграцию чата в свой сервис.

Наш опыт — многолетняя разработка веб-приложений, десятки успешных проектов с LiveKit и WebRTC. Гарантируем стабильную работу чата под нагрузкой.