WebRTC简介
WebRTC通信原理
WebRTC需要通过长链接查找到通信双方,然后通过 peer to peer 的方式传输音频数据。
PeerConnection
WebRTC中最主要的就是一个叫做PeerConnection
的对象,这个是WebRTC中已经封装好的对象。每一路的音视频会话都会有唯一的一个PeerConnection
对象,WebRTC通过这个PeerConnection
对象进行视频的发起、传输、接收和挂断等操作。
PeerConnection中包含的属性如下:
- localDescription:本地描述信息,类型:
RTCSessionDescription
- remoteDescription:远端描述信息,类型:
RTCSessionDescription
- onicecandidate:传入一个回调方法,该回调方法有一个返回参数,返回参数类型为:
RTCIceCandidateEvent
, - onaddstream:传入一个回调方法,该回调方法有一个返回参数,返回参数类型为:``,如果检测到有远程媒体流传输到本地之后便会调用该方法。
- ondatachannel:(暂未用到)
- oniceconnectionstatechange:(暂未用到)
- onnegotiationneeded:(暂未用到)
- onremovestream:(暂未用到)
- onsignalingstatechange:(暂未用到)
PeerConnection
中还包含了一些方法:
- setLocalDescription:设置本地offer,将自己的描述信息加入到
PeerConnection
中,参数类型:RTCSessionDescription
- setRemoteDescription:设置远端的
answer
,将对方的描述信息加入到PeerConnection
中,参数类型:RTCSessionDescription
- createOffer:创建一个offer,需要传入两个参数,第一个参数是创建offer成功的回调方法,会返回创建好的offer,可以在这里将这个offer发送出去。第二个参数是创建失败的回调方法,会返回错误信息。
- createAnswer:创建一个
answer
,需要传入两个参数,第一个参数是创建answer
成功的回调方法,会返回创建好的answer
,可以在这里将这个answer
发送出去。第二个参数是创建失败的回调方法,会返回错误信息。 - addIceCandidate:将打洞服务器加入到配置信息中,参数类型:
RTCIceCandidate
- addStream:向
PeerConnection
中加入需要发送的数据流,参数类型:MediaStream
- close:
- createDTMFSender:
- createDataChannel:
- getLocalStreams:
- getRemoteStreams:
- getStats:
- getStreamById:
- removeStream:
- updateIce:
RTCSessionDescription
RTCSessionDescription
类型中包含了两个属性:
- sdp:这个包含了所有的音视频的配置信息。
- type:这个指明了是视频的接收方还是发起方,这个将在之后进行讨论。
通信过程:
A向B发起通信请求
- A链接socket;
- A获取音频数据;
- A创建一个
Ice Candidate
; - A通过创建好的
Ice Candidate
创建一个PeerConnection
; - A创建一个
offer
,offer
中包含了视频设置sdp
,将创建好的offer
设置为PeerConnection
的localDescription
; - A同时将创建的
offer
和Ice Candidate
通过socket发送给B; - 将A获取到的音频数据存入
PeerConnection
; - 如果B先接收到A发过来的
offer
,那么先将offer
存起来,等到接收到A发过来的Ice Candidate
后通过Ice Candidate
创建一个PeerConnection
,再将保存好的offer
设置为PeerConnection
的remoteDescription
。
如果B先接收到A发过来的Ice Candidate
,那么通过A发过来的Ice Candidate
创建一个PeerConnection
,然后等待接收到A发过来的offer
,再将A发过来的offer
设置为PeerConnection
的remoteDescription
; - B接收到A发过来的
offer
后要创建一个answer
,将answer
设置为PeerConnection
的localDescription
。并且将创建的answer
通过socket返回给A。 - B开始获取音频数据,将音频数据存入
PeerConnection
中,WebRTC便会自动将音频数据发送给A。 - A接收到B返回的
answer
,将B返回的answer
设置为PeerConnection
的remoteDescription
。 - 这个时候WebRTC会将音频数据自动发送给B,A和B就建立起了实时音频通信。
WebRTC实现
1.信令服务器
首先WebRTC需要一个信令服务器,也就是一个socket链接用来发起视频通信,发送WebRTC中的offer
和回复answer
。
如何搭建一个简单的socket服务器,可以找我的这篇文章《》,也可以是用webSocket搭建信令服务器。
2.打洞服务器
WebRTC需要打洞服务器(一个stun
,一个turn
)来穿透防火墙等,我们需要配置打洞服务器:
var iceServer = { "iceServers": [{ "urls" : ["stun:stun.l.google.com:19302"] }, { "urls" : ["turn:numb.viagenie.ca"], "username" : "webrtc@live.com", "credential" : "muazkh" }] };
3.创建PeerConnection
WebRTC由于是未来的一种即时通信的标准,所以目前在Chrome、Firefox和Opera浏览器中有内置插件,均提供一个全局的PeerConnection类。
- Chrome浏览器中为
webkitRTCPeerConnection
- FireFox浏览器中为
mozRTCPeerConnection
- Opera浏览器中暂时没有特殊名称
创建PeerConnection的对象:
var peerConnection = new webkitRTCPeerConnection(iceServer)
创建时需要传入打洞服务器的配置信息,如果不穿入打洞服务器的配置信息,则只可以在内网中使用实时音频通讯。
由于PeerConnection是全局的,所以我们可以通过另外的一种方式进行创建:
window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; var peerConnection = new RTCPeerConnection(iceServer)
如何判断浏览器是否支持WebRTC:
if (RTCPeerConnection) (function () { console.log("浏览器支持实时音频通讯"); // 这里面可以做其他操作 })();else { console.log("您使用的浏览器暂不支持实时音频通讯。"); }
4.获取本地音视频数据
WebRTC也提供了一个全局单例来获取本地的音视频信息:
- Chrome浏览器中为
webkitGetUserMedia
- Firefox浏览器中为
mozGetUserMedia
- Opera浏览器中为
msGetUserMedia
GetUserMedia需要传入三个参数,第一个参数为配置信息,第二个参数为获取成功的回调,第三个参数为获取失败的回调。
获取到视频流之后
navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia navigator.getMedia({ audio: true, // 是否开启麦克风 video: true // 是否开启摄像头,这里还可以进行更多的配置 }, function(stream){ // 获取到视频流stream // 绑定本地媒体流到video标签用于输出 document.getElementById('localVideo').src = URL.createObjectURL(stream); // 向PeerConnection中加入需要发送的流 peerConnection.addStream(stream); }, function(error){ // 获取本地视频流失败 })
5.发起音频通话请求
创建一个offer并发送给指定的对象:
peerConnection.createOffer(function(desc){ console.log("创建offer成功"); // 将创建好的offer设置为本地offer peerConnection.setLocalDescription(desc); // 通过socket发送offer }, function(error){ // 创建offer失败 console.log("创建offer失败"); })
创建offer时要同时发送打洞服务器配置信息,WebRTC给了一个监听:
peerConnection.onicecandidate = function (event) { console.log("发送打洞服务器配置信息"); }
返回的参数中有一个candidate
属性,便是打洞服务器的配置信息。
6.收到音频通话请求
音频通话请求是通过socket发来的,需要通过socket去监听。
如果收到了offer,那么需要将offer存到自己的peerConnection中,并且创建一个answer发送回对方。
将offer存入peerConnection中:
peerConnection.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
创建一个answer并返回给对方:
peerConnection.createAnswer(function(desc){ console.log("创建answer成功"); // 将创建好的answer设置为本地offer peerConnection.setLocalDescription(desc); // 通过socket发送answer }, function(error){ // 创建answer失败 console.log("创建answer失败"); })
如果收到了打洞服务器的配置信息,那么需要将打洞服务器的配置信息存入到peerConnection
中:
peerConnection.addIceCandidate(new RTCIceCandidate(json.data.candidate));
发送给对方answer后便可以等待接受对方的数据流了:
peerConnection.onaddstream = function(event){ console.log("检测到媒体流连接到本地"); // 绑定远程媒体流到video标签用于输出 document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream); };
至此,整个简单的WebRTC的流程就完成了
WebRTC例子
<html> <body> Local: <br> <video id="localVideo" autoplay></video><br> Remote: <br> <video id="remoteVideo" autoplay></video> <script> console.log("开始"); // 仅仅用于控制哪一端的浏览器发起offer,#号后面有值的一方发起 // #号后面加true的为发起者 var isCaller = window.location.href.split('#')[1]; // 与信令服务器的WebSocket连接 var socket = new WebSocket("ws://127.0.0.1:3000"); // stun和turn服务器,打洞服务器设置 var iceServer = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }, { "url": "turn:numb.viagenie.ca", "username": "webrtc@live.com", "credential": "muazkh" }] }; // 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯) window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; var peerConnection = new RTCPeerConnection(iceServer) // 发送offer的函数 var sendOfferFn = function (desc) { // 设置本地Offer peerConnection.setLocalDescription(desc); // 发送offer socket.send(JSON.stringify({ "event": "_offer", "data": { "sdp": desc } })); }; // 发送answer的函数,发送本地session描述 var sendAnswerFn = function(desc){ // 发送answer peerConnection.setLocalDescription(desc); // 设置本地Offer socket.send(JSON.stringify({ // 发送answer "event": "_answer", "data": { "sdp": desc } })); }; // 获取本地音频数据 navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia navigator.getMedia({ audio: true, // 是否开启麦克风 video: true // 是否开启摄像头,这里还可以进行更多的配置 }, function(stream){ // 获取到视频流stream // 绑定本地媒体流到video标签用于输出 document.getElementById('localVideo').src = URL.createObjectURL(stream); // 向PeerConnection中加入需要发送的流 peerConnection.addStream(stream); // 如果是发起方则发送一个offer信令 if(isCaller){ peerConnection.createOffer(sendOfferFn, function (error) { console.log('Failure callback: ' + error); }); } }, function(error){ // 获取本地视频流失败 console.log("获取本地视频流失败"); }) // 发送ICE候选到其他客户端 peerConnection.onicecandidate = function(event){ if (event.candidate !== null) { socket.send(JSON.stringify({ "event": "_ice_candidate", "data": { "candidate": event.candidate } })); } }; // 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出 peerConnection.onaddstream = function(event){ document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream); }; //处理到来的信令 socket.onmessage = function(event){ var json = JSON.parse(event.data); //如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述 if( json.event === "_ice_candidate" ){ peerConnection.addIceCandidate(new RTCIceCandidate(json.data.candidate)); } else { peerConnection.setRemoteDescription(new RTCSessionDescription(json.data.sdp)); // 如果是一个offer,那么需要回复一个answer if(json.event === "_offer") { peerConnection.createAnswer(sendAnswerFn, function (error) { console.log('Failure callback: ' + error); }); } } }; </script> </body> </html>
转自:https://www.jianshu.com/p/57fd3b5d2f80 作者:Shmily落墨