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落墨