关于此 Codelab
1. 准备工作
此 Codelab 会教您如何构建应用,以使用网络摄像头获取视频和拍摄快照,并利用 WebRTC 点对点分享这些视频和快照。您还将学习如何使用核心 WebRTC API 并通过 Node.js 设置消息传递服务器。
前提条件
- HTML、CSS 和 JavaScript 的基本知识
构建内容
- 使用网络摄像头获取视频。
- 通过
RTCPeerConnection
流式传输视频。 - 通过
RTCDataChannel
流式传输数据。 - 设置信令服务以交换消息。
- 结合使用对等连接和信令。
- 拍照并使用数据通道进行分享。
所需条件
- Chrome 47 或更高版本
- Web Server for Chrome 或您选择的网络服务器
- 您选择的文本编辑器
- Node.js
2. 获取示例代码
下载代码
- 如果您熟悉 Git,请运行以下命令,以从 GitHub 克隆此 Codelab 的代码:
git clone https://github.com/googlecodelabs/webrtc-web
或者,点击此链接下载 ZIP 文件格式的代码:
- 打开下载的 ZIP 文件,将名为
webrtc-web-master
的项目文件夹解压缩,其中包含一个文件夹(对应此 Codelab 的每个步骤)以及您需要的所有资源。
您可以在名为 work
的目录中处理所有代码。
step-nn
文件夹包含此 Codelab 的每个步骤的已完成版本,以供参考。
安装并验证网络服务器
尽管您可以随意使用自己的网络服务器,但此 Codelab 旨在与 Web Server for Chrome 配合使用。
- 如果您没有 Web Server for Chrome,请点击此链接从 Chrome 网上应用店安装它:
- 点击添加至 Chrome,这会安装 Web Server for Chrome 并自动在新标签页中打开您的 Google 应用。
- 点击 Web Server:
系统会显示一个对话框,您可以在其中配置本地网络服务器:
- 点击 Choose Folder。
- 选择您刚刚创建的
work
文件夹。
在 Web Server URL(s) 下方,您会看到可供您在 Chrome 中查看工作进展情况的
网址。
- 在 Options (may require restart) 下方,选中 Automatically show index.html 复选框。
- 切换 Web Server: Started 两次以停止然后重启服务器。
- 点击 Web Server URL(s) 下方的网址,在网络浏览器中查看您的工作。
您应该会看到如下所示的页面,它对应于 work/index.html
:
很显然,此应用目前不会执行任何有趣的操作。它只是一个极简的框架,用于确保您的网络服务器能够正常运行。您可以在后续步骤中添加功能和布局功能。
3. 通过网络摄像头流式传输视频
此步骤的完整版本位于 step-01
文件夹中。
添加 HTML 格式的 dash
复制此代码并将其粘贴到 work
目录下的 index.html
文件中,以添加 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 格式的 pinch
复制此代码并将其粘贴到 js
文件夹中的 main.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
属性使用 MediaStream:
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;
}
获得奖励积分
- 传递给
getUserMedia()
的localStream
对象处于全局范围内,因此您可以从浏览器控制台进行检查。打开控制台,输入stream,
,然后按Enter
(在 Mac 上,按Return
)。如需在 Chrome 中查看控制台,请按Control+Shift+J
(或者在 Mac 上,按Command+Option+J
)。 localStream.getVideoTracks()
会返回什么?- 调用
localStream.getVideoTracks()[0].stop()
。 - 查看 Constraints 对象。将其更改为
{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%);
}
提示
- 不要忘记
video
元素上的autoplay
属性。否则,您只会看到一个帧! getUserMedia()
限制条件还有许多其他选项。如需查看更多示例,请参阅 WebRTC 示例中的限制条件和统计信息以及更多限制条件和统计信息。
最佳做法
确保视频元素不会溢出容器。此 Codelab 添加了 width
和 max-width
,用于设置视频的首选大小和最大大小。浏览器会自动计算高度。
video {
max-width: 100%;
width: 320px;
}
4. 通过 RTCPeerConnection API 流式传输视频
此步骤的完整版本位于 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
元素会显示本地数据流,另一个显示远程数据流。)
添加 adapter.js shim
复制此脚本元素并将其粘贴到 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
文件。 - 点击 Start,使用网络摄像头获取视频。
- 点击 Call 以建立对等连接
您应该会在两个 video
元素中看到使用网络摄像头录制的相同视频。
- 请查看浏览器控制台,了解 WebRTC 日志记录。
运作方式
此步骤会完成很多操作。
WebRTC 使用 RTCPeerConnection
API 设置连接,以便在 WebRTC 客户端(称为对等设备)之间流式传输视频。
在此示例中,这两个 RTCPeerConnection
对象位于同一页面上:pc1
和 pc2
。
WebRTC 对等设备之间的调用设置涉及三项任务:
- 为各调用端分别创建一个
RTCPeerConnection
,并在每端添加来自getUserMedia()
的本地数据流。 - 获取和分享网络信息。
可能的连接端点称为 ICE 候选对象。
- 获取和分享本地和远程说明。
与本地媒体相关的元数据采用会话描述协议 (SDP) 格式。
假设 Alice 和 Bob 想使用 RTCPeerConnection
设置视频聊天。
首先,Alice 和 Bob 交换网络信息。“寻找候选对象”这一表达式是指使用 ICE 框架查找网络接口和端口的过程。
- Alice 使用
onicecandidate (addEventListener('icecandidate'))
处理程序创建RTCPeerConnection
对象。
这对应于 main.js
中的以下代码:
let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
'iceconnectionstatechange', handleConnectionChange);
- Alice 调用
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
处理程序。 - Alice 向 Bob 发送经过序列化的候选数据。
在真实应用中,此过程称为“信令”,通过消息传递服务进行。您将在后续步骤中了解如何执行此操作。当然,在此步骤中,这两个 RTCPeerConnection
对象位于同一页面上,可以直接进行通信,无需进行外部消息传递。
- 当 Bob 从 Alice 收到候选消息时,他会调用
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 格式继续交换元数据 blob(称为提议和应答)。
- Alice 运行
RTCPeerConnection
createOffer()
方法。
返回的 promise 提供 RTCSessionDescription
- Alice 的本地会话描述:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
- 如果成功,Alice 会使用
setLocalDescription()
设置本地说明,然后通过其信令通道将会话描述发送给 Bob。 - Bob 使用
setRemoteDescription()
将 Alice 发给他的描述设置为远程描述。 - Bob 运行
RTCPeerConnection
createAnswer()
方法,并向其传递从 Alice 收到的远程描述,以便生成与她的描述相符的本地会话。 createAnswer()
promise 会传递RTCSessionDescription
,Bob 将后者设置为本地描述并将其发送给 Alice。- 在 Alice 获取 Bob 的会话描述后,她通过
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 统计信息和调试数据。(您可以前往 chrome://about 查找 Chrome 网址的完整列表。)
- 使用 CSS 设置页面的样式:
- 并排显示视频。
- 让按钮保持相同宽度,但使用较大字体。
- 确保布局适用于移动设备。
- 在 Chrome 开发者工具控制台中,查看
localStream
、localPeerConnection
和remotePeerConnection
。 - 在控制台中,查看
localPeerConnectionpc1.localDescription
。
SDP 格式是什么样的?
提示
- 如需详细了解 adapter.js shim,请参阅 adapter.js GitHub 代码库。
- 请查看 AppRTC 及其代码,这是 WebRTC 项目用于 WebRTC 调用的规范应用。调用设置时间不到 500 毫秒。
最佳做法
为确保您的代码在未来可继续使用,请使用基于 Promise 的新 API,并通过 adapter.js 实现与不支持这些 API 的浏览器的兼容性。
5. 使用数据通道交换数据
此步骤的完整版本位于 step-03
文件夹中。
更新 HTML
在这一步中,您将使用 WebRTC 数据通道在同一页面上的两个 textarea
元素之间发送文本。这不是很有用,但确实演示了如何使用 WebRTC 共享数据和流式传输视频。
从 index.html,
中移除 video
和 button
元素,并将其替换为以下 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
中输入一些文本。 - 点击 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
的语法类似于使用 send()
方法和 message
事件的 WebSocket。
请注意这里使用 dataConstraint
。数据通道可以配置为启用不同类型的数据共享,例如优先考虑消息传递的可靠性而非性能。
获得奖励积分
- 借助 SCTP(WebRTC 数据通道使用的协议),可靠且有序的数据传送默认处于启用状态。在什么情况下,
RTCDataChannel
可能需要提供可靠的数据传送,在什么情况下,性能可能更为重要(即使这意味着会丢失部分数据)? - 使用 CSS 改善页面布局,并为
dataChannelReceive
textarea
添加占位符属性。 - 在移动设备上测试页面。
了解详情
6. 设置信令服务以交换消息
您已了解如何在同一页面上的对等设备之间交换数据,但如何在不同的计算机之间交换数据呢?首先,您需要设置信令通道来交换元数据消息。
此步骤的完整版本位于 step-04
文件夹中。
关于此应用
WebRTC 使用客户端 JavaScript API,但在实际使用中还需要信令(消息传递)服务器以及 STUN 和 TURN 服务器。如需了解详情,可点击此处。
在此步骤中,您将使用 Socket.IO Node.js 模块和 JavaScript 库构建一个简单的 Node.js 信令服务器,用于消息传递。
在此示例中,服务器(Node.js 应用)是在 index.js
中实现的,在其上运行的客户端(Web 应用)是在 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
(或者在 Mac 上,按 Command+Option+J
)。
- 将
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);
});
设置要在 Node.js 上运行的 Socket.IO 文件
在 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
中定义的依赖项。
- 在
work
目录(而非js
目录)的顶层创建一个新文件index.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。
每次转到此网址时,系统都会提示您输入房间名称。
若要加入同一房间,每次都输入相同的房间名称,例如 foo
。
- 打开一个新标签页,再次转到 http://localhost:8080,然后重新输入相同的房间名称。
- 打开另一个新标签页,再次转到 http://localhost:8080,然后重新输入相同的房间名称。
- 查看每个标签页中的控制台。
您应该会看到来自 JavaScript 的日志记录。
获得奖励积分
- 有哪些可行的替代消息传送机制?您在使用纯 WebSocket 时可能会遇到哪些问题?
- 扩缩此应用可能涉及哪些问题?您能开发一种方法来同时测试数千个或数百万个房间请求吗?
- 此应用使用 JavaScript 提示来获取房间名称。了解如何通过网址获取房间名称。例如,根据 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
目录完成此 Codelab,则可能需要安装 step-05
文件夹或当前工作文件夹的依赖项。
- 从工作目录运行以下命令:
npm install
- 安装完毕后,如果 Node.js 服务器未运行,请在
work
目录中运行以下命令来启动该服务器:
node index.js
请确保您使用的是上一步中实现 Socket.IO 的 index.js
版本。如需详细了解 Node 和 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);
}
- 点击 Send,将该图片转换为字节并通过数据通道发送这些字节:
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
目录完成此 Codelab,则可能需要安装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,请查看“设置信令
服务以交换消息”部分。
- 如有必要,请点击 Allow,以允许应用使用您的网络摄像头。
此应用将创建一个随机房间 ID,并将该 ID 添加到相应网址中。
- 从新的浏览器标签页或窗口的地址栏中打开该网址。
- 点击 Snap & Send,然后在另一个标签页的页面底部查看 Incoming photos。
此应用可在各个标签页之间转移照片。
您应会看到类似下图的内容:
获得奖励积分
如何更改代码以便能够共享任意类型的文件?