WebRTC學習總結

Nirvana-cn發表於2018-06-25

WebRTC (Web Real-Time Communications) 是一項實時通訊技術,它允許網路應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer)的連線,實現視訊流和(或)音訊流或者其他任意資料的傳輸。WebRTC包含的這些標準使使用者在無需安裝任何外掛或者第三方的軟體的情況下,建立點對點(Peer-to-Peer)的資料分享和電話會議成為可能。

本篇文章從自身實踐出發,結合相關程式碼,總結WebRTC實現的基本流程。

1. 引言

首先我們先看《WebRTC權威指南》上給出的流程圖,從這張圖,我們要明確兩件事:

  • 第一,通訊雙方需要先通過伺服器交換一些資訊
  • 第二,完成資訊交換後,通訊雙方將直接進行連線以傳輸資料

WebRTC學習總結

然後我們再介紹一下WebRTC中的專有名詞,方便讀者對下文的理解。

  • RTCPeerConnection:核心物件,每一個連線物件都需要新建該物件
  • SDP(Session Description Protocol,會話描述協議):包含建立連線的一些必要資訊,比如IP地址等,sdpRTCPeerConnection物件方法建立,我們目前不需要知道該物件中的具體內容,使用黑盒傳輸即可
  • ICE(Interactive Connectivity Establishment,互動式連線建立技術):使用者之間建立連線的方式,用來選取使用者之間最佳的連線方式

2. WebRTC實現流程

以下程式碼不能直接執行,因為我這裡並沒有實現信令伺服器,如何實現信令伺服器可自由選擇(比如,socket.io、websocket等)。

首先發起方獲取視訊流,如果成功,則新建RTCPeerConnection物件,然後建立offer,併傳送給應答方。

  • addStream方法將getUserMedia方法中獲取的流(stream)新增到RTCPeerConnection物件中,以進行傳輸
  • onaddStream事件用來監聽通道中新加入的流,通過e.stream獲取
  • onicecandidate事件用來尋找合適的ICE
  • createOffer()RTCPeerConnection物件自帶的方法,用來建立offer,建立成功後呼叫setLocalDescription方法將localDescription設定為offerlocalDescription即為我們需要傳送給應答方的sdp
  • sendOffersendCandidate方法是自定義方法,用來將資料傳送給伺服器
// 引入<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>指令碼
// 提升瀏覽器相容性
var localConnection
var constraints={
    audio:false,
    video:true
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError)
function handleSuccess(stream) {
  document.getElementById("video").srcObject = stream
  localConnection=new RTCPeerConnection()
  localConnection.addStream(stream)
  localConnection.onaddstream=function(e) {
    console.log('獲得應答方的視訊流' + e.stream)
  }
  localConnection.onicecandidate=function(event){
    if(event.candidate){
        sendCandidate(event.candidate)
    }
  }
  localConnection.createOffer().then((offer)=>{
    localConnection.setLocalDescription(offer).then(sendOffer)
  })
}
複製程式碼

同樣的,接收方也需要新建一個RTCPeerConnection物件

var remoteConnection
var constraints={
    audio:false,
    video:true
    }
}
navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError)
function handleSuccess(stream) {
  document.getElementById("video").srcObject = stream
  remoteConnection=new RTCPeerConnection()
  remoteConnection.addStream(stream)
  remoteConnection.onaddstream=function(e) {
    console.log('獲得發起方的視訊流' + e.stream)
  }
  remoteConnection.onicecandidate=function(event){
      if(event.candidate){
          sendCandidate(event.candidate)
      }
  }
}
複製程式碼

當應答方收到發起方傳送的offer之後,呼叫setRemoteDescription設定RTCPeerConnection物件的remoteDescription屬性,設定成功之後呼叫createAnswer方法,建立answer成功之後將其設定為localDescription,然後把answer傳送給伺服器

let desc=new RTCSessionDescription(sdp)
remoteConnection.setRemoteDescription(desc).then(function() {
    remoteConnection.createAnswer().then((answer)=>{
        remoteConnection.setLocalDescription(answer).then(sendAnswer)
    })
})
複製程式碼

當發起方收到應答方傳送的answer之後,將其設定為remoteDescription,至此WebRTC連線完成。

let desc=new RTCSessionDescription(sdp)
localConnection.setRemoteDescription(desc).then(()=>{console.log('Peer Connection Success')})
複製程式碼

此時雖然WebRTC連線已經完成,但是通訊雙方還不能直接通訊,因為傳送的ICE還沒有處理,通訊雙方還沒有確定最優的連線方式。

應答方收到發起方傳送的ICE資料時,呼叫RTCPeerConnection物件的addIceCandidate方法。

remoteConnection.addIceCandidate(new RTCIceCandidate(ice))
複製程式碼

發起方收到應答方傳送的ICE資料時,同樣呼叫RTCPeerConnection物件的addIceCandidate方法。

localConnection.addIceCandidate(new RTCIceCandidate(ice))
複製程式碼

至此,一個最簡單的WebRTC連線已經建立完成。

3. 資料通道

WebRTC擅長進行資料傳輸,不僅僅是音訊和視訊流,還包括我們希望的任何資料型別,相比於複雜的資料交換過程,建立一個資料通道這個主要功能已經在RTCDataConnection物件中實現了:

var peerConnection = new RTCPeerConnection();
var dataChannel = peerConnection.createDataChannel("label",dataChannelOptions);
複製程式碼

WebRTC會處理好所有的事情,包括瀏覽器內部層。瀏覽器通過一系列的事件來通知應用程式,當前資料通道所處的狀態。ondatachannel事件會通知RTCPeerConnection物件,RTCDataChannel物件本身在開啟、關閉、發生錯誤或者接收到訊息時會觸發對應的事件。

dataChannel.onerror = function (error){
    console.log(error)
}

dataChannel.onmessage = function (event){
    console.log(event.data)
}

dataChannel.onopen = function (error){
    console.log('data channel opened')
    // 當建立一個資料通道後,你必須等onopen事件觸發後才能傳送訊息
    dataChannel.send('Hello world')
}

dataChannel.onclose = function (error){
    console.log('data channel closed')
}
複製程式碼

資料通道datachannel建立的過程略微不同於建立視訊流或音訊流雙向連線,offer、answer、ice處理完畢之後,由一方發起請求即可。

localConnection = new RTCPeerConnection();

sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
複製程式碼

接收方此時並不需要再次呼叫createDataChannel方法,只需要監聽RTCPeerConnection例項物件上的ondatachannel事件就可以在回撥中拿到傳送方的請求,資料通道就建立起來了。

remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;

function receiveChannelCallback(event) {
    receiveChannel = event.channel;
    receiveChannel.onmessage = handleReceiveMessage;
    receiveChannel.onopen = handleReceiveChannelStatusChange;
    receiveChannel.onclose = handleReceiveChannelStatusChange;
  }
複製程式碼

dataChannelOptions傳入的配置項是可選的,並且是一個普通的JavaScript物件,這些配置項可以使應用在UDP或者TCP的優勢之間進行變化。

  • reliable:設定訊息是否進行擔保
  • ordered:設定訊息的接受是否需要按照傳送時的順序
  • maxRetransmitTime:設定訊息傳送失敗時,多久重新傳送
  • maxRetransmits:設定訊息傳送失敗時,最多重發次數
  • protocol:設定強制使用其他子協議,但當使用者代理不支援該協議時會報錯
  • negotiated:設定開發人員是否有責任在兩邊建立資料通道,還是瀏覽器自動完成這個步驟
  • id:設定通道的唯一標識

4. 檔案共享

目前,資料通道支援如下型別:

  • String:JavaScript基本的字串
  • Blob(binary large object):二進位制大物件
  • ArrayBuffer:確定陣列長度的資料型別
  • ArrayBufferView:基礎的陣列檢視

其中,Blob型別是一個可以儲存二進位制檔案的容器,結合HTML5相關檔案讀取API,可以實現檔案共享的功能。

5.更多

MDN文件:>>>點我進入

WebRTC學習資料大全:>>>點我進入

官方Github地址:>>>點我進入

SDP欄位解析:>>>點我進入

個人demo程式碼地址:>>>點我進入

書籍《WebRTC權威指南》,《Learning WebRTC 中文版》