WebRTC (Web Real-Time Communications)
是一項實時通訊技術,它允許網路應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer
)的連線,實現視訊流和(或)音訊流或者其他任意資料的傳輸。WebRTC
包含的這些標準使使用者在無需安裝任何外掛或者第三方的軟體的情況下,建立點對點(Peer-to-Peer
)的資料分享和電話會議成為可能。
本篇文章從自身實踐出發,結合相關程式碼,總結WebRTC
實現的基本流程。
1. 引言
首先我們先看《WebRTC
權威指南》上給出的流程圖,從這張圖,我們要明確兩件事:
- 第一,通訊雙方需要先通過伺服器交換一些資訊
- 第二,完成資訊交換後,通訊雙方將直接進行連線以傳輸資料
然後我們再介紹一下WebRTC
中的專有名詞,方便讀者對下文的理解。
RTCPeerConnection
:核心物件,每一個連線物件都需要新建該物件SDP
(Session Description Protocol
,會話描述協議):包含建立連線的一些必要資訊,比如IP
地址等,sdp
由RTCPeerConnection
物件方法建立,我們目前不需要知道該物件中的具體內容,使用黑盒傳輸即可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
設定為offer
,localDescription
即為我們需要傳送給應答方的sdp
sendOffer
和sendCandidate
方法是自定義方法,用來將資料傳送給伺服器
// 引入<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 中文版》