О практической работе
1. Прежде чем вы начнете
Эта лаборатория кода научит вас, как создать приложение для получения видео и снимков с помощью веб-камеры, а также обмена ими в одноранговой сети с помощью WebRTC. Вы также узнаете, как использовать основные API-интерфейсы WebRTC и настроить сервер обмена сообщениями с помощью Node.js.
Предпосылки
- Базовые знания HTML, CSS и JavaScript
Что вы будете строить
- Получите видео с веб-камеры.
- Потоковое видео с
RTCPeerConnection
. - Потоковая передача данных с помощью
RTCDataChannel
. - Настройте службу сигнализации для обмена сообщениями.
- Объедините одноранговое соединение и сигнализацию.
- Сделайте снимок и используйте канал данных, чтобы поделиться им.
Что вам понадобится
- Chrome 47 или выше
- Веб-сервер для Chrome или веб-сервер по вашему выбору
- Текстовый редактор на ваш выбор
- Node.js
2. Получить пример кода
Скачать код
- Если вы знакомы с Git, запустите эту команду, чтобы клонировать код для этой кодовой лаборатории с GitHub:
git clone https://github.com/googlecodelabs/webrtc-web
Или щелкните эту ссылку, чтобы загрузить zip-файл с кодом:
- Откройте загруженный zip-файл, чтобы распаковать папку проекта с именем
webrtc-web-master
, которая содержит одну папку для каждого шага этой лаборатории кода и все необходимые ресурсы.
Вы выполняете всю свою работу с кодом в каталоге с именем work
.
Папки step-nn
содержат готовую версию для каждого шага этой лаборатории кода. Они там для справки.
Установить и проверить веб-сервер
Хотя вы можете использовать свой собственный веб-сервер, эта лаборатория кода хорошо работает с веб-сервером для Chrome.
- Если у вас нет веб-сервера для Chrome, щелкните эту ссылку, чтобы установить его из Интернет-магазина Chrome:
- Нажмите Добавить в Chrome , чтобы установить веб-сервер для Chrome и автоматически открыть приложения Google в новой вкладке.
- Нажмите Веб-сервер :
Появится диалоговое окно, позволяющее настроить локальный веб-сервер:
- Щелкните Выбрать папку.
- Выберите
work
папку, которую вы создали.
В разделе URL- адреса веб-сервера вы видите URL-адрес, по которому вы можете просмотреть свою текущую работу в
Хром.
- В разделе « Параметры» (может потребоваться перезагрузка) установите флажок « Автоматически показывать index.html» .
- Переключение веб-сервера: запускался дважды, чтобы остановить и перезапустить сервер.
- Щелкните URL-адрес в разделе URL-адреса веб-сервера , чтобы увидеть свою работу в веб-браузере.
Вы должны увидеть страницу, которая выглядит так, что соответствует work/index.html
:
Очевидно, что это приложение пока не делает ничего интересного. Это всего лишь минимальный скелет для обеспечения правильной работы вашего веб-сервера. Вы добавляете функциональные возможности и особенности макета на последующих шагах.
3. Потоковое видео с веб-камеры
Полная версия этого шага находится в папке step-01
.
Добавьте штрих HTML
Скопируйте этот код и вставьте его в файл index.html
в вашем work
каталоге, чтобы добавить video
и элемент script
:
<!DOCTYPE html>
<html>
<head>
<title>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Real-time communication with WebRTC</h1>
<video autoplay playsinline></video>
<script src="js/main.js"></script>
</body>
</html>
Добавьте щепотку JavaScript
Скопируйте этот код и вставьте его в файл main.js
в вашей папке js
:
'use strict';
// In this codelab, you only stream video, not audio (video: true).
const mediaStreamConstraints = {
video: true,
};
// The video element where the stream is displayed
const localVideo = document.querySelector('video');
// The local stream that's displayed on the video
let localStream;
// Handle success and add the MediaStream to the video element
function gotLocalMediaStream(mediaStream) {
localStream = mediaStream;
localVideo.srcObject = mediaStream;
}
// Handle error and log a message to the console with the error message
function handleLocalMediaStreamError(error) {
console.log('navigator.getUserMedia error: ', error);
}
// Initialize media stream
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
Попытайся
Откройте файл index.html
в своем браузере, и вы должны увидеть что-то вроде этого, но, конечно, с видом с вашей веб-камеры:
Как это работает
После getUserMedia()
браузер запрашивает разрешение на доступ к вашей камере, если это первый запрос на доступ к камере для текущего источника.
В случае успеха возвращается MediaStream
, который media
-элемент может использовать через атрибут 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
(Return
на Mac). Чтобы просмотреть консоль в Chrome, нажмитеControl+Shift+J
(илиCommand+Option+J
на Mac). - Что
localStream.getVideoTracks()
? - Вызовите
localStream.getVideoTracks()[0].stop()
. - Посмотрите на объект ограничений. Что произойдет, если вы измените его на
{audio: true, video: true}
? - Какого размера элемент видео? Как вы можете получить естественный размер видео из JavaScript, а не размер дисплея? Используйте инструменты разработчика Google Chrome для проверки.
- Добавьте фильтры CSS к элементу видео, например:
video {
filter: blur(4px) invert(1) opacity(0.5);
}
- Добавьте фильтры SVG, например:
video {
filter: hue-rotate(180deg) saturate(200%);
}
Советы
- Не забудьте
autoplay
для элементаvideo
. Без этого вы видите только один кадр! - Есть много других опций для ограничений
getUserMedia()
. Дополнительные примеры см. в разделах Ограничения и статистика и Дополнительные ограничения и статистика в примерах WebRTC .
Лучшая практика
Убедитесь, что ваш элемент видео не переполняет контейнер. Эта лаборатория кода добавила width
и max-width
, чтобы установить предпочтительный размер и максимальный размер видео. Ваш браузер вычисляет высоту автоматически.
video {
max-width: 100%;
width: 320px;
}
4. Потоковое видео с API RTCPeerConnection
Полная версия этого шага находится в папке step-2
.
Добавьте видеоэлементы и кнопки управления
В файле index.html
замените один элемент video
двумя элементами video
и тремя элементами button
:
<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
. (В реальном приложении один video
будет отображать локальный поток, а другой — удаленный.)
Добавьте прокладку адаптера.js
Скопируйте этот элемент сценария и вставьте его над элементом сценария для main.js
:
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Теперь ваш файл index.html
должен выглядеть так:
<!DOCTYPE html>
<html>
<head>
<title>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Real-time 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
. - Нажмите «Пуск» , чтобы получить видео с веб-камеры.
- Нажмите « Позвонить» , чтобы установить одноранговое соединение.
Вы должны увидеть одно и то же видео с веб-камеры в обоих video
.
- Откройте консоль браузера, чтобы просмотреть ведение журнала WebRTC.
Как это работает
Этот шаг делает многое.
WebRTC использует API RTCPeerConnection
для настройки соединения для потоковой передачи видео между клиентами WebRTC, известными как одноранговые узлы.
В этом примере два объекта RTCPeerConnection
находятся на одной странице: pc1
и pc2
.
Настройка вызова между одноранговыми узлами WebRTC включает в себя три задачи:
- Создайте
RTCPeerConnection
для каждого конца вызова и на каждом конце добавьте локальный поток изgetUserMedia()
. - Получайте и делитесь информацией о сети.
Потенциальные конечные точки подключения известны как кандидаты ICE .
- Получайте и делитесь локальными и удаленными описаниями.
Метаданные о локальном носителе представлены в формате протокола описания сеанса (SDP).
Представьте, что Алиса и Боб хотят использовать RTCPeerConnection
для настройки видеочата.
Сначала Алиса и Боб обмениваются сетевой информацией. Выражение « поиск кандидатов » относится к процессу поиска сетевых интерфейсов и портов с использованием фреймворка ICE .
- Алиса создает объект
RTCPeerConnection
сonicecandidate (addEventListener('icecandidate'))
.
Это соответствует следующему коду из main.js
:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
- Алиса вызывает
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.');
- Обработчик
onicecandidate
из первого шага вызывается, когда становятся доступными сетевые кандидаты. - Алиса отправляет сериализованные данные кандидата Бобу.
В реальном приложении этот процесс, известный как сигнализация, происходит через службу обмена сообщениями. Вы узнаете, как это сделать, на более позднем этапе. Конечно, на этом шаге два объекта RTCPeerConnection
находятся на одной странице и могут взаимодействовать напрямую без необходимости обмена внешними сообщениями.
- Когда Боб получает сообщение кандидата от Алисы, он вызывает
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 .
- Алиса запускает
RTCPeerConnection
createOffer()
.
Возвращаемое обещание предоставляет RTCSessionDescription
— описание локального сеанса Алисы:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- В случае успеха Алиса устанавливает локальное описание с помощью
setLocalDescription()
а затем отправляет это описание сеанса Бобу через их сигнальный канал. - Боб устанавливает описание, отправленное ему Алисой, как удаленное описание с помощью
setRemoteDescription()
. - Боб запускает метод
RTCPeerConnection
createAnswer()
и передает ему удаленное описание, полученное от Алисы, чтобы сгенерировать локальный сеанс, совместимый с ее сеансом. -
createAnswer()
передаетRTCSessionDescription
, которое Боб устанавливает как локальное описание и отправляет Алисе. - Когда Алиса получает описание сеанса Боба, она устанавливает его как удаленное описание с помощью
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);
}
Набрать бонусные баллы
- Перейдите к chrome://webrtc-internals.
На этой странице представлена статистика WebRTC и данные отладки. (Вы можете найти полный список URL-адресов Chrome по адресу chrome://about.)
- Стилизовать страницу с помощью CSS:
- Поместите видео рядом.
- Сделайте кнопки одинаковой ширины с более крупным текстом.
- Убедитесь, что макет работает на мобильных устройствах.
- В консоли инструментов разработчика Chrome просмотрите
localStream
,localPeerConnection
иremotePeerConnection
. - В консоли посмотрите на
localPeerConnectionpc1.localDescription
.
Как выглядит формат SDP?
Советы
- Дополнительные сведения о прокладке adapter.js см. в GitHub-репозитории adapter.js .
- Взгляните на AppRTC и его код , каноническое приложение проекта WebRTC для вызовов WebRTC. Время установления вызова составляет менее 500 мс.
Лучшая практика
Чтобы защитить свой код в будущем, используйте новые API-интерфейсы на основе Promise и включите совместимость с браузерами, которые их не поддерживают, с помощью adapter.js .
5. Используйте канал данных для обмена данными
Полная версия этого шага находится в папке 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>
Одно textarea
предназначено для ввода текста, а другое — для отображения текста в потоковом режиме между одноранговыми узлами.
Теперь ваш файл index.html
должен выглядеть так:
<!DOCTYPE html>
<html>
<head>
<title>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Real-time 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
область слева. - Нажмите « Отправить» , чтобы передать текст по каналу данных 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
. Каналы данных могут быть настроены для включения различных типов обмена данными, таких как приоритет надежной доставки над производительностью.
Набрать бонусные баллы
- С SCTP , протоколом, используемым каналами данных WebRTC, надежная и упорядоченная доставка данных включена по умолчанию. Когда может
RTCDataChannel
для обеспечения надежной доставки данных, а когда производительность может быть важнее, даже если это означает потерю некоторых данных? - Используйте CSS для улучшения макета страницы и добавьте атрибут-заполнитель в текстовую
textarea
dataChannelReceive
. - Протестируйте страницу на мобильном устройстве.
Узнать больше
6. Настройка службы сигнализации для обмена сообщениями
Вы научились обмениваться данными между пирами на одной странице, но как вы это делаете между разными машинами? Во-первых, вам нужно настроить сигнальный канал для обмена сообщениями метаданных.
Полная версия этого шага находится в папке step-04
.
О приложении
WebRTC использует API JavaScript на стороне клиента, но для реального использования также требуется сервер сигнализации (обмена сообщениями), а также серверы STUN и TURN. Вы можете узнать больше здесь .
На этом шаге вы создадите простой сигнальный сервер Node.js, используя модуль Socket.IO Node.js и библиотеку JavaScript для обмена сообщениями.
В этом примере сервер (приложение 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>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Real-time communication with WebRTC</h1>
<script src="/socket.io/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>
На этом шаге вы ничего не увидите на странице. Все журналирование выполняется в консоли браузера. Чтобы просмотреть консоль в Chrome, нажмите Control+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>
- На верхнем уровне вашего
work
каталога создайте файл с именем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
, запустите следующую команду из терминала командной строки вwork
каталоге:
npm install
Вы должны увидеть журнал установки, который заканчивается примерно так:
Как видите, npm
установил зависимости, определенные в package.json
.
- Создайте новый файл
index.js
на верхнем уровне вашегоwork
каталога (не каталога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);
}
});
}
});
});
- В терминале командной строки выполните следующую команду в
work
каталоге:
node index.js
- В браузере перейдите по адресу http://localhost:8080 .
Каждый раз, когда вы переходите по этому URL-адресу, вам предлагается ввести имя комнаты.
Чтобы присоединиться к одной и той же комнате, каждый раз вводите одно и то же имя комнаты, например, foo
.
- Откройте новую вкладку, снова перейдите по адресу http://localhost:8080 и снова введите то же имя комнаты.
- Откройте еще одну новую вкладку, снова перейдите по адресу http://localhost:8080 и снова введите то же имя комнаты.
- Проверьте консоль на каждой из вкладок.
Вы должны увидеть журнал из JavaScript.
Набрать бонусные баллы
- Какие альтернативные механизмы обмена сообщениями могут быть возможны? С какими проблемами вы можете столкнуться при использовании чистого WebSocket?
- Какие проблемы могут возникнуть при масштабировании этого приложения? Можете ли вы разработать метод тестирования тысяч или миллионов одновременных запросов на комнату?
- Это приложение использует подсказку JavaScript для получения имени комнаты. Выясните, как получить имя комнаты из URL-адреса. Например, http://localhost:8080/foo даст имя комнаты
foo
.
Узнать больше
7. Комбинируйте одноранговое соединение и сигнализацию
Полная версия этого шага находится в папке step-05
.
Замените HTML и JavaScript
- Замените содержимое
index.html
следующим:
<!DOCTYPE html>
<html>
<head>
<title>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Real-time 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
Если вы не следуете этой лаборатории кода из своего work
каталога, вам может потребоваться установить зависимости для папки step-05
или вашей текущей рабочей папки.
- Запустите следующую команду из вашего рабочего каталога:
npm install
- После установки, если ваш сервер Node.js не запущен, запустите его, выполнив следующую команду в
work
каталоге:
node index.js
Убедитесь, что вы используете версию index.js
из предыдущего шага, которая реализует Socket.IO. Дополнительные сведения об узлах и сокетах ввода-вывода см. в разделе Настройка службы сигнализации для обмена сообщениями.
- В браузере перейдите по адресу http://localhost:8080.
- Откройте новую вкладку и снова перейдите по адресу http://localhost:8080.
Один элемент video
отображает локальный поток от getUserMedia()
, а другой показывает удаленное видео, переданное через RTCPeerconnection
.
- Просмотр журнала в консоли браузера.
Набирайте бонусные баллы
- Это приложение поддерживает только видеочат один на один. Как бы вы изменили дизайн, чтобы несколько человек могли пользоваться одним и тем же видеочатом?
- В примере имя комнаты
foo
жестко запрограммировано. Как лучше всего включить другие имена комнат? - Как пользователи будут делиться названием комнаты? Попробуйте создать альтернативу совместному использованию имен комнат.
- Как вы могли бы изменить приложение?
Советы
- Найдите статистику WebRTC и данные отладки на chrome://webrtc-internals.
- Используйте средство устранения неполадок WebRTC , чтобы проверить локальную среду и протестировать камеру и микрофон.
- Если у вас возникли странные проблемы с кэшированием, попробуйте следующее:
- Нажмите
Control
и щелкните Перезагрузить эту страницу . - Перезапустите браузер.
- Запустите
npm cache clean
из командной строки.
8. Сделайте снимок и поделитесь им через канал данных
Полная версия этого шага находится в папке step-06
.
Как это работает
Ранее вы узнали, как обмениваться текстовыми сообщениями с помощью RTCDataChannel
. Этот шаг позволяет обмениваться целыми файлами. В этом примере фотографии захвачены с помощью getUserMedia()
.
Основные части этого этапа следующие:
- Установить канал данных.
На этом шаге вы не добавляете никаких медиапотоков к одноранговому соединению.
- Захватите видеопоток с веб-камеры с помощью
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);
});
}
- Нажмите Snap , чтобы получить снимок (видеокадр) из видеопотока и отобразить его в элементе
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);
}
- Нажмите « Отправить », чтобы преобразовать изображение в байты и отправить их по каналу данных:
function sendPhoto() {
// Split the 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 approximately 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 applicable.
if (len % CHUNK_LEN) {
console.log('last ' + len % CHUNK_LEN + ' byte(s)');
dataChannel.send(img.data.subarray(n * CHUNK_LEN));
}
}
Принимающая сторона преобразует байты сообщения канала данных в изображение и отображает изображение пользователю:
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');
// The trail is the element that holds 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);
}
Получить код
- Замените содержимое
work
папки содержимымstep-06
.
Ваш work
файл index.html
теперь должен выглядеть так**:**
<!DOCTYPE html>
<html>
<head>
<title>Real-time communication with WebRTC</title>
<link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<h1>Real-time 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 & 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>
- Если вы не следуете этой лаборатории кода из своего
work
каталога, вам может потребоваться установить зависимости для папкиstep-06
или вашей текущей рабочей папки. Просто запустите следующую команду из своего рабочего каталога:
npm install
- После установки, если ваш сервер Node.js не запущен, запустите его, выполнив следующую команду из
work
каталога:
node index.js
Make sure that you're using the version of `index.js` that implements Socket.IO and
не забудьте перезапустить сервер Node.js, если вы вносите изменения.
Дополнительные сведения о Node и Socket.IO см. в разделе Настройка сигнализации.
Сервис для обмена сообщениями.
- При необходимости нажмите Разрешить , чтобы разрешить приложению использовать вашу веб-камеру.
Приложение создает случайный идентификатор комнаты и добавляет идентификатор к URL-адресу.
- Откройте URL-адрес из адресной строки в новой вкладке или окне браузера.
- Нажмите «Создать и отправить » и посмотрите « Входящие фотографии » на другой вкладке в нижней части страницы.
Приложение передает фотографии между вкладками.
Вы должны увидеть что-то вроде этого:
Набрать бонусные баллы
Как вы можете изменить код, чтобы сделать возможным совместное использование файлов любого типа?
Узнать больше
9. Поздравления
Вы создали приложение для потоковой передачи видео и обмена данными в реальном времени!