Всем привет, дорогие читатели Хабр. Мы долго думали, чтобы нам сделать такое, что от нас не потребует глубоких знаний бэкенда и базы данных, но все же интересное и обучающее, исключительно ориентированное на конечного пользователя. Так мы пришли к тому, что нам бы хотелось изучить более подробно сферу WebRTC и WebSockets и решили сделать что-то похожее на Google Meet c еë основными фичами, которые более подробно описаны чуть ниже. Но давайте все по порядку :) Приготовьтесь, будет много кода!
Стек технологий
Для клиентской части мы выбрали NextJS, этот фреймворк из коробки даёт нам возможность создать "endpoint handler" в два щелчка (это нам понадобится для SocketIO), плюс супер легко настраивать навигацию по страницам.
Для стилизации, наш выбор пал на TailwindCSS, потому что мы с ним никогда раньше не работали и нам хотелось попробовать.
Прочитав пару тройку статей и просмотрев несколько видео, мы узнали, что WebRTC в чистом виде является громоздким и неповоротливым. В связи с этим перед нами стоял выбор между библиотеками PeerJS и Simple-Peer. Поэкспериментировав обеими библиотеками, для проекта мы остановились на PeerJS. PeerJS покрывает все настройки по умолчанию такие как STUN server, ICE candidate, так что можно не беспокоиться насчëт этого. Для нас это было супер решением из-за наших скудных знаний в этой области.
Более подробно о протоколах WebRTC можете изучить здесь.
Когда вы работаете с WebRTC технологией, у вас также должен быть Signaling Server в роли синхронизатора. Этот сервер поможет вам держать всех участников обновлёнными и реагировать соответственно в случае тех или иных событий пользователя. В качестве Signaling Server у нас служит SocketIO.
Для полного антуража, мы также подкрутили базовую авторизацию на платформе Auth0.
Фичи
Так какие же фичи нас ждут в этом приложении:
страница-лобби для первоначальных настроек перед входом в комнату
создание комнаты
особые возможности организатора комнаты
демонстрация экрана
отключение светового индикатора при выключении камеры
индикатор активного спикера
обмен сообщениями в реальном времени
список гостей в комнате с их статусами
Перед тем как приступить к разработке вышеперечисленных фич приложения нам потребовалось установить несколько библиотек и настроить их под наши задачи. Ниже вы можете найти ссылки на наши Pull Request-ы и официальные документации:
установка и настройка Tailwind - Pull Request, документация
интеграция Auth0 - Pull Request, документация
настройка SocketIO с NextJS - Pull Request
Перед тем как мы будем объяснять реализацию фич, мы бы хотели показать структуру папок проекта, потому что каждый код-сниппет указывает в каком файле он находится и вам будет легче сориентироваться при дальнейшем чтений.
|- app
|----| index.tsx
|- components
|----| lobby.tsx
|----| control-panel.tsx
|----| chat.tsx
|----| status.tsx
|- contexts
|----| users-connection.tsx
|----| users-settings.tsx
|- hooks
|----| use-is-audio-active.ts
|----| use-media-stream.ts
|----| use-peer.ts
|----| use-screen.ts
|- pages
|----| index.tsx
|----| room
|--------| [roomId].tsx
Переход на страницу-лобби
На главной странице приложения у пользователя есть две возможности: создать новую комнату или присоединиться к существующей. Независимо от выбора действия, пользователь попадает на страницу-лобби, где пользователь может предварительно вкл/выкл своë аудио/видео перед входом в комнату. Логика "страницы-лобби" реализована через useState.
// pages/[roomId].tsx
export default function Room(): NextPage {
const [isLobby, setIsLobby] = useState(true);
const { stream } = useMediaStream();
return isLobby
? <Lobby stream={stream} onJoinRoom={() => setIsLobby(false)} />
: <Room stream={stream} />;
}
Так как мы работаем с видео и аудио, а они являются потоком данных в определенном промежутке времени, и чтобы комфортно работать с этим типом нам нужен подходящий интерфейс. К счастью он у нас есть. Протокол MediaCapture и Streams API позволяет создать нам поток (stream) медиа данных. Поток состоит из нескольких треков (tracks), таких как видео и аудио. Наш кастомный хук useMediaStream берëт ответственность за создание и манипуляцию над потоком.
На странице-лобби происходят два действия:
управление первоначальными настройками стрима
вход в комнату.
// components/lobby.tsx
// pseudocode
const Lobby = ({
stream,
onJoinRoom,
}: {
stream: MediaStream;
onJoinRoom: () => void;
}) => {
const { toggleAudio, toggleVideo } = useMediaStream(stream);
return (
<>
<video srcObject={stream} />
<button onClick={toggleVideo}>Toggle video</button>
<button onClick={toggleAudio}>Toggle audio</button>
<button onClick={onJoinRoom}>Join</button>
</>
);
};
Просим заметить, что если track.enabled = true (track - audio / video), то состояние трека выводится из источника к слушателям без посредников (WebSocket, PeerJS), иначе, выводятся пустые кадры. Исходя из этого, не нужно хранить локальный стэйт для того чтобы вкл/выкл аудио/видео.
Полный код реализаций страницы-лобби можете посмотреть здесь.
Переход в комнату
Допустим, что человек на странице-лобби выбрал аудио и видео включенными и затем переходит в комнату. На этом моменте начинается всë самое интересное