50行代碼完成視頻通話 (WebRTC + WebSocket)

前言

“它(WebRTC)允許網絡應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer)的連接,實現視頻流和(或)音頻流或者其他任意數據的傳輸”

這是 MDN 上對 WebRTC 的描述,初次接觸時無法理解 WebRTC 爲什麼要和 WebSocket 搭配,明明說的很清楚 不借助中間媒介 ,那 WebSocket 充當的是什麼角色?整個 WebRTC 通話建立的流程又是怎樣的?

開始

先看一下最終效果

1.相關技術

本示例主要使用了 WebRTCWebSocket

  • WebRTC(Web Real-Time Communication)即網頁即時通信,是一個支持網頁瀏覽器進行實時語音對話或視頻對話的API。
  • WebSocket是一種在單個TCP連接上進行全雙工通信的協議。在 WebSocket 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

2.通話建立流程

簡單說一下流程,如瀏覽器 A 想和瀏覽器 B 進行音視頻通話:

  1. A、B 都連接信令服務器(ws);
  2. A 創建本地視頻,並獲取會話描述對象(offer sdp)信息;
  3. A 將 offer sdp 通過 ws 發送給 B;
  4. B 收到信令後,B 創建本地視頻,並獲取會話描述對象(answer sdp)信息;
  5. B 將 answer sdp 通過 ws 發送給 A;
  6. A 和 B 開始打洞,收集並通過 ws 交換 ice 信息;
  7. 完成打洞後,A 和 B 開始爲安全的媒體通信協商祕鑰;
  8. 至此, A 和 B 可以進行音視頻通話。

引用網上的有關WebRTC建立的時序圖,可能更加直觀:

從上述流程,可以發現通信雙方在建立連接前需要交換信息,這也就是開頭提到的 WebSocket 充當的角色:信令服務器,用於轉發信息。而 WebRTC 不借助中間媒介 的意思是,在建立對等連接後,不需要藉助第三方服務器中轉,而是直接在兩個實體(瀏覽器)間進行傳輸。

3.代碼

第一步

獲取視頻標籤,連接信令服務器,創建 RTCPeerConnection 對象。其中 RTCPeerConnection 的作用是在兩個對等端之間建立連接,其構造函數支持傳一個配置對象,包含ICE“打洞”(由於本示例在本機進行測試,故不需要)。

const localVideo = document.querySelector('#local-video');
const remoteVideo = document.querySelector('#remote-video');
const socket = new WebSocket('ws://localhost:8080');
const peer = new RTCPeerConnection();

socket.onmessage = () => { // todo }
peer.ontrack = () => { // todo }
peer.onicecandidate = () => { // todo }

第二步

獲取本地攝像頭/麥克風(需要允許使用權限),拿到本地媒體流(MediaStream)後,需要將其中所有媒體軌道(MediaStreamTrack)添加到軌道集,這些軌道將被髮送到另一對等方。

navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then(stream => {
        localVideo.srcObject = stream;
        stream.getTracks().forEach(track => {
            peer.addTrack(track, stream);
        });
    });

第三步

創建發起方會話描述對象(createOffer),設置本地SDP(setLocalDescription),並通過信令服務器發送到對等端,以啓動與遠程對等端的新WebRTC連接。

peer.createOffer().then(offer => {
    peer.setLocalDescription(offer);
    socket.send(JSON.stringify(offer));
});

當調用 setLocalDescription 方法,PeerConnection 開始收集候選人(ice信息),併發送offer_ice到對等方。這邊補充第一步中的peer.onicecandidatesocket.onmessage

對等方收到ice信息後,通過調用 addIceCandidate 將接收的候選者信息傳遞給瀏覽器的ICE代理。

peer.onicecandidate = e => {
    if (e.candidate) {
        socket.send(JSON.stringify({
            type: 'offer_ice',
            iceCandidate: e.candidate
        }));
    } 
};

socket.onmessage = e => {
    const { type, sdp, iceCandidate } = JSON.parse(e.data);
    if (type === 'offer_ice') {
        peer.addIceCandidate(iceCandidate);
    }
}

第四步

接收方收到了offer信令後,開始獲取攝像頭/麥克風,與發起方操作一致。同時將收到offer SDP指定爲連接的遠程對等方屬性(setRemoteDescription),並創建應答SDP(createAnswer),發送到對等端。這邊補充第一步中的socket.onmessage

socket.onmessage = e => {
    const { type, sdp, iceCandidate } = JSON.parse(e.data);
    if (type === 'offer') {
        navigator.mediaDevices.getUserMedia();        // 與發起方一致,省略
        const offerSdp = new RTCSessionDescription({ type, sdp });
        peer.setRemoteDescription(offerSdp).then(() => {
            peer.createAnswer(answer => {
                socket.send(JSON.stringify(answer));
                peer.setLocalDescription(answer)
            });
        });
    }
}

注意:當 setLocalDescription 方法調用後,開始收集候選人信息,併發送 answer_ice 到對等方。與發送方同理,不贅述。

第五步

通過不斷收集ICE信息(onicecandidate),發起方和應答方最終將建立一條最優的連接方式,此時會觸發 ontrack 回調,即可獲取到對等方的媒體流。

peer.ontrack = e => {
    if (e && e.streams) {
        remoteVideo.srcObject = e.streams[0];
    }
};

4.最後

整合發起方和應答方的代碼,差不多50行,不算標題黨!哈哈哈哈哈哈...

完整示例相關代碼已上傳 github.com/shushushv/webrtc-p2p ,⭐🦆~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章