Реализуем трехсторонний видеочат на следующих платформах: Google Chrome на десктопе, Android приложение на планшете, и iOS приложение для Apple iPhone.
Напомним два основных принципа построения видеочата:
- Каждый подключившийся пользователь может отправить (опубликовать) свой видеопоток на сервер.
- Пользователи знают имена видеопотоков друг-друга и имеют возможность их воспроизвести.
Таким образом, в видеочате из трех участников, каждому из участников придется воспроизводить по два видеопотока.
Последовательность из девяти действий выглядит как показано ниже. Сначала участники публикуют свои видеопотоки, потом воспроизводят потоки друг друга.
Публикуют потоки
1. Alice: session.createStream({name:»stream_alice»}).publish();
2. Boris: session.createStream({name:»stream_boris»}).publish();
3. Anna: session.createStream({name:»stream_anna»}).publish();
Играют потоки
4. Alice: session.createStream({name:»stream_anna»}).play();
5. Alice: session.createStream({name:»stream_boris»}).play();
6. Boris: session.createStream({name:»stream_alice»}).play();
7. Boris: session.createStream({name:»stream_anna»}).play();
8. Anna: session.createStream({name:»stream_alice»}).play();
9. Anna: session.createStream({name:»stream_boris»}).play();
От разработчика в данном случае требуется организовать передачу названий и статусов видеопотоков участникам видеочата. Это можно сделать на любой подходящей технологии, например PHP, websockets, Node.js и т.д., так как передача имени видеопотока ничем не отличается от передачи обычного текстового сообщения от одного пользователя к другому.
У потока может быть три основных статуса: PUBLISHING, PLAYING, STOPPED Для успешного воспроизведения, запрашиваемый поток должен быть в активном статусе PUBLISHING.
На этой схеме показано, как именно можно реализовать обмен именами и статусами потоков, в упрощенном варианте, когда Алиса демонстрирует свой видеопоток Борису и Анне. Такая процедура занимает 8 шагов, и может быть названа сигналингом, т.к. В результате этой процедуры проводится согласование:
- Алиса отправляет видеопоток на WCS-сервер.
- Алиса получает от сервера подтверждение в виде статуса PUBLISHING
- Алиса отправляет Борису сообщение, о том, что ее поток готов к воспроизведению.
- Алиса отправляет Анне сообщение, о том, что ее поток готов к воспроизведению.
- Анна воспроизводит видеопоток с WCS-сервера.
- Анна получает подтверждение статус PLAYING.
- Борис воспроизводит видеопоток с WCS-сервера.
- Борис получает подтверждение статус PLAYING.
В результате может быть реализован произвольный сценарий взаимодействия: с подключением двух и более пользователей к чату, с подключением просто зрителей, и т.д.
Комнаты
Организовать обмен именами видеопотоков и их статусами не так уж и сложно технически, но это требует определенных трудозатрат и работы с кодом.
С другой стороны, для такой задачи напрашивается какое-то универсальное решение, которое могло бы помочь пользователям быстро согласовать потоки и попасть в чат. Такое решение называется Комнаты или Room API.
Действительно, если двое или более пользователей взаимодействуют в одном контексте, то это похоже на комнату. Внутри комнаты пользователи видят видеопотоки друг-друга, знают кто находится в комнате и могут обмениваться сообщениями, в том числе и приватными.
Таким образом, имеем четыре объекта, которые полностью покрывают работу с комнатами:
- Room — комната
- Stream — видеопоток
- Participant — участник
- Message — сообщение
Room API дает возможность кроссплатформенно использовать перечисленные выше абстракции: Room, Stream, Participant и Message для реализации следующих функций:
Подключение
- Пользователь может подключиться к комнате.
- Пользователь задает название комнаты при подключении.
- Если комната с таким названием существует, то пользователь попадает в эту комнату.
- Если комнаты с таким названием нет, то создается новая комната и пользователь попадает во вновь созданную комнату.
- Пользователь получает уведомления о подключениях / отключениях других участников.
- Пользователь может получить список участников.
Стриминг
- Пользователь может опубликовать видеопоток внутри комнаты.
- Пользователь получает уведомления о статусах видеопотоков других участников.
- Пользователь может воспроизвести видеопоток внутри комнаты.
Сообщения
- Пользователи могут обмениваться сообщениями внутри комнаты
- Пользователи могут обмениваться изображениями или другим контентом, если он упакован в текстовый формат
- Сообщение можно отправить одному или нескольким участникам
Room API
Для Web платформы, комнаты были реализованы в виде JavaScript — модуля со следующими основными функциями:
1. Получаем соединение с сервером.
var connection = Flashphoner.roomApi.connect({urlServer: "wss://host:8443", username: "Alice"});
2. Входим в комнату.
connection.join({name: "room1"});
3. Получаем список участников комнаты.
var participants = room.getParticipants();
4. Отправляем свой видеопоток в комнату.
room.publish({ display: document.getElementById("localDisplay"), constraints: constraints, record: false, receiveVideo: false, receiveAudio: false });
5. Воспроизводим поток участника.
participant.getStreams()[0].play(document.getElementById(pDisplay))
6. Следим за участниками комнаты:
connection.on(ROOM_EVENT.JOINED, function(participant){...}); connection.on(ROOM_EVENT.LEFT, function(participant){...}); connection.on(ROOM_EVENT.PUBLISHED, function(participant){...});
JOINED — к комнате присоединился новый участник
LEFT — участник вышел
PUBLISHED — участник опубликовал свой видеопоток
7. Получаем сообщения от других участников.
connection.on(ROOM_EVENT.MESSAGE, function(participant){...});
8. Отправляем приватное сообщение конкретному участнику
participants[i].sendMessage(message);
9. Отправляем сообщение всем участникам сразу.
var participants = room.getParticipants(); for (var i = 0; i < participants.length; i++) { participants[i].sendMessage(message); } }
Таким образом, реализация комнат дает простой обмен сообщениями и статусами всех подключенных к комнате участников.
Ограничения Room API
Вся логика при работе с комнатами ложится на клиента. Сервер управляет только только базовым функционалом комнаты:
- уведомления о подключении / отключении пользователей в комнате
- уведомления о создании / отключении видеопотоков пользователями внутри комнаты
- маршрутизация сообщений указанным участникам
Таким образом, аутентификация, права доступа, роли участников (модератор, зритель, ведущий), и другая логика, должны быть реализованы на клиенте и / или стороннем бэк-энде. Комнаты лишь помогают произвести быстрый обмен информацией о видеопотоках и статусах.
Комнаты для Web, Android, iOS
В каждой из SDK (Web, Android, iOS) для работы с сервером, есть API для работы с комнатами.
Примеры входа в комнату:
Web SDK
connection.join({name: "room1"});
Android SDK
Room room = roomManager.join(roomOptions);
iOS SDK
room = [roomManager join:options];
Таким образом в одну и ту же комнату может войти три участника с трех разных платформ:
- Web — браузер
- Мобильное приложение для Android
- Мобильное приложение для iOS
Тестовые приложения для работы с комнатами
Ниже мы приведем три тестовых приложения для работы с комнатами. Каждое из них имеет открытый код и может быть собрано из исходников. Каждое из следующих приложений позволяет обмениваться видеопотоками и сообщениями трём участникам:
- Conference for Web
- Conference for Android
- Conference for iOS
Conference for Web
Код этого приложения доступен для скачивания здесь. В HTML-коде можно найти два div-элемента
Эти элементы отвечают за отображение видео участников. div — элемент localDisplay отвечает за отображение видео, захваченного с камеры.
С помощью кнопок Join / Leave можно войти и выйти из комнаты соответственно. С помощью Stop / Start можно отправить видео в комнату или остановить вещание. Поле Login должно быть уникальным, т.к. идентифицирует участника.
Следующий элемент интерфейса — это текстовый чат. Этот чат отображает сообщения полученные или отправленные другими пользователями, а также отображает информацию о событиях, происходящих в комнате.
И последний элемент — это ссылка на приглашение. Если пользователь вошел первым, создается новая комната. В данном случае с именем roomName=room-fb41b7. Если второй пользователь укажет этот же roomName, он попадет в ту же комнату. В приложении Conference for Web приглашения реализованы генерацией ссылки входа, содержащей roomName в качестве параметра. В версиях приложения для Android и iOS, имя комнаты указывается напрямую в интерфейсе.
Таким образом, тестовое приложение Conference for Web реализует все заложенные выше функции комнат и позволяет нескольким пользователям подключиться к одной и той же комнате и обмениваться видеопотоками и текстовыми сообщениями. Ниже на скриншоте показано как работает комната с тремя участниками. Было открыто три вкладки браузера Google Chrome и на каждой из вкладок было инициировано подключение к комнате.
Conference for Android
Код этого приложения доступен для скачивания здесь. В приложении можно ввести имя комнаты прямо из UI. В остальном приложение очень похоже на Conference for Web, которое было описано выше и имеет те же элементы интерфейса:
- Кнопки Leave / Join для входа и выхода из комнаты.
- Два видео окна для воспроизведения видео участников.
- Одно видео окно для отображения захвата видео с камеры.
- Текстовый чат с системной информацией и сообщениями участников
Conference for iOS
Код приложения доступен для скачивания здесь. Интерфейс приложения для iOS уже практически ничем не отличается от приложения под Android, с точностью до названия кнопок и контролов.
В итоге мы сделали тест и собрали все три платформы в одной комнате с номером 3119d6.
Браузер Google Chrome — это заяц, который выбирается из норы:
Android 5.1.1 на планшете Asus — это цветочный горшок.
iOS 10.1.1 на iPhone 6 — это Фикус Бенджамина на подоконнике.
Ниже показан полный скриншот с iOS — устройства:
Таким образом, мы завершили тестирование и обзор всех трех приложений, построенных на основе Room API, и можем переходить к исходному коду и сборке.
Сборка приложения Conference for iOS из исходников
Покажем как собрать пример Conference for iOS и опишем основные куски кода примера. Первым делом нужно обзавестись Mac-железом и установить последний Xcode.
Процедура сборки требует установки Cocoapods, скачивания кода примеров и SDK для iOS.
Собирать будем в терминале, а потом откроем проект в Xcode.
1. Устанавливаем Cocoapods
sudo gem install cocoapods
2. Скачиваем все примеры с github
git clone https://github.com/flashphoner/wcs-ios-sdk-samples.git
3. Скачиваем iOS SDK
wget https://flashphoner.com/downloads/builds/flashphoner_client/wcs-ios-sdk/WCS-iOS-SDK-2.3.0.tar.gz
4. Распаковываем архив
tar -xvzf WCS-iOS-SDK-2.3.0.tar.gz
5. Копируем папку FPWCSApi2.framework в примеры
cp -R FPWCSApi2.framework wcs-ios-sdk-samples
6. Запускаем сборку.
./build_example.sh
Если сборка прошла успешно, в терминале выводится **ARCHIVE SUCCEDED**
После того как примеры собраны из консоли с помощью Cocoapods, подтянуты и сконфигурированы все зависимости и дальнейшие сборки примеров можно проводить непосредственно из Xcode.
7. Открываем WCSExample.xcworkspace в Xcode
8. Выбираем Generic iOS Device в целях сборки. Запускаем сборку примера Conference из меню Product / Build и дожидаемся завершения.
9. Подключаем iPhone по USB и запускаем собранное приложение Conference. В консоли появляется отладочная информация.
10. На экране iPhone появляется приложение Conference
Таким образом, мы собрали мобильное приложение Conference для iOS из исходников и использовали iOS SDK версии 2.3.0 + Cocoapods для этой сборки. В результате мы смогли запустить это приложение на iPhone 6, подключенном по USB к Mac, на котором производилась эта сборка.
Код для Web, Android, iOS видеочатов
Выше мы привели примеры приложений для трех платформ, которые используют Room API и обмениваются видеопотоками внутри созданной комнаты. Попытаемся кратко перечислить основные куски кода, отвечающие за работу видеочата в каждой из этих трех платформ:
Web | Android | iOS | |
Код | JavaScript | Java | Objective-C |
Основной код | conference.js | ConferenceActivity.java | ViewController.m |
Подключение к серверу | connection = Flashphoner.roomApi.connect({urlServer: url, username: username}); | roomManager = Flashphoner.createRoomManager(roomManagerOptions); | roomManager = [FPWCSApi2 createRoomManager:options error:&error]; |
Подключение к комнате | connection.join({name: getRoomName()}); | room = roomManager.join(roomOptions); | room = [roomManager join:options]; |
Получение списка участников | var participants = room.getParticipants(); | room.getParticipants() | NSDictionary *participants = [room getParticipants]; |
Отправка видеопотока в комнату | room.publish({ display: document.getElementById(«localDisplay»), constraints: constraints, record: false, receiveVideo: false, receiveAudio: false }); | stream = room.publish(localRenderer); | publishStream = [room publish:_localDisplay]; |
Воспроизводим видеопоток участника | participant.getStreams()[0].play(document.getElementById(pDisplay)); | participant.play(participantView.surfaceViewRenderer); | [participant play:pv.display]; |
Отслеживаем присоединение новых участников к комнате | connection.on(ROOM_EVENT.JOINED, function(participant){…}); | public void onJoined(final Participant participant){…}; | [room on:kFPWCSRoomParticipantEventJoined participantCallback:^(FPWCSApi2Room *room, FPWCSApi2RoomParticipant *participant){…}] |
Отслеживаем покидание участниками комнаты | connection.on(ROOM_EVENT.LEFT, function(participant){…}); | public void onLeft(final Participant participant){…} | [room on:kFPWCSRoomParticipantEventLeft participantCallback:^(FPWCSApi2Room *room, FPWCSApi2RoomParticipant *participant){…}] |
Отслеживаем публикацию видеопотока участником комнаты | connection.on(ROOM_EVENT.PUBLISHED, function(participant){…}); | public void onPublished(final Participant participant){…} | [room on:kFPWCSRoomParticipantEventPublished participantCallback:^(FPWCSApi2Room *room, FPWCSApi2RoomParticipant *participant){…}] |
Получаем входящее сообщение от других участников | connection.on(ROOM_EVENT.MESSAGE, function(participant){…}); | public void onMessage(final Message message){…} | [room onMessageCallback:^(FPWCSApi2Room *room, FPWCSApi2RoomMessage *message){…}] |
Отправляем сообщение одному из участников | participants[i].sendMessage(message); | participant.sendMessage(text); | [participant sendMessage:_messageBody.text]; |
Итого получаем по 10 основных конструкций на каждую из платформ. Каждую из перечисленных конструкций вы сможете найти в соответствующем файле с кодом. Все три приложения Web, iOS и Android были протестированы с последней сборкой Web Call Server 5 — сервера для видеочатов и low-latency трансляций с поддержкой технологии WebRTC.
Ссылки
Web Call Server — сервер для WebRTC видеочатов
EC2 launch — запуск готового образа на Amazon EC2
Install — установка сервера на VPS или Dedicated хост
Cocoapods — менеджер зависимостей для сборки
Web SDK | Android SDK | iOS SDK | |
SDK | html | html | html |
Скачать SDK | tar.gz | aar | tar.gz |
Пример приложения чата с комнатами | WCS demo | Google Play | Только Ad hoc |
Основной файл кода примера | Conference.js | ConferenceActivity.java | ViewController.m |
Исходный код всех примеров | github | github | github |
Описание процедуры сборки примеров из исходного кода | html | html | html |
Описание кода примера с комнатами Conference | html | html |
Статьи по теме
Разрабатываем видеочат между браузером и мобильным приложением
Особенности публикации видеочат — iOS приложения в App Store