Связь в реальном времени с WebRTC

1. Введение

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

WebRTC имеет несколько API на JavaScript — перейдите по ссылкам, чтобы посмотреть демонстрации.

  • getUserMedia() : захват аудио и видео.
  • MediaRecorder : запись аудио и видео.
  • RTCPeerConnection : потоковая передача аудио и видео между пользователями.
  • RTCDataChannel : потоковая передача данных между пользователями.

Где можно использовать WebRTC?

В браузерах Firefox, Opera и Chrome на настольных компьютерах и устройствах Android. WebRTC также доступен для нативных приложений на iOS и Android.

Что такое сигнализация?

WebRTC использует RTCPeerConnection для передачи потоковых данных между браузерами, но также нуждается в механизме для координации связи и отправки управляющих сообщений — процессе, известном как сигнализация. Методы и протоколы сигнализации WebRTC не определены. В этом практическом занятии вы будете использовать Socket.IO для обмена сообщениями, но существует множество альтернатив .

Что такое STUN и TURN?

WebRTC разработан для работы в режиме peer-to-peer, поэтому пользователи могут подключаться по максимально прямому маршруту. Однако WebRTC создан для работы в реальных сетевых условиях: клиентским приложениям необходимо преодолевать NAT-шлюзы и брандмауэры, а в одноранговых сетях требуются резервные варианты на случай сбоя прямого соединения. В рамках этого процесса API WebRTC используют STUN-серверы для получения IP-адреса вашего компьютера и TURN-серверы для работы в качестве ретрансляционных серверов в случае сбоя одноранговой связи. (Подробнее об этом можно прочитать в статье «WebRTC в реальном мире »).

Безопасен ли WebRTC?

Шифрование является обязательным для всех компонентов WebRTC, а его JavaScript API можно использовать только из защищенных источников (HTTPS или localhost). Механизмы сигнализации не определены стандартами WebRTC, поэтому вам необходимо убедиться в использовании безопасных протоколов.

2. Обзор

Создайте приложение для получения видео и создания снимков экрана с помощью веб-камеры и обмена ими между пользователями через WebRTC. В процессе вы научитесь использовать основные API WebRTC и настроите сервер обмена сообщениями с помощью Node.js.

Что вы узнаете

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

Что вам понадобится

  • Chrome 47 или выше
  • Используйте веб-сервер для Chrome или собственный веб-сервер по вашему выбору.
  • Пример кода
  • Текстовый редактор
  • Базовые знания HTML, CSS и JavaScript.

3. Получите пример кода.

Скачать код

Если вы знакомы с Git, вы можете загрузить код для этого практического занятия с GitHub, клонировав его:

git clone https://github.com/googlecodelabs/webrtc-web

Или же нажмите следующую кнопку, чтобы загрузить ZIP-файл с кодом:

Откройте загруженный zip-файл. Это распакует папку проекта ( adaptive-web-media ), которая содержит по одной папке для каждого шага этого практического занятия, а также все необходимые ресурсы.

Всю работу по программированию вы будете выполнять в директории с именем work .

В папках step-nn содержится готовая версия каждого шага этого практического занятия. Они предназначены для ознакомления.

Установите и проверьте веб-сервер.

Хотя вы можете использовать свой собственный веб-сервер, этот практический урок разработан для эффективной работы с веб-сервером Chrome. Если у вас еще не установлено это приложение, вы можете установить его из Chrome Web Store.

6ddeb4aee53c0f0e.png

После установки приложения «Веб-сервер для Chrome» щелкните ярлык «Приложения Chrome» на панели закладок, на странице новой вкладки или в панели запуска приложений:

1d2b4aa977ab7e24.png

Нажмите на значок веб-сервера:

27fce4494f641883.png

Далее вы увидите диалоговое окно, позволяющее настроить локальный веб-сервер:

Screen Shot 2016-02-18 at 11.48.14 AM.png

Нажмите кнопку «ВЫБРАТЬ ПАПКУ» и выберите только что созданную рабочую папку. Это позволит вам просматривать ход работы в Chrome по URL-адресу, выделенному в диалоговом окне «Веб-сервер» в разделе «URL(-а) веб-сервера» .

В разделе «Параметры» установите флажок рядом с пунктом «Автоматически отображать index.html» , как показано ниже:

Screen Shot 2016-02-18 at 11.56.30 AM.png

Затем остановите и перезапустите сервер, переместив переключатель с надписью «Веб-сервер: ЗАПУЩЕН» влево, а затем обратно вправо.

Screen Shot 2016-02-18 at 12.22.18 PM.png

Теперь откройте свой рабочий сайт в веб-браузере, щелкнув по выделенному URL-адресу веб-сервера. Вы должны увидеть страницу, которая выглядит примерно так, что соответствует work/index.html :

18a705cb6ccc5181.png

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

4. Транслируйте видео с веб-камеры.

Что вы узнаете

На этом этапе вы узнаете, как:

  • Получайте видеопоток с вашей веб-камеры.
  • Управление воспроизведением потока.
  • Используйте CSS и SVG для обработки видео.

Полная версия этого шага находится в папке step-01 .

Немного HTML...

Добавьте элемент video и элемент script в файл index.html в вашей рабочей директории:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

  <script src="js/main.js"></script>

</body>

</html>

...и немного JavaScript

Добавьте следующее в файл main.js в папке js :

'use strict';

// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
  video: true,
};

// Video element where stream will be placed.
const localVideo = document.querySelector('video');

// Local stream that will be reproduced on the video.
let localStream;

// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Попробуйте!

Откройте файл index.html в браузере, и вы увидите что-то подобное (разумеется, с изображением с вашей веб-камеры!):

9297048e43ed0f3d.png

Как это работает

После вызова метода getUserMedia() браузер запрашивает у пользователя разрешение на доступ к его камере (если это первый запрос доступа к камере для текущего источника). В случае успеха возвращается объект MediaStream , который может быть использован медиаэлементом через атрибут srcObject :

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);


}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Аргумент constraints позволяет указать, какие медиафайлы следует получить. В этом примере — только видео, поскольку звук по умолчанию отключен:

const mediaStreamConstraints = {
  video: true,
};

Для дополнительных требований, таких как разрешение видео, можно использовать ограничения:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

Спецификация MediaTrackConstraints перечисляет все потенциальные типы ограничений, хотя не все параметры поддерживаются всеми браузерами. Если запрошенное разрешение не поддерживается текущей выбранной камерой, вызов getUserMedia() будет отклонен с OverconstrainedError , и пользователю не будет предложено предоставить разрешение на доступ к своей камере.

Если getUserMedia() выполняется успешно, видеопоток с веб-камеры устанавливается в качестве источника видеоэлемента:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

Бонусные баллы

  • Объект localStream передаваемый в getUserMedia() находится в глобальной области видимости, поэтому вы можете просмотреть его содержимое в консоли браузера: откройте консоль, введите stream и нажмите Enter. (Чтобы просмотреть консоль в Chrome, нажмите Ctrl-Shift-J, или Command-Option-J, если вы используете Mac.)
  • Что возвращает localStream.getVideoTracks() ?
  • Попробуйте вызвать localStream.getVideoTracks()[0].stop() .
  • Посмотрите на объект constraints: что произойдет, если изменить его на {audio: true, video: true} ?
  • Какой размер у видеоэлемента? Как получить естественный размер видео из JavaScript, в отличие от размера экрана? Воспользуйтесь инструментами разработчика Chrome, чтобы проверить.
  • Попробуйте добавить CSS-фильтры к элементу видео. Например:
video {
  filter: blur(4px) invert(1) opacity(0.5);
}
  • Попробуйте добавить SVG-фильтры. Например:
video {
   filter: hue-rotate(180deg) saturate(200%);
 }

Что вы узнали

На этом этапе вы научились:

  • Получайте видео с веб-камеры.
  • Установите ограничения на использование медиафайлов.
  • Поэкспериментируйте с видеоэлементом.

Полная версия этого шага находится в папке step-01 .

Советы

  • Не забудьте добавить атрибут autoplay к video . Без него вы увидите только один кадр!
  • Существует множество дополнительных опций для ограничений функции getUserMedia() . Посмотрите демонстрацию по адресу webrtc.github.io/samples/src/content/peerconnection/constraints . Как вы увидите, на этом сайте много интересных примеров использования WebRTC.

Передовая практика

  • Убедитесь, что ваш видеоэлемент не выходит за пределы контейнера. Мы добавили width и max-width для установки предпочтительного и максимального размера видео. Высота будет рассчитана браузером автоматически:
video {
  max-width: 100%;
  width: 320px;
}

Далее

У вас есть видео, но как его транслировать? Узнайте на следующем шаге!

5. Трансляция видео с помощью RTCPeerConnection

Что вы узнаете

На этом этапе вы узнаете, как:

  • Скройте различия между браузерами с помощью WebRTC-плагина adapter.js .
  • Используйте API RTCPeerConnection для потоковой передачи видео.
  • Управление захватом и потоковой передачей мультимедиа.

Полная версия этого шага находится в папке шага 2 .

Что такое RTCPeerConnection?

RTCPeerConnection — это API для выполнения вызовов WebRTC для потоковой передачи видео и аудио, а также обмена данными.

В этом примере устанавливается соединение между двумя объектами RTCPeerConnection (известными как пиры) на одной странице.

Практическая польза невелика, но полезно для понимания принципа работы RTCPeerConnection.

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

В файле index.html замените единственный видеоэлемент двумя видеоэлементами и тремя кнопками:

<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>


<div>
  <button id="startButton">Start</button>
  <button id="callButton">Call</button>
  <button id="hangupButton">Hang Up</button>
</div>

Один видеоэлемент будет отображать поток, полученный из getUserMedia() , а другой — тот же видеопоток, передаваемый через RTCPeerconnection. (В реальном приложении один видеоэлемент будет отображать локальный поток, а другой — удалённый.)

Добавьте полифил adapter.js.

Добавьте ссылку на текущую версию файла adapter.js над ссылкой на файл main.js :

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

Теперь файл index.html должен выглядеть так:

<!DOCTYPE html>
<html>

<head>
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Realtime communication with WebRTC</h1>

  <video id="localVideo" autoplay playsinline></video>
  <video id="remoteVideo" autoplay playsinline></video>

  <div>
    <button id="startButton">Start</button>
    <button id="callButton">Call</button>
    <button id="hangupButton">Hang Up</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

Установите код RTCPeerConnection.

Замените файл main.js на версию из папки step-02 .

Сделайте звонок

Откройте index.html , нажмите кнопку «Start» , чтобы получить видео с вашей веб-камеры, и нажмите «Call» , чтобы установить соединение с другим пользователем. Вы должны увидеть одно и то же видео (с вашей веб-камеры) в обоих видеоэлементах. Для просмотра логов WebRTC перейдите в консоль браузера.

Как это работает

Этот шаг многое меняет...

WebRTC использует API RTCPeerConnection для установления соединения для потоковой передачи видео между клиентами WebRTC, известными как пиры .

В этом примере два объекта RTCPeerConnection находятся на одной странице: pc1 и pc2 . Практическое применение не очень полезно, но хорошо подходит для демонстрации работы API.

Для установления соединения между участниками WebRTC-сети необходимо выполнить три задачи:

  • Создайте объект RTCPeerConnection для каждого конца вызова и на каждом конце добавьте локальный поток из getUserMedia() .
  • Получайте и передавайте сетевую информацию: потенциальные точки подключения называются кандидатами ICE .
  • Получайте и передавайте локальные и удаленные описания: метаданные о локальных медиафайлах в формате SDP .

Представьте, что Алиса и Боб хотят использовать RTCPeerConnection для организации видеочата.

Первым делом Алиса и Боб обмениваются сетевой информацией. Выражение «поиск кандидатов» относится к процессу поиска сетевых интерфейсов и портов с использованием структуры ICE .

  1. Алиса создает объект RTCPeerConnection с обработчиком onicecandidate (addEventListener('icecandidate')) . Это соответствует следующему коду из файла main.js :
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);
  1. Алиса вызывает getUserMedia() и добавляет переданный ей поток:
navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');
  1. Обработчик onicecandidate из шага 1 вызывается, когда становятся доступны сетевые кандидаты.
  2. Алиса отправляет сериализованные данные-кандидаты Бобу. В реальном приложении этот процесс (известный как сигнализация ) происходит через службу обмена сообщениями — вы узнаете, как это сделать на следующем шаге. Конечно, на этом шаге два объекта RTCPeerConnection находятся на одной странице и могут взаимодействовать напрямую без необходимости использования внешних средств обмена сообщениями.
  3. Когда Боб получает от Алисы сообщение с кандидатом, он вызывает функцию addIceCandidate() , чтобы добавить кандидата в описание удаленного узла:
function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

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

  1. Алиса запускает метод RTCPeerConnection createOffer() . Возвращаемое промис-сообщение содержит RTCSessionDescription: описание локальной сессии Алисы:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);
  1. В случае успеха Алиса устанавливает локальное описание с помощью setLocalDescription() , а затем отправляет это описание сессии Бобу через их сигнальный канал.
  2. Боб устанавливает описание, отправленное ему Алисой, в качестве удаленного описания, используя setRemoteDescription() .
  3. Боб запускает метод createAnswer() класса RTCPeerConnection, передавая ему удаленное описание, полученное от Алисы, чтобы можно было сгенерировать локальную сессию, совместимую с ее сессией. В промисе createAnswer() передается объект RTCSessionDescription: Боб устанавливает его в качестве локального описания и отправляет Алисе.
  4. Когда Алиса получает описание сессии Боба, она устанавливает его в качестве удаленного описания с помощью setRemoteDescription() .
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}
  1. Пинг!

Бонусные баллы

  1. Посмотрите на chrome://webrtc-internals . Там вы найдете статистику WebRTC и отладочные данные. (Полный список URL-адресов Chrome находится по адресу chrome://about .)
  2. Оформите страницу с помощью CSS:
  • Разместите видео рядом.
  • Сделайте кнопки одинаковой ширины, но с более крупным шрифтом.
  • Убедитесь, что интерфейс корректно отображается на мобильных устройствах.
  1. В консоли инструментов разработчика Chrome найдите localStream , localPeerConnection и remotePeerConnection .
  2. В консоли посмотрите на файл localPeerConnectionpc1.localDescription . Как выглядит формат SDP?

Что вы узнали

На этом этапе вы научились:

  • Скройте различия между браузерами с помощью WebRTC-плагина adapter.js .
  • Используйте API RTCPeerConnection для потоковой передачи видео.
  • Управление захватом и потоковой передачей мультимедиа.
  • Обмен мультимедийной и сетевой информацией между участниками для осуществления вызова WebRTC.

Полная версия этого шага находится в папке шага 2 .

Советы

  • На этом этапе предстоит многому научиться! Чтобы найти другие ресурсы, которые более подробно объясняют RTCPeerConnection, загляните на webrtc.org . На этой странице вы найдете рекомендации по использованию JavaScript-фреймворков — если вы хотите использовать WebRTC, но не хотите разбираться с API.
  • Подробнее о вспомогательном модуле adapter.js можно узнать в репозитории adapter.js на GitHub .
  • Хотите увидеть, как выглядит лучшее в мире приложение для видеочата? Взгляните на AppRTC, каноническое приложение проекта WebRTC для вызовов WebRTC: приложение , код . Время установления соединения составляет менее 500 мс.

Передовая практика

  • Чтобы обеспечить совместимость вашего кода с будущими версиями, используйте новые API на основе промисов и обеспечьте совместимость с браузерами, которые их не поддерживают, с помощью adapter.js .

Далее

На этом этапе показано, как использовать WebRTC для потоковой передачи видео между участниками сети, но этот практический урок также посвящен данным!

На следующем шаге выясните, как передавать произвольные данные с помощью RTCDataChannel.

6. Используйте RTCDataChannel для обмена данными.

Что вы узнаете

  • Как обмениваться данными между конечными точками WebRTC (пирами).

Полная версия этого шага находится в папке step-03 .

Обновите свой HTML-код

На этом этапе вы будете использовать каналы передачи данных WebRTC для отправки текста между двумя textarea полями на одной странице. Это не очень полезно, но демонстрирует, как WebRTC можно использовать для обмена данными, а также для потоковой передачи видео.

Удалите элементы video и button из файла index.html и замените их следующим HTML-кодом:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

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

Теперь файл index.html должен выглядеть так:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
  <textarea id="dataChannelReceive" disabled></textarea>

  <div id="buttons">
    <button id="startButton">Start</button>
    <button id="sendButton">Send</button>
    <button id="closeButton">Stop</button>
  </div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Обновите JavaScript

Замените содержимое файла main.js содержимым файла step-03/js/main.js .

Попробуйте потоковую передачу данных между узлами: откройте index.html , нажмите «Start» , чтобы установить соединение с узлом, введите текст в textarea слева, затем нажмите «Send» , чтобы передать текст с использованием каналов передачи данных WebRTC.

Как это работает

Этот код использует RTCPeerConnection и RTCDataChannel для обмена текстовыми сообщениями.

Большая часть кода на этом шаге совпадает с кодом из примера RTCPeerConnection.

Большая часть нового кода содержится в функциях sendData() и createConnection() :

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

Синтаксис RTCDataChannel намеренно похож на WebSocket, с методом send() и событием message .

Обратите внимание на использование dataConstraint . Каналы данных можно настроить для обеспечения различных типов обмена данными — например, отдавая приоритет надежной доставке над производительностью. Более подробную информацию о параметрах можно найти в сети разработчиков Mozilla .

Бонусные баллы

  1. В протоколе SCTP , используемом каналами передачи данных WebRTC, надежная и упорядоченная доставка данных включена по умолчанию. Когда RTCDataChannel может потребоваться обеспечить надежную доставку данных, и когда производительность может быть важнее — даже если это означает потерю части данных?
  2. Используйте CSS для улучшения макета страницы и добавьте атрибут-заполнитель к текстовому полю "dataChannelReceive".
  3. Протестируйте страницу на мобильном устройстве.

Что вы узнали

На этом этапе вы научились:

  • Установить соединение между двумя участниками WebRTC-сети.
  • Обмен текстовыми данными между участниками.

Полная версия этого шага находится в папке step-03 .

Узнать больше

Далее

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

7. Настройте службу сигнализации для обмена сообщениями.

Что вы узнаете

На этом этапе вы узнаете, как:

  • Используйте npm для установки зависимостей проекта в соответствии с указаниями в файле package.json.
  • Запустите сервер Node.js и используйте node-static для обслуживания статических файлов.
  • Настройте службу обмена сообщениями на Node.js с использованием Socket.IO.
  • Используйте это для создания «комнат» и обмена сообщениями.

Полная версия этого шага находится в папке step-04 .

Концепции

Для установления и поддержания WebRTC-соединения клиентам (участникам) WebRTC необходимо обмениваться метаданными:

  • Информация о кандидате (сети).
  • Предлагайте и отвечайте на сообщения, содержащие информацию о медиафайлах, такую ​​как разрешение и кодеки.

Иными словами, для осуществления потоковой передачи аудио, видео или данных между двумя узлами необходим обмен метаданными. Этот процесс называется сигнализацией .

На предыдущих этапах объекты RTCPeerConnection отправителя и получателя находятся на одной странице, поэтому «сигнализация» сводится к простой передаче метаданных между объектами.

В реальных условиях соединения RTCPeerConnections отправителя и получателя работают на веб-страницах на разных устройствах, и вам нужен способ для обмена метаданными между ними.

Для этого используется сигнальный сервер : сервер, который может передавать сообщения между клиентами WebRTC (участниками). Сами сообщения представляют собой обычный текст: строковые объекты JavaScript.

Необходимое условие: установить Node.js.

Для выполнения следующих шагов этого практического задания (папки с шага 4 по шаг 6 ) вам потребуется запустить сервер на локальном компьютере с использованием Node.js.

Вы можете скачать и установить Node.js по этой ссылке или через предпочитаемый вами менеджер пакетов .

После установки вы сможете импортировать зависимости, необходимые для следующих шагов (выполнив npm install ), а также запустить небольшой локальный сервер для выполнения практического задания (выполнив node index.js ). Эти команды будут указаны позже, когда они понадобятся.

О приложении

WebRTC использует клиентский JavaScript API, но для реального использования также требует наличия сигнального (сервера обмена сообщениями), а также STUN- и TURN-серверов. Подробнее можно узнать здесь .

На этом этапе вы создадите простой сервер сигнализации на Node.js, используя модуль Socket.IO для Node.js и библиотеку JavaScript для обмена сообщениями. Опыт работы с Node.js и Socket.IO будет полезен, но не обязателен; компоненты обмена сообщениями очень просты.

В этом примере сервер (приложение Node.js) реализован в файле index.js , а клиент, работающий на нём (веб-приложение), — в файле index.html .

На этом этапе приложение Node.js выполняет две задачи.

Во-первых, он выступает в качестве ретранслятора сообщений:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

Во-вторых, он управляет видеочатами WebRTC:

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

Наше простое приложение WebRTC позволит одновременно находиться в одной комнате максимум двум участникам.

HTML и JavaScript

Обновите файл index.html так, чтобы он выглядел следующим образом:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

На этом этапе на странице ничего не отобразится: все данные записываются в консоль браузера. (Чтобы просмотреть консоль в Chrome, нажмите Ctrl-Shift-J, или Command-Option-J, если вы используете Mac.)

Замените файл js/main.js следующим содержимым:

'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

Настройте Socket.IO для работы на Node.js

В HTML-файле вы, возможно, заметили, что используете файл Socket.IO:

<script src="/socket.io/socket.io.js"></script>

В корневом каталоге вашей рабочей директории создайте файл с именем package.json со следующим содержимым:

{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

Это манифест приложения, который сообщает менеджеру пакетов Node ( npm ), какие зависимости проекта следует установить.

Для установки зависимостей (например, /socket.io/socket.io.js ) выполните следующую команду в командной строке терминала в вашей рабочей директории:

npm install

В журнале установки должно отображаться что-то подобное:

3ab06b7bcc7664b9.png

Как видите, npm установил зависимости, указанные в файле package.json .

Создайте новый файл index.js в корневой директории вашей рабочей директории (не в директории js ) и добавьте в него следующий код:

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});

В командной строке терминала выполните следующую команду в рабочем каталоге:

node index.js

Откройте в браузере localhost:8080 .

При каждом открытии этой ссылки вам будет предложено ввести название комнаты. Чтобы присоединиться к одной и той же комнате, каждый раз выбирайте одно и то же название, например, «foo».

Откройте новую вкладку и снова перейдите по адресу localhost:8080 . Выберите то же название комнаты.

Откройте localhost:8080 в третьей вкладке или окне. Снова выберите то же название комнаты.

Проверьте консоль на каждой из вкладок: вы должны увидеть сообщения из приведенного выше JavaScript-кода.

Бонусные баллы

  1. Какие альтернативные механизмы обмена сообщениями возможны? С какими проблемами можно столкнуться при использовании «чистого» WebSocket?
  2. Какие проблемы могут возникнуть при масштабировании этого приложения? Можете ли вы разработать метод для тестирования тысяч или миллионов одновременных запросов на использование комнат?
  3. Это приложение использует запрос JavaScript для получения названия комнаты. Придумайте способ получить название комнаты из URL-адреса. Например, localhost:8080/foo даст название комнаты foo .

Что вы узнали

На этом этапе вы научились:

  • Используйте npm для установки зависимостей проекта в соответствии с указаниями в файле package.json.
  • Запустите сервер Node.js для обслуживания статических файлов.
  • Настройте службу обмена сообщениями на Node.js, используя socket.io.
  • Используйте это для создания «комнат» и обмена сообщениями.

Полная версия этого шага находится в папке step-04 .

Узнать больше

Далее

Узнайте, как использовать сигналы для установления однорангового соединения между двумя пользователями.

8. Сочетайте взаимодействие между сверстниками и передачу сигналов.

Что вы узнаете

На этом этапе вы узнаете, как:

  • Запустите службу сигнализации WebRTC с использованием Socket.IO на Node.js.
  • Используйте этот сервис для обмена метаданными WebRTC между участниками.

Полная версия этого шага находится в папке step-05 .

Замените HTML и JavaScript

Замените содержимое файла index.html следующим:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <div id="videos">
    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

Замените содержимое файла js/main.js содержимым файла step-05/js/main.js .

Запустите сервер Node.js

Если вы выполняете это задание не из своей рабочей директории, вам может потребоваться установить зависимости для папки step-05 или вашей текущей рабочей директории. Выполните следующую команду из своей рабочей директории:

npm install

После установки, если ваш сервер Node.js не запущен, запустите его, выполнив следующую команду в рабочей директории:

node index.js

Убедитесь, что вы используете ту версию index.js из предыдущего шага, которая реализует Socket.IO. Для получения дополнительной информации о Node и Socket IO см. раздел «Настройка службы сигнализации для обмена сообщениями».

Откройте в браузере localhost:8080 .

Откройте localhost:8080 снова в новой вкладке или окне. Один видеоэлемент будет отображать локальный поток из getUserMedia() , а другой — «удалённое» видео, передаваемое через RTCPeerconnection.

Просмотреть журнал событий можно в консоли браузера.

Бонусные баллы

  1. Это приложение поддерживает только видеочат один на один. Как можно изменить дизайн, чтобы в одном видеочате могли участвовать несколько человек?
  2. В примере название комнаты foo задано жестко. Как лучше всего включить другие названия комнат?
  3. Как пользователи смогут делиться названием комнаты? Попробуйте разработать альтернативный способ обмена названиями комнат.
  4. Как можно изменить приложение?

Что вы узнали

На этом этапе вы научились:

  • Запустите службу сигнализации WebRTC с использованием Socket.IO на Node.js.
  • Используйте этот сервис для обмена метаданными WebRTC между участниками.

Полная версия этого шага находится в папке step-05 .

Советы

  • Статистику WebRTC и отладочные данные можно получить по адресу chrome://webrtc-internals .
  • С помощью test.webrtc.org можно проверить локальную среду и протестировать камеру и микрофон.
  • Если у вас возникли странные проблемы с кэшированием, попробуйте следующее:
  • Для принудительного обновления страницы удерживайте клавишу Ctrl и нажмите кнопку «Обновить» .
  • Перезапустите браузер
  • Выполните команду npm cache clean из командной строки.

Далее

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

9. Сделайте снимок и поделитесь им через канал передачи данных.

Что вы узнаете

На этом этапе вы узнаете, как:

  • Сделайте фотографию и получите из неё данные, используя элемент canvas.
  • Обменивайтесь данными изображений с удаленным пользователем.

Полная версия этого шага находится в папке step-06 .

Как это работает

Ранее вы узнали, как обмениваться текстовыми сообщениями с помощью RTCDataChannel.

Этот шаг позволяет обмениваться целыми файлами: в этом примере — фотографиями, полученными с помощью getUserMedia() .

Основные этапы этого шага следующие:

  1. Создайте канал передачи данных. Обратите внимание, что на этом этапе вы не добавляете никаких медиапотоков к соединению с партнером.
  2. Захват видеопотока с веб-камеры пользователя с помощью getUserMedia() :
var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}
  1. Когда пользователь нажимает кнопку «Снимок» , получите снимок (кадр видео) из видеопотока и отобразите его в элементе canvas :
var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}
  1. Когда пользователь нажимает кнопку «Отправить» , преобразуйте изображение в байты и отправьте их по каналу передачи данных:
function sendPhoto() {
  // Split data channel message in chunks of this byte length.
  var CHUNK_LEN = 64000;
  var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
    len = img.data.byteLength,
    n = len / CHUNK_LEN | 0;

  console.log('Sending a total of ' + len + ' byte(s)');
  dataChannel.send(len);

  // split the photo and send in chunks of about 64KB
  for (var i = 0; i < n; i++) {
    var start = i * CHUNK_LEN,
      end = (i + 1) * CHUNK_LEN;
    console.log(start + ' - ' + (end - 1));
    dataChannel.send(img.data.subarray(start, end));
  }

  // send the reminder, if any
  if (len % CHUNK_LEN) {
    console.log('last ' + len % CHUNK_LEN + ' byte(s)');
    dataChannel.send(img.data.subarray(n * CHUNK_LEN));
  }
}
  1. Принимающая сторона преобразует байты сообщения канала передачи данных обратно в изображение и отображает это изображение пользователю:
function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // trail is the element holding the incoming images
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

Получите код

Замените содержимое вашей рабочей папки содержимым шага 6. Теперь ваш файл index.html в рабочей папке должен выглядеть так:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

Если вы выполняете это задание не из своей рабочей директории, вам может потребоваться установить зависимости для папки step-06 или вашей текущей рабочей директории. Просто выполните следующую команду из своей рабочей директории:

npm install

После установки, если ваш сервер Node.js не запущен, запустите его, выполнив следующую команду из вашей рабочей директории:

node index.js

Убедитесь, что вы используете версию index.js , поддерживающую Socket.IO, и не забудьте перезапустить сервер Node.js после внесения изменений. Для получения дополнительной информации о Node и Socket.IO см. раздел «Настройка службы сигнализации для обмена сообщениями».

При необходимости нажмите кнопку «Разрешить» , чтобы разрешить приложению использовать вашу веб-камеру.

Приложение создаст случайный идентификатор комнаты и добавит его к URL-адресу. Откройте URL-адрес из адресной строки в новой вкладке или окне браузера.

Нажмите кнопку «Сфотографировать и отправить», а затем посмотрите раздел «Входящие» на другой вкладке внизу страницы. Приложение переносит фотографии между вкладками.

Вы должны увидеть что-то подобное:

911b40f36ba6ba8.png

Бонусные баллы

  1. Как изменить код, чтобы можно было обмениваться файлами любого типа?

Узнать больше

Что вы узнали

  • Как сделать фотографию и получить с неё данные, используя элемент canvas.
  • Как обменяться этими данными с удалённым пользователем?

Полная версия этого шага находится в папке step-06 .

10. Поздравляем!

Вы разработали приложение для потоковой передачи видео в реальном времени и обмена данными!

Что вы узнали

В этом практическом занятии вы научились:

  • Получайте видео с веб-камеры.
  • Транслируйте видео с помощью RTCPeerConnection.
  • Передача данных осуществляется с помощью RTCDataChannel.
  • Настройте службу сигнализации для обмена сообщениями.
  • Объедините взаимодействие между сверстниками и передачу сигналов.
  • Сделайте снимок и поделитесь им через канал передачи данных.

Следующие шаги

Узнать больше

  • На сайте webrtc.org доступен широкий спектр ресурсов для начала работы с WebRTC.