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

1. Введение

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

WebRTC имеет несколько API-интерфейсов JavaScript — щелкните ссылку, чтобы просмотреть демо-версии.

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

Где я могу использовать WebRTC?

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

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

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

Что такое СТАН и ПОВОРОТ?

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

Является ли WebRTC безопасным?

Шифрование является обязательным для всех компонентов WebRTC, а его API-интерфейсы JavaScript можно использовать только из безопасных источников (HTTPS или локальный хост). Механизмы сигнализации не определены стандартами 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.

6ddeb4aee53c0f0e.png

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

1d2b4aa977ab7e24.png

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

27fce4494f641883.png

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

Снимок экрана 18 февраля 2016 г., 11.48.14.png

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

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

Снимок экрана 18 февраля 2016 г., 11.56.30.png

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

Снимок экрана 18 февраля 2016 г., 22.22.18.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() находится в глобальной области видимости, поэтому вы можете проверить его из консоли браузера: откройте консоль, введите поток и нажмите Return. (Чтобы просмотреть консоль в Chrome, нажмите Ctrl-Shift-J или Command-Option-J, если вы используете Mac.)
  • Что возвращает localStream.getVideoTracks() ?
  • Попробуйте вызвать localStream.getVideoTracks()[0].stop() .
  • Посмотрите на объект ограничений: что произойдет, если вы измените его на {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, адаптера.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. (В реальном приложении один видеоэлемент будет отображать локальный поток, а другой — удаленный поток.)

Добавьте прокладку адаптера.js

Добавьте ссылку на текущую версию адаптера.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 версией из папки шаг-02 .

Позвонить

Откройте index.html , нажмите кнопку « Пуск» , чтобы получить видео с веб-камеры, и нажмите «Вызов» , чтобы установить одноранговое соединение. Вы должны увидеть одно и то же видео (с вашей веб-камеры) в обоих видеоэлементах. Откройте консоль браузера, чтобы увидеть журналирование 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. Алиса запускает метод createOffer() RTCPeerConnection. Возвращенное обещание предоставляет 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 Dev Tools посмотрите localStream , localPeerConnection и remotePeerConnection .
  2. В консоли посмотрите localPeerConnectionpc1.localDescription . Как выглядит формат SDP?

Что вы узнали

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

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

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

Советы

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

Лучшая практика

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

Дальше

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

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

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

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

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

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

Обновите свой HTML

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

Удалите элементы видео и кнопок из 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 , нажмите « Пуск», чтобы настроить одноранговое соединение, введите текст в textarea слева, затем нажмите « Отправить» , чтобы передать текст с использованием каналов данных 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 Developer Network .

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

  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.

Чтобы выполнить следующие шаги этой лаборатории кода (папки с шага от 04 до шага 06 ), вам потребуется запустить сервер на локальном хосте с использованием Node.js.

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

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

О приложении

WebRTC использует клиентский API JavaScript, но для реального использования также требуется сервер сигнализации (обмена сообщениями), а также серверы 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 Package Manager ( 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 .

Каждый раз, когда вы открываете этот URL-адрес, вам будет предложено ввести название комнаты. Чтобы присоединиться к одной и той же комнате, каждый раз выбирайте одно и то же имя комнаты, например «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

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

npm install

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

node index.js

Убедитесь, что вы используете версию index.js из предыдущего шага, которая реализует 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. Сделайте фотографию и поделитесь ею через канал передачи данных.

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

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

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

Полная версия этого шага находится в папке 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);
}

Получить код

Замените содержимое вашей рабочей папки содержимым шага 06 . Ваш рабочий файл 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>

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

npm install

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

node index.js

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

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

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

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

Вы должны увидеть что-то вроде этого:

911b40f36ba6ba8.png

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

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

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

Что вы узнали

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

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

10. Поздравления

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

Что вы узнали

В этой лаборатории вы узнали, как:

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

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

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

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