WebRTC基于瀏覽器的開發(fā)

WebRTC簡介


WebRTC通信原理

WebRTC需要通過長鏈接查找到通信雙方,然后通過 peer to peer 的方式傳輸音頻數(shù)據(jù)。

PeerConnection

WebRTC中最主要的就是一個叫做PeerConnection的對象,這個是WebRTC中已經(jīng)封裝好的對象。每一路的音視頻會話都會有唯一的一個PeerConnection對象,WebRTC通過這個PeerConnection對象進行視頻的發(fā)起、傳輸、接收和掛斷等操作。
PeerConnection中包含的屬性如下:

  • localDescription:本地描述信息,類型:RTCSessionDescription
  • remoteDescription:遠端描述信息,類型:RTCSessionDescription
  • onicecandidate:傳入一個回調(diào)方法,該回調(diào)方法有一個返回參數(shù),返回參數(shù)類型為:RTCIceCandidateEvent
  • onaddstream:傳入一個回調(diào)方法,該回調(diào)方法有一個返回參數(shù),返回參數(shù)類型為:``,如果檢測到有遠程媒體流傳輸?shù)奖镜刂蟊銜{(diào)用該方法。
  • ondatachannel:(暫未用到)
  • oniceconnectionstatechange:(暫未用到)
  • onnegotiationneeded:(暫未用到)
  • onremovestream:(暫未用到)
  • onsignalingstatechange:(暫未用到)

PeerConnection中還包含了一些方法:

  • setLocalDescription:設置本地offer,將自己的描述信息加入到PeerConnection中,參數(shù)類型:RTCSessionDescription
  • setRemoteDescription:設置遠端的answer,將對方的描述信息加入到PeerConnection中,參數(shù)類型:RTCSessionDescription
  • createOffer:創(chuàng)建一個offer,需要傳入兩個參數(shù),第一個參數(shù)是創(chuàng)建offer成功的回調(diào)方法,會返回創(chuàng)建好的offer,可以在這里將這個offer發(fā)送出去。第二個參數(shù)是創(chuàng)建失敗的回調(diào)方法,會返回錯誤信息。
  • createAnswer:創(chuàng)建一個answer,需要傳入兩個參數(shù),第一個參數(shù)是創(chuàng)建answer成功的回調(diào)方法,會返回創(chuàng)建好的answer,可以在這里將這個answer發(fā)送出去。第二個參數(shù)是創(chuàng)建失敗的回調(diào)方法,會返回錯誤信息。
  • addIceCandidate:將打洞服務器加入到配置信息中,參數(shù)類型:RTCIceCandidate
  • addStream:向PeerConnection中加入需要發(fā)送的數(shù)據(jù)流,參數(shù)類型:MediaStream
  • close:
  • createDTMFSender:
  • createDataChannel:
  • getLocalStreams:
  • getRemoteStreams:
  • getStats:
  • getStreamById:
  • removeStream:
  • updateIce:
RTCSessionDescription

RTCSessionDescription類型中包含了兩個屬性:

  • sdp:這個包含了所有的音視頻的配置信息。
  • type:這個指明了是視頻的接收方還是發(fā)起方,這個將在之后進行討論。
通信過程:

A向B發(fā)起通信請求

  1. A鏈接socket;
  2. A獲取音頻數(shù)據(jù);
  3. A創(chuàng)建一個Ice Candidate;
  4. A通過創(chuàng)建好的Ice Candidate創(chuàng)建一個PeerConnection;
  5. A創(chuàng)建一個offer,offer中包含了視頻設置sdp,將創(chuàng)建好的offer設置為PeerConnectionlocalDescription;
  6. A同時將創(chuàng)建的offerIce Candidate通過socket發(fā)送給B;
  7. 將A獲取到的音頻數(shù)據(jù)存入PeerConnection;
  8. 如果B先接收到A發(fā)過來的offer,那么先將offer存起來,等到接收到A發(fā)過來的Ice Candidate后通過Ice Candidate創(chuàng)建一個PeerConnection,再將保存好的offer設置為PeerConnectionremoteDescription。
    如果B先接收到A發(fā)過來的Ice Candidate,那么通過A發(fā)過來的Ice Candidate創(chuàng)建一個PeerConnection,然后等待接收到A發(fā)過來的offer,再將A發(fā)過來的offer設置為PeerConnectionremoteDescription;
  9. B接收到A發(fā)過來的offer后要創(chuàng)建一個answer,將answer設置為PeerConnectionlocalDescription。并且將創(chuàng)建的answer通過socket返回給A。
  10. B開始獲取音頻數(shù)據(jù),將音頻數(shù)據(jù)存入PeerConnection中,WebRTC便會自動將音頻數(shù)據(jù)發(fā)送給A。
  11. A接收到B返回的answer,將B返回的answer設置為PeerConnectionremoteDescription
  12. 這個時候WebRTC會將音頻數(shù)據(jù)自動發(fā)送給B,A和B就建立起了實時音頻通信。

WebRTC實現(xiàn)

1.信令服務器

首先WebRTC需要一個信令服務器,也就是一個socket鏈接用來發(fā)起視頻通信,發(fā)送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.創(chuàng)建PeerConnection

WebRTC由于是未來的一種即時通信的標準,所以目前在Chrome、Firefox和Opera瀏覽器中有內(nèi)置插件,均提供一個全局的PeerConnection類。

  • Chrome瀏覽器中為webkitRTCPeerConnection
  • FireFox瀏覽器中為mozRTCPeerConnection
  • Opera瀏覽器中暫時沒有特殊名稱

創(chuàng)建PeerConnection的對象:

var peerConnection = new webkitRTCPeerConnection(iceServer) 

創(chuàng)建時需要傳入打洞服務器的配置信息,如果不穿入打洞服務器的配置信息,則只可以在內(nèi)網(wǎng)中使用實時音頻通訊。

由于PeerConnection是全局的,所以我們可以通過另外的一種方式進行創(chuàng)建:

window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var peerConnection = new RTCPeerConnection(iceServer) 

如何判斷瀏覽器是否支持WebRTC:

if (RTCPeerConnection) (function () {
    console.log("瀏覽器支持實時音頻通訊");
    // 這里面可以做其他操作
})();else {
    console.log("您使用的瀏覽器暫不支持實時音頻通訊。");
}
4.獲取本地音視頻數(shù)據(jù)

WebRTC也提供了一個全局單例來獲取本地的音視頻信息:

  • Chrome瀏覽器中為webkitGetUserMedia
  • Firefox瀏覽器中為mozGetUserMedia
  • Opera瀏覽器中為msGetUserMedia

GetUserMedia需要傳入三個參數(shù),第一個參數(shù)為配置信息,第二個參數(shù)為獲取成功的回調(diào),第三個參數(shù)為獲取失敗的回調(diào)。
獲取到視頻流之后

navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia
navigator.getMedia({
    audio: true, // 是否開啟麥克風
    video: true // 是否開啟攝像頭,這里還可以進行更多的配置
}, function(stream){
    // 獲取到視頻流stream
    // 綁定本地媒體流到video標簽用于輸出
    document.getElementById('localVideo').src = URL.createObjectURL(stream);
    // 向PeerConnection中加入需要發(fā)送的流
    peerConnection.addStream(stream);
}, function(error){
    // 獲取本地視頻流失敗
})
5.發(fā)起音頻通話請求

創(chuàng)建一個offer并發(fā)送給指定的對象:

peerConnection.createOffer(function(desc){
    console.log("創(chuàng)建offer成功");
    // 將創(chuàng)建好的offer設置為本地offer
    peerConnection.setLocalDescription(desc);
    // 通過socket發(fā)送offer
}, function(error){
    // 創(chuàng)建offer失敗
    console.log("創(chuàng)建offer失敗");
})

創(chuàng)建offer時要同時發(fā)送打洞服務器配置信息,WebRTC給了一個監(jiān)聽:

peerConnection.onicecandidate = function (event) {
    console.log("發(fā)送打洞服務器配置信息");
}

返回的參數(shù)中有一個candidate屬性,便是打洞服務器的配置信息。

6.收到音頻通話請求

音頻通話請求是通過socket發(fā)來的,需要通過socket去監(jiān)聽。

如果收到了offer,那么需要將offer存到自己的peerConnection中,并且創(chuàng)建一個answer發(fā)送回對方。
將offer存入peerConnection中:

peerConnection.setRemoteDescription(new RTCSessionDescription(json.data.sdp));

創(chuàng)建一個answer并返回給對方:

peerConnection.createAnswer(function(desc){
    console.log("創(chuàng)建answer成功");
    // 將創(chuàng)建好的answer設置為本地offer
    peerConnection.setLocalDescription(desc);
    // 通過socket發(fā)送answer
}, function(error){
    // 創(chuàng)建answer失敗
    console.log("創(chuàng)建answer失敗");
})

如果收到了打洞服務器的配置信息,那么需要將打洞服務器的配置信息存入到peerConnection中:

peerConnection.addIceCandidate(new RTCIceCandidate(json.data.candidate));

發(fā)送給對方answer后便可以等待接受對方的數(shù)據(jù)流了:

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("開始");
    // 僅僅用于控制哪一端的瀏覽器發(fā)起offer,#號后面有值的一方發(fā)起
    // #號后面加true的為發(fā)起者
    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"
        }]
    };

    // 創(chuàng)建PeerConnection實例 (參數(shù)為null則沒有iceserver,即使沒有stunserver和turnserver,仍可在局域網(wǎng)下通訊)
    window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
    var peerConnection = new RTCPeerConnection(iceServer) 

    // 發(fā)送offer的函數(shù)
    var sendOfferFn = function (desc) {
        // 設置本地Offer
        peerConnection.setLocalDescription(desc); 
        // 發(fā)送offer
        socket.send(JSON.stringify({
            "event": "_offer",
                "data": {
                    "sdp": desc
                }
            }));
    };
    // 發(fā)送answer的函數(shù),發(fā)送本地session描述
    var sendAnswerFn = function(desc){ // 發(fā)送answer
        peerConnection.setLocalDescription(desc); // 設置本地Offer
        socket.send(JSON.stringify({ // 發(fā)送answer
            "event": "_answer",
            "data": {
                "sdp": desc
            }
        }));
    };

    // 獲取本地音頻數(shù)據(jù)
    navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia
    navigator.getMedia({
        audio: true, // 是否開啟麥克風
        video: true // 是否開啟攝像頭,這里還可以進行更多的配置
    }, function(stream){ 
        // 獲取到視頻流stream 
        // 綁定本地媒體流到video標簽用于輸出 
        document.getElementById('localVideo').src = URL.createObjectURL(stream); 
        // 向PeerConnection中加入需要發(fā)送的流 
        peerConnection.addStream(stream);
        // 如果是發(fā)起方則發(fā)送一個offer信令
        if(isCaller){
            peerConnection.createOffer(sendOfferFn, function (error) {
                console.log('Failure callback: ' + error);
            });
        }
    }, function(error){ 
        // 獲取本地視頻流失敗
        console.log("獲取本地視頻流失敗");
    })

    // 發(fā)送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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容