与WebRTC实时通信
一、介绍
WebRTC是一个开源项目,可以在Web和本机应用程序中实现音频,视频和数据的实时通信。WebRTC有几个JavaScript API:
getUserMedia():捕获音频和视频。
MediaRecorder:录制音频和视频。
RTCPeerConnection:在用户之间传输音频和视频。
RTCDataChannel:用户之间的流数据。
在Firefox,Opera和桌面和Android上的Chrome中可以使用WebRTC。WebRTC也可用于iOS和Android上的本机应用程序。
什么是signaling?
WebRTC使用RTCPeerConnection在浏览器之间传递流数据,但也需要一种协调通信和发送控制消息的机制,这一过程称为信令。WebRTC未指定信令方法和协议。本例将使用Socket.IO进行消息传递。
什么是STUN和TURN?
WebRTC旨在实现点对点工作,因此用户可以通过最直接的路由进行连接。但是,WebRTC的构建是为了应对真实的网络:客户端应用程序需要遍历NAT网关和防火墙,并且在直接连接失败的情况下,对等网络需要回退。作为此过程的一部分,WebRTC API使用STUN服务器获取计算机的IP地址,并使用TURN服务器作为中继服务器,以防对等通信失败。
二、概述
构建应用程序以获取视频并使用网络摄像头拍摄快照,并通过WebRTC进行点对点共享。在此过程中,将学习如何使用核心WebRTC API并使用Node.js设置消息传递服务器。
你将学到什么
从网络摄像头获取视频
使用RTCPeerConnection流式传输视频
使用RTCDataChannel流式传输数据
建立信令服务以交换消息
结合对等连接和信令
拍照并通过数据通道分享
你需要什么
Chrome 47或以上
适用于Chrome的Web Server,或使用您自己选择的Web服务器。
示例代码
文本编辑器
HTML,CSS和JavaScript的基础知识
三、获取示例代码
下载代码
如果您熟悉git,可以通过克隆它从GitHub下载此codelab的代码:
git clone https://github.com/googlecodelabs/webrtc-web
或者单击此处下载代码的.zip文件。
打开下载的zip文件。这个项目文件夹包含每个步骤以及需要的所有资源。您将在名为work的目录中完成所有编码工作。
安装并验证Web服务器
虽然您可以自由使用自己的Web服务器,但此codelab可以与Chrome Web服务器配合使用。如果您尚未安装该应用,则可以从Chrome网上应用店安装该应用。
安装Web Server for Chrome应用程序后,单击书签栏,新标签页或应用启动器中的Chrome应用程序快捷方式:
单击Web Server图标:
接下来,您将看到此对话框,它允许您配置本地Web服务器:
单击“ 选择文件夹”按钮,然后选择刚刚创建的工作文件夹。这样,您就可以通过“ Web服务器URL”部分的“Web服务器”对话框中突出显示的URL查看Chrome中正在进行的工作。
在选项下,选中自动显示index.html旁边的框,如下所示:
然后通过滑动标记为Web Server的切换来停止并重新启动服务器:STARTED向左,然后向右移动。
现在,通过单击突出显示的Web服务器URL,在Web浏览器中访问您的工作站点。你应该看到一个看起来像这样的页面,它对应于work / index.html:
从现在开始,应使用此Web服务器设置执行所有测试和验证,只需刷新测试浏览器选项卡。
四、通过网络摄像头流式传输视频
在这一步中,您将了解如何:
从您的网络摄像头获取视频流。
操纵流播放。
使用CSS和SVG来操纵视频。
此步骤的完整版本位于step-01文件夹中。
在工作目录中为index.html添加video元素和script元素:
<!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>
以下内容添加到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);
这里的所有JavaScript示例都用于’use strict’;避免常见的编码问题。
在浏览器中打开index.html,可以看到网络摄像头的视图。
在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
}
}
}
如果getUserMedia()成功,则将来自网络摄像头的视频流设置为视频元素的来源:
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream;
}
不要忘记元素的autoplay属性video。没有它,你只会看到一个帧!
五、使用RTCPeerConnection流式传输视频
在这一步中,您将了解:
使用RTCPeerConnection API流式传输视频。
控制媒体捕获和流媒体。
此步骤的完整版本位于step-2文件夹中。
什么是RTCPeerConnection?
RTCPeerConnection是用于进行WebRTC调用以流式传输视频和音频以及交换数据的API。
此示例在同一页面上的两个RTCPeerConnection对象(称为对等方)之间建立连接。实用性不大,但有助于理解RTCPeerConnection的工作原理。
添加视频元素和控制按钮
在index.html中,用两个视频元素和三个按钮替换单个视频元素:
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
一个视频元素将显示getUserMedia()流,另一个将显示通过RTCPeerconnection流传输的相同视频。(在实际应用程序中,一个视频元素将显示本地流,另一个视频元素将显示远程流。)
添加adapter.js填充程序
添加当前版本adapter.js 链接上面main.js:
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
adapter.js是一个填充程序,可以将应用程序与规范更改和前缀差异隔离开来。
Index.html现在应该如下所示:
<!DOCTYPE html>
<html>
<head>
<title>Realtime communication with WebRTC</title>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<h1>Realtime communication with WebRTC</h1>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
安装RTCPeerConnection代码
将main.js替换为step-02文件夹中的版本。
在codelab中使用大块代码进行剪切和粘贴并不理想,但为了使RTCPeerConnection启动并运行,除了整个替换之外别无选择。
打开index.html,单击“ Start”按钮以从网络摄像头获取视频,然后单击“ Call”以建立对等连接。您应该在两个视频元素中看到相同的视频(来自您的网络摄像头)。查看浏览器控制台以查看WebRTC日志记录。
在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.');
当网络候选者可用时,步骤1中的处理程序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的会话描述协议格式交换元数据块(称为offer 和answer)来进行交换媒体配置信息的信令:
1 Alice运行RTCPeerConnection createOffer()方法。返回的promise提供了一个RTCSessionDescription:Alice的本地会话描述:
trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
.then(createdOffer).catch(setSessionDescriptionError);
2 如果成功,Alice使用本地描述设置setLocalDescription(),然后通过其信令通道将此会话描述发送给Bob。
3 Bob将Alice发送给他的描述设置为使用的远程描述setRemoteDescription()。
4 Bob运行RTCPeerConnection createAnswer()方法,向其传递从Alice获得的远程描述,因此可以生成与她兼容的本地会话。createAnswer() promise 传递一个RTCSessionDescription:Bob设置为本地描述,并将其发送给Alice。
5 当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);
}
六、使用RTCDataChannel交换数据
你将学到什么
如何在WebRTC端点(对等方)之间交换数据。
此步骤的完整版本位于步骤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>
更新您的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);
}
注意使用dataConstraint。可以配置数据通道以实现不同类型的数据共享 - 例如,优先考虑可靠的交付而不是性能。
七、建立信令服务以交换消息
你将学到什么
使用npm安装项目依赖中规定的package.json
运行Node.js服务器并使用node-static来提供静态文件。
使用Socket.IO在Node.js上设置消息传递服务。
用它来创建“房间”并交换消息。
此步骤的完整版本位于step-04文件夹中。
为了设置和维护WebRTC调用,WebRTC客户端(对等方)需要交换元数据:候选人(网络)信息。提供和回答提供有关媒体信息的消息,例如分辨率和编解码器。换句话说,在可以发生音频,视频或数据的对等流传输之前,需要交换元数据。此过程称为信令。
在前面的步骤中,发送方和接收方RTCPeerConnection对象位于同一页面上,因此“信令”只是在对象之间传递元数据的问题。
在现实世界的应用程序中,发送方和接收方RTCPeerConnections在不同设备上的网页中运行,您需要一种方式让它们进行元数据通信。
为此,您使用信令服务器:可以在WebRTC客户端(对等方)之间传递消息的服务器。实际的消息是纯文本:字符串化的JavaScript对象。
先决条件:安装Node.js.
为了运行此codelab的下一步(文件夹步骤04到步骤06),您需要使用Node.js在localhost上运行服务器。
您可以下载并安装由Node.js的这个链接。
安装后,您将能够导入后续步骤(运行npm install)所需的依赖项,以及运行一个小型localhost服务器来执行codelab(运行node index.js)。这些命令将在以后需要时显示。
关于该应用程序
WebRTC使用客户端JavaScript API,但对于实际使用,还需要信令(消息)服务器,以及STUN和TURN服务器。
在这一步中,您将构建一个简单的Node.js信令服务器,使用Socket.IO Node.js模块和JavaScript库进行消息传递。使用Node.js和Socket.IO会很有用,但并不重要; 消息传递组件非常简单。
Socket.IO的设计使得构建交换消息的服务变得简单,Socket.IO因其内置的“房间”概念而适合学习WebRTC信令。
在此示例中,服务器(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>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。如果您使用的是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);
});
设置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
您应该看到一个安装日志,结束如下所示:
在工作目录的顶层(而不是在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);
}
});
}
});
});
从命令行终端,在工作目录中运行以下命令:
node index.js
在浏览器中,打开localhost:8080。
每次打开此URL时,系统都会提示您输入房间名称。要加入同一个房间,请每次选择相同的房间名称,例如“foo”。
打开一个新标签页,然后再次打开localhost:8080。选择相同的房间名称。
在第三个选项卡或窗口中打开localhost:8080。再次选择相同的房间名称。
检查每个选项卡中的控制台:您应该从上面的JavaScript中看到日志记录。
八、结合对等连接和信令
你将学到什么
使用在Node.js上运行的Socket.IO运行WebRTC信令服务
使用该服务在对等体之间交换WebRTC元数据。
此步骤的完整版本位于步骤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替换为步骤05 / js / main.js的内容。
运行Node.js服务器
node index.js
在浏览器中,打开localhost:8080。
在新选项卡或窗口中再次打开localhost:8080。一个视频元素将显示本地流getUserMedia(),另一个将显示通过RTCPeerconnection流式传输的“远程”视频。
注意:每次关闭客户端选项卡或窗口时,都需要重新启动Node.js服务器。
可在浏览器控制台中查看日志记录
如果您遇到奇怪的缓存问题,请尝试以下方法:
按住ctrl并单击“ 重新加载”按钮进行硬刷新
重启浏览器
运行npm cache clean命令行。
九、拍照并通过数据通道分享
你将学到什么
拍摄照片并使用canvas元素从中获取数据。
与远程用户交换图像数据。
此步骤的完整版本位于步骤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 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));
}
}
接收方将数据通道消息字节转换回图像并将图像显示给用户:
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的内容替换work文件夹的内容。你的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 & 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>
该应用程序将创建一个随机的房间ID,并将该ID添加到URL。在新的浏览器选项卡或窗口中打开地址栏中的URL。
单击“ Snap & Send”按钮,然后在页面底部的另一个选项卡中查看传入区域。该应用在标签之间传输照片。