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

前言

「它(WebRTC)容許網絡應用或者站點,在不借助中間媒介的狀況下,創建瀏覽器之間點對點(Peer-to-Peer)的鏈接,實現視頻流和(或)音頻流或者其餘任意數據的傳輸」

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

開始

先看一下最終效果github

1.相關技術

本示例主要使用了 WebRTCWebSocketweb

  • 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鏈接。socket

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

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

對等方收到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 ,⭐🦆~

相關文章
相關標籤/搜索