3 HTML躬行記——WebRTC視頻通話( 二 )

在上面的代碼中,實現了最簡易的路由分發,當訪問 http://localhost:1234 時,讀取 index.html 靜態頁面,結構如下所示 。
<video id="localVideo"></video><button id="btn">開播</button><video id="remoteVideo" muted="muted"></video><script src="https://www.huyubaike.com/biancheng/socket.io.js"></script><script src="https://www.huyubaike.com/biancheng/client.js"></script>socket.io.js 是官方的 socket.io 庫,client.js 是客戶端的腳本邏輯 。
在 remoteVideo 中附帶 muted 屬性是為了避免報錯:DOMException: The play() request was interrupted by a new load request 。
最后就可以通過 node server.js 命令,開啟 HTTP 服務器 。
2)長連接
為了便于演示,指定了一個房間,當與信令服務器連接時,默認就會被安排進 living room 。
并且只提供了一個 message 事件,這是交換各端信息的關鍵代碼,將一個客戶端發送來的消息中繼給其他各端 。
const io = new Server(server);const roomId = 'living room';io.on('connection', (socket) => {// 指定房間socket.join(roomId);// 發送消息socket.on('message', (data) => {// 發消息給房間內的其他人socket.to(roomId).emit('message', data);});});因為默認是在本機演示 , 所以也不會安裝 CoTurn,有興趣的可以自行實現 。
三、客戶端在之前的 HTML 結構中,可以看到兩個 video 元素和一個 button 元素 。
const btn = document.getElementById('btn');// 開播按鈕const localVideo = document.getElementById('localVideo');const remoteVideo = document.getElementById('remoteVideo');const size = 300;在兩個 video 元素中,第一個是接收本地的音視頻流,第二個是接收遠端的音視頻流 。
1)媒體協商
在下圖中,Alice 和 Bob 通過信令服務器在交換 SDP 信息 。

3 HTML躬行記——WebRTC視頻通話

文章插圖
Alice 先調用 createOffer() 創建一個 Offer 類型的 SDP , 然后調用 setLocalDescription() 配置本地描述 。
Bob 接收發送過來的 Offer,調用 setRemoteDescription() 配置遠端描述 。
再調用 createAnswer() 創建一個 Answer 類型的 SDP,最后調用 setLocalDescription() 配置本地描述 。
而 Bob 也會接收 Answer 并調用 setRemoteDescription() 配置遠端描述 。后面的代碼會實現上述過程 。
2)RTCPeerConnection
在 WebRTC 中創建連接,需要先初始化 RTCPeerConnection 類,其構造函數可以接收 STUN/TURN 服務器的配置信息 。
// STUN/TURN Serversconst pcConfig = {//'iceServers': [{//'urls': '',//'credential': "",//'username': ""http://}]};// 實例化 RTCPeerConnectionconst pc = new RTCPeerConnection(pcConfig);然后注冊 icecandidate 事件 , 將本機的網絡信息發送給信令服務器 , sendMessage() 函數后面會介紹 。
pc.onicecandidate = function(e) {if(!e.candidate) {return;}// 發送 ICE CandidatesendMessage({type: 'candidate',label: e.candidate.sdpMLineIndex,id: e.candidate.sdpMid,candidate: e.candidate.candidate});};最后注冊 track 事件,接收遠端的音視頻流 。
pc.ontrack = function(e) {remoteVideo.srcObject = e.streams[0];remoteVideo.play();};3)長連接
在客戶端中,已經引入了 socket.io 庫,所以只需要調用 io() 函數就能建立長連接 。
sendMessage() 函數就是發送信息給服務器的 message 事件 。
const socket = io("http://localhost:1234");// 發送消息function sendMessage(data){socket.emit('message', data);}本地也有個 message 事件 , 會接收從服務端發送來的消息,其實就是那些轉發的消息 。
data 對象有個 type 屬性,可創建和接收遠端的 Answer 類型的 SDP 信息,以及接收遠端的 ICE 候選者信息 。
socket.on("message", function (data) {switch (data.type) {case "offer":// 配置遠端描述pc.setRemoteDescription(new RTCSessionDescription(data));// 創建 Answer 類型的 SDP 信息pc.createAnswer().then((desc) => {pc.setLocalDescription(desc);sendMessage(desc);});break;case "answer":// 接收遠端的 Answer 類型的 SDP 信息pc.setRemoteDescription(new RTCSessionDescription(data));break;case "candidate":// 實例化 RTCIceCandidateconst candidate = new RTCIceCandidate({sdpMLineIndex: data.label,candidate: data.candidate});pc.addIceCandidate(candidate);break;}});在代碼中,用 RTCSessionDescription 描述 SDP 信息 , 用 RTCIceCandidate 描述 ICE 候選者信息 。
4)開播
為開播按鈕注冊點擊事件,在事件中,首先通過 getUserMedia() 獲取本地的音視頻流 。
btn.addEventListener("click", function (e) {// 獲取音視頻流navigator.mediaDevices.getUserMedia({video: {width: size,height: size},audio: true}).then((stream) => {localVideo.srcObject = stream;localStream = stream;// 將 Track 與 RTCPeerConnection 綁定stream.getTracks().forEach((track) => {pc.addTrack(track, stream);});// 創建 Offer 類型的 SDP 信息pc.createOffer({offerToRecieveAudio: 1,offerToRecieveVideo: 1}).then((desc) => {// 配置本地描述pc.setLocalDescription(desc);// 發送 Offer 類型的 SDP 信息sendMessage(desc);});localVideo.play();});btn.disabled = true;});

推薦閱讀