开篇导读,这篇是从 https://codelabs.developers.google.com/codelabs/webrtc-web 代码实验室搬运过来的,属于WebRTC理论入门,毕竟WebRTC从名字是来说,人家建立的出发点就是在Web上的,所以我们从正统渠道入手,往后再学习基于操作系统移植的SDK。
7、设置信令服务以交换消息
您将学到什么?
- 使用npm安装package.json中指定的项目依赖项。Use
npm
to install project dependencies as specified in package.json - 运行Node.js服务器,并使用node-static来提供静态文件。Run a Node.js server and use node-static to serve static files.
- 使用Socket.IO在Node.js上设置消息传递服务。Set up a messaging service on Node.js using Socket.IO.
- 使用它来创建“房间”并交换消息。Use that to create ‘rooms' and exchange messages.
该步骤的完整版本位于step-04文件夹中。
相关概念
为了建立和维护WebRTC的呼叫调用,WebRTC客户端(端对端对等方)需要交换元数据:
- (网络)候选人的信息。Candidate (network) information.
- Offer和answer的消息,提供有关媒体的信息,例如分辨率和编解码器。Offer and answer messages providing information about media, such as resolution and codecs.
换句话说,在可以进行音频,视频或数据的对等流传输之前,需要交换元数据。该过程称为信令。
在前面的步骤中,发送方和接收方RTCPeerConnection对象位于同一页上,因此“信号发送”仅是在对象之间传递元数据的问题。
在实际的应用程序中,发送方和接收方RTCPeerConnections在不同设备上的网页中运行,所以需要一种使它们通信元数据的方法。
为此,我们使用信令服务器:可以在WebRTC客户端(端对端)之间传递消息的服务器。实际消息为纯文本:字符串化的JavaScript对象。
前置条件:安装Node.js
为了运行此代码实验室的后续步骤(文件夹步骤04至步骤06),您将需要使用Node.js在本地主机上运行服务器。(windows安装npm教程 )
一旦安装完成后,您将能够导入下一步所需的依赖项(运行npm install)以及运行小型的本地服务器来执行实验室的代码(运行节点index.js)。
关于应用程序
WebRTC使用客户端JavaScript API,但对于实际使用,还需要(消息传递)信令服务器——STUN和TURN服务器。你可以在这里 here 找到更多。
在这一步中,您将使用Socket.IO Node.js模块和JavaScript库进行消息传递,从而构建一个简单的Node.js信令服务器。使用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>
在此步骤中,您将不会在页面上看到任何内容,所有日志记录都已完成到浏览器控制台。
将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的文件,这是一个应用清单,它告诉Node Package Manager(npm)要安装哪些项目依赖项。其中包含以下内容:
{
"name": "webrtc-codelab",
"version": "0.0.1",
"description": "WebRTC codelab",
"dependencies": {
"node-static": "^0.7.10",
"socket.io": "^1.2.0"
}
}
要安装依赖项(例如/socket.io/socket.io.js),请从命令行终端在工作目录中运行以下命令:npm install。如您所愿,npm会安装package.json中定义的依赖项。
在工作目录的顶层(不在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中看到日志记录。
8、结合对等连接和信令
您将学到什么?
- 使用在Node.js上运行的Socket.IO运行WebRTC信令服务。Run a WebRTC signaling service using Socket.IO running on Node.js
- 使用该服务在对等方之间交换WebRTC元数据。Use that service to exchange WebRTC metadata between peers.
此步骤的完整版本位于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="js/lib/adapter.js"></script>
<script src="js/main.js"></script>
</body>
</html>
运行Node.js服务器
如果您不是从工作目录中遵循此代码实验室,则可能需要安装step-05文件夹或当前工作文件夹的依赖项。从您的工作目录中运行以下命令:npm install
安装后,如果您的Node.js服务器未运行,请通过在工作目录中调用以下命令来启动它:node index.js
确保您使用的是实现Socket.IO的上一步中的index.js版本。有关节点和套接字IO的更多信息,请查看“设置信令服务以交换消息”部分。
在新的标签或窗口中再次打开localhost:8080。一个视频元素将显示来自getUserMedia()的本地流,另一个视频元素将显示通过RTCPeerconnection流式传输的“远程”视频。
在浏览器控制台中查看日志记录。
温馨提示:
- 可以从chrome:// webrtc-internals获得WebRTC统计信息和调试数据。WebRTC stats and debug data are available from chrome://webrtc-internals.
- test.webrtc.org 可用于检查您的本地环境并测试您的相机和麦克风。test.webrtc.org can be used to check your local environment and test your camera and microphone.
- 如果您在缓存方面遇到麻烦,请尝试以下操作:If you have odd troubles with caching, try the following:
- 按住Ctrl键并单击“重新加载”按钮,以进行强制刷新 Do a hard refresh by holding down ctrl and clicking the Reload button
- 重新启动浏览器 Restart the browser
- 命令行运行npm cache clean。 Run
npm cache clean
from the command line.
9、拍照并通过数据通道分享
您将学到什么?
- 拍摄照片,并使用canvas元素从中获取数据。Take a photo and get the data from it using the canvas element.
- 与远程用户交换图像数据。Exchange image data with a remote user.
此步骤的完整版本位于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);
});
}
3、当用户单击“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);
}
4、当用户单击“发送”按钮时,将图像转换为字节并通过数据通道发送它们:
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));
}
}
5、接收方将数据通道消息字节转换回图像,并向用户显示该图像:
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);
}
了解更多
- The MediaStream Image Capture API: 用于拍照和控制相机的API-很快就会出现在您附近的浏览器中!
- MediaRecorder API,用于记录音频和视频: demo, documentation.
10、恭喜啦
您构建了一个应用程序来进行实时视频流和数据交换!
你都学到了什么
- 从您的网络摄像头获取视频。Get video from your webcam.
- 使用RTCPeerConnection传输视频。Stream video with RTCPeerConnection.
- 使用RTCDataChannel传输数据。Stream data with RTCDataChannel.
- 设置信令服务以交换消息。Set up a signaling service to exchange messages.
- 结合对等连接和信令。Combine peer connection and signaling.
- 拍照并通过数据通道共享。Take a photo and share it via a data channel.
下一步
- 查看规范的WebRTC聊天应用程序AppRTC的代码和体系结构: app, code.
- 试用 live demos 来自github.com/webrtc/samples.
了解更多
- 可从 webrtc.org. 获得一系列WebRTC入门资源。