Во всех наших приложениях сотрудники могут общаться при помощи встроенного чата. При этом все чаще можно увидеть приложения, которые позволяют общаться пользователям по видеосвязи без перехода в сторонние сервисы.

В этой статье я расскажу про удобный способ создания такого функционала. Расскажу про готовые инструменты, опишу основные моменты, мысли, ссылки. Будет интересно!

Что такое WebRTC

Web Real Time Communications - это технология, которая позволяет осуществлять передачу аудио и видео потоков. На базе нее строятся приложения для видеконференций, например, Google meet, также ее применяют в приложениях для совместой работы (miro) и социальных сетях (vk). Подробно описывать WebRTC не будем, на эту тему полно статей и документации, сосредоточимся на технологии Angular.

Openvidu

Это платформа для создания видеозвонков основанная на webrtc. Она включает в себя бекенд реализацию и клиентские библиотеки. Мы сосредоточимся на клиенте так как статья посвящена Angular.

Приведем возможности клиентской бибилиотеки Openvidu:

  • Осуществлять аудио/видео звонки

  • Демонстрировать экран

  • Поддержка чата

  • Возможность переключения устройств ввода/вывода

  • Определение речи

  • Кастомизация клиентских библиотек

Многие возможности мы рассмотрим на примерах ниже.

Основные объекты из API Openvidu для построения приложения:

  • OpenVidu - класс отвечающий за основную точку входа для работы с webRtc

  • Session - представляет из себя реализацию звонков. По сути комната, в рамках которой участники конференции могут взаимодействовать

  • Publisher - описывает участника сессии, который стримит аудио/видео поток

  • Subscriber - описывает участника, который может получать аудио/видео поток от Publisher в рамках сессии

Подготовка окружения

Для демо приложения воспользуемся готовым docker контейнером, который содержит в себе сервер openvidu. Документацию для установки можно найти по ссылке.

Предполагается, что у вас установлен docker

Для запуска контейнера необходимо выполнить следующую команду.

docker run -p 4443:4443 --rm -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-server-kms:2.20.0

Также для разработки и тестирования могут потребоваться виртуальные камеры. Можно рассмотреть следущие:

  • SplitCam

  • ManyCam

С помощью них можно подставить любое видео в качестве видеопотока для приложения. А также API браузера сможет определять виртуальные устройства в качестве источника.

Эти два простых шага достаточны для создания клиентского демо приложения. Приступим!

Создание приложения

Для примера создадим приложение для совещаний, в котором будут находиться N пользователей. Каждый из них будет иметь возможность совершать базовые действия в приложении:

  • Подключаться к конференции

  • Включать/выключать видео/аудио

  • Демонстрировать экран

  • Идентифицировать говорящего

Подключение участников к видеоконференции

Создадим video-call.service.ts, который будет отвечать за работу с OpenVidu и упралять логикой видеозвонка. Для начала реализуем подключение к серверу и создание сессии join:

join() {
    this.session = this.OV.initSession();
    this.subscribeSessionEvents();

    from(this.getToken())
    .pipe(
      switchMap((token: string) => {
        return from(this.session.connect(token, { clientData: this.userName }))
      }),
      tap(() => {
        this.initPublisher();
      })
    ).subscribe();
  }

В этом методе происходит следующее:

  • создание объекта сессии

  • подписка на события сессии

  • получение токена с сервера

  • подключение к сессии видеозвонка

  • создание паблишера

Рассмотрим подробнее создание паблишера:

initPublisher() {
    const publisher: Publisher = this.OV.initPublisher('', {
      audioSource: undefined,
      videoSource: this.deviceId,
      publishAudio: true,
      publishVideo: true,
      resolution: '640x480',
      frameRate: 30,
      insertMode: 'APPEND',
      mirror: false
    });

    this.session.publish(publisher);
    this.currentUser$.next(publisher);
  }

initPublisher создает инстанс паблишера в который передаются настройки:

  • audioSource/videoSource - источники звука и видео. Это могут быть все возможные физические и виртуальные камеры и микрофоны

  • publishAudio/publishVideo - состояние публикации звука или видео

  • а также различные настройки качества и разрешения видео

Для трансляции видео нужно сделать компонент в котором будет связываться элемент video и видеопоток.

Шаблон компонента, который содержит тег video

<div class="ov-video">
    <video #videoElement></video>
</div>

Код компонента. В нем происходит получение ссылки на video элемент и при помощи API openvidu и метода addVideoElement видеопоток привязвается к html.

 @ViewChild('videoElement') elementRef: ElementRef | undefined;

  _streamManager: StreamManager;
  
  ngAfterViewInit() {
    this._streamManager.addVideoElement(this.elementRef.nativeElement);
  }

Результат работы программы

Подключение участников к видеозвонку Angular/Openvidu
Подключение участников к видеозвонку Angular/Openvidu

Демонстрация экрана участником

Демонстрация очень похожа на инициализацию камеры за исключением того что в качестве видеоисточника указывается 'screen'

 initScreenPublisher() {
    const publisher: Publisher = this.OV.initPublisher('', {
      audioSource: undefined,
      videoSource: 'screen',
      publishAudio: true,
      publishVideo: true,
      resolution: '640x480',
      frameRate: 30,
      insertMode: 'APPEND',
      mirror: false
    });

    this.session.publish(publisher);
    this.currentUser$.next(publisher);
  }

Результат работы

Демонстрация экрана Angular/openvidu
Демонстрация экрана Angular/openvidu

Выключение/включение камеры/звука участниками

Для того чтобы отключить или включить видео/звук нужно воспользоваться следующими методами у паблишера

	enableVideo() {
    this.currentUser$.value.publishVideo(true);
    this.videoEnabled = true;
  }

  disableVideo() {
    this.currentUser$.value.publishVideo(false);
    this.videoEnabled = false;
  }

Подсветка говорящего участника с помощью API определения речи

В начале создания приложения мы вызывали subscribeSessionEvents, настало время его рассмотреть подробнее:

subscribeSessionEvents() {
    this.session.on('streamCreated', (event: any) => {
      const subscriber: Subscriber = this.session.subscribe(event.stream, '');
      this.users$.value.push(subscriber);
      this.users$.next([...this.users$.value]);
    });

    this.session.on('streamDestroyed', (event: any) => {
     // Удалить пользователя с конференции
    });

    this.session.on('publisherStartSpeaking', (event: any) => {
      // пробросить событие и подстветить юзера с event.connection.connectionId
    });

    this.session.on('publisherStopSpeaking', (event: any) => {
      // пробросить событие и подстветить 
    });
  }

Сессия сама предоставляет нам информацию об участнике, который начинает и заканчивает говорить

  • publisherStartSpeaking - участник начинает говорить в объекте event содержится идентификатор участника, имея эту информацию можно выдеделить компонент этого участника

  • publisherStopSpeaking - по этому событию можно снять выделение участника

Переключение камеры

Есть два сценария выбора камеры

  • при инициализации паблишера

  • при переключении во время публикации

Для первого варианта необходимо получать информацию о текущих девайсах устройства, для этого можно воспользоваться как стандартными браузерными API так и оберткой из OpenVidu

getDevices() {
    from(this.OV.getDevices())
    .pipe(
      tap(x => {
        const videoDevices = x.filter(x => x.kind === 'videoinput');
       	.....
      })
    ).subscribe();
  }

Устройства можно вывести в выпадающий список в интерфейсе и при выборе его устанавливать значение deviceId в паблишер (методы выше initPublisher)

Для того, чтобы переключать камеры в режиме реального времени, нужно получить mediaStream из выбранного устройства

 selectCamera(value) {
    this.videoCallService.getVideoMediaTrack({videoSource: value}).pipe(
      untilDestroyed(this),
      tap(mediaTrack => {
          if (mediaTrack) {
            this.videoCallService.videoMediaTrack$.next(mediaTrack);
          }
        }
      )).subscribe();
  }

Далее воспользуемся API:

replaceTrack(track: MediaStreamTrack) {
   this.publisher.replaceTrack(track).then(() => ...
        .catch(error => console.error('Error replacing track'));
  }

На этом этапе мы научились делать весь фукнкционал который обозначили в начале статьи

Рекомендации, решения, мысли, ссылки

  • В Chrome существует особая политика с автовоспроизведением видео. Ее стоит учитывать, так как существуют сценарии когда видеопоток не будет стартовать, например пока пользователь не сделает какое-либо действие на странице.

  • Помимо трансляции видео с камеры( виртуальной камеры) или рабочего стола, можно транслировать обычное видео с помощью API браузера - captureStream

  • Документация MediaStream - в ней можно узнать детали потоков медиаданных

  • Для включения видеоэкрана "во весь экран" можно воспользоваться API requestFullscreen

  • В одной из статей я писал про технологию Web Speech API. Интересно можно ли ее применить для WebRTC, например, для преобразования аудиопотока в текст прямо в браузере. Был ли у вас такой опыт?

  • Ссылка на пример из статьи

  • У ZOOM есть свой sdk для построения подобных приложений, интересны его возможности

Заключение

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

Комментарии (2)


  1. robert_ayrapetyan
    02.02.2022 18:23

    А как в используемых фреймворках обстоят дела с реализацией ICE? Самое проблемное в WebRTC именно правильно настроить релей для пользователей за NAT-ом. По статистике таких 30%, а в мобильном трафике близко к 100%.


    1. alexbraun Автор
      02.02.2022 20:42

      Есть поддержка такой реализации
      Описано а статье https://openvidu.medium.com/openvidu-2-2-0-turn-made-easy-9d7e145f8905
      и в документации https://docs.openvidu.io/en/2.11.0/api/openvidu-browser/interfaces/openviduadvancedconfiguration.html