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服务器:
单击“ 选择文件夹”按钮,然后选择刚刚创建的工作文件夹。这样,您就可以通过“ 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元素:
Realtime communication with WebRTC
Realtime communication with WebRTC
以下内容添加到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 API流式传输视频。
控制媒体捕获和流媒体。
此步骤的完整版本位于step-2文件夹中。
什么是RTCPeerConnection?
RTCPeerConnection是用于进行WebRTC调用以流式传输视频和音频以及交换数据的API。
此示例在同一页面上的两个RTCPeerConnection对象(称为对等方)之间建立连接。实用性不大,但有助于理解RTCPeerConnection的工作原理。
添加视频元素和控制按钮
在index.html中,用两个视频元素和三个按钮替换单个视频元素:
一个视频元素将显示getUserMedia()流,另一个将显示通过RTCPeerconnection流传输的相同视频。(在实际应用程序中,一个视频元素将显示本地流,另一个视频元素将显示远程流。)
添加adapter.js填充程序
添加当前版本adapter.js 链接上面main.js:
adapter.js是一个填充程序,可以将应用程序与规范更改和前缀差异隔离开来。
Index.html现在应该如下所示:
Realtime communication with WebRTC
Realtime communication with WebRTC
安装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);
}
你将学到什么
如何在WebRTC端点(对等方)之间交换数据。
此步骤的完整版本位于步骤03文件夹中。
更新您的HTML
对于此步骤,您将使用WebRTC数据通道textarea在同一页面上的两个元素之间发送文本。这不是很有用,但确实演示了WebRTC如何用于共享数据以及流式视频。
从index.html中删除视频和按钮元素,并使用以下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);
}
注意使用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所以它看起来像这样:
Realtime communication with WebRTC
Realtime communication with WebRTC
在此步骤中,您不会在页面上看到任何内容:所有日志记录都在浏览器控制台上完成。(要在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文件: