先看一段視頻演示
簡介
WebRTC允許網絡應用或者站點,在不借助中間媒介的情況下,建立瀏覽器之間點對點(Peer-to-Peer)的連接,p2p實現視頻流和(或)音頻流或者其他任意數據的傳輸”。
通話流程
如瀏覽器 A 想和瀏覽器 B 進行音視頻通話:
A、B 都連接信令服務器(ws);
A 創建本地視頻,並獲取會話描述對象(offer sdp)信息;
A 將 offer sdp 通過 ws 發送給 B;
B 收到信令后,B 創建本地視頻,並獲取會話描述對象(answer sdp)信息;
B 將 answer sdp 通過 ws 發送給 A;
A 和 B 開始打洞,收集並通過 ws 交換 ice 信息; 完成打洞后,
A 和 B 開始為安全的媒體通信協商秘鑰;
至此, A 和 B 可以進行音視頻通話。
我們將使用websocket來作為信令服務器
如果要在非局域網使用,需要再搭一個穿透服務器,coturn
連接信令服務,websocket
initWs(){ let _this = this //修改成你自己websocket服務端的ip和端口 _this.ws = new WebSocket("ws://localhost:9326/ws?username="+this.fromIm); _this.ws.onopen = function(){ // Web Socket 已連接上,使用 send() 方法發送數據 alert("WebSocket連接成功"); //心跳檢測 setInterval(() => { _this.ws.send("{msgType: 'PING'}") },5000) }; //我自己定義的格式,你們可以根據自己修改 //ret = {msgType: 'RTC',msg: '消息體',toIm: '接收人', fromIm: '發送人'} _this.ws.onmessage = function (ret){ var data = JSON.parse(ret.data) console.log("收到消息",data) if(data.msgType!=='RTC'){ return; } const { type, sdp, iceCandidate } = JSON.parse(data.msg) console.log("收到消息",type,iceCandidate) if (type === 'answer') { _this.rtcPeer.setRemoteDescription(new RTCSessionDescription({ type, sdp })); } else if (type === 'answer_ice') { _this.answerIces.push(iceCandidate) } else if (type === 'offer') { _this.toIm = data.fromIm _this.initMedia("answer",new RTCSessionDescription({ type, sdp })); } else if (type === 'offer_ice') { _this.offerIces.push(iceCandidate) } }; _this.ws.onclose = function(){ // 關閉 websocket alert("WebSocket連接已關閉..."); }; }, //接收撥打方的消息,判斷后添加候選人(因為addIceCandidate必須在設置描述remoteDescription之后,如果還沒設置描述,我們先把它存起來,添加描述后再添加候選人) intervalAddIce(){ let _this = this setInterval(() => { if(_this.rtcPeer && _this.rtcPeer.remoteDescription && _this.rtcPeer.remoteDescription.type){ if(!_this.iceFlag){ _this.iceFlag = true; while(_this.offerIces.length>0){ let iceCandidate = _this.offerIces.shift(); _this.rtcPeer.addIceCandidate(iceCandidate).then(_=>{ console.log("success addIceCandidate()"); }).catch(e=>{ console.log("Error: Failure during addIceCandidate()",e); }); } while(_this.answerIces.length>0){ let iceCandidate = _this.answerIces.shift(); _this.rtcPeer.addIceCandidate(iceCandidate).then(_=>{ console.log("success addIceCandidate()"); }).catch(e=>{ console.log("Error: Failure during addIceCandidate()",e); }); } _this.iceFlag = false; } } }, 1000); },
創建媒體源
async initMedia(iceType,offerSdp){ var _this = this //ios瀏覽器不判斷這部分會提示不支持 (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) const UserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || (navigator.mediaDevices && navigator.mediaDevices.getUserMedia); if(!UserMedia){ alert("media,您的瀏覽器不支持訪問用戶媒體設備,請換一個瀏覽器") return; } //RTCPeerConnection 接口代表一個由本地計算機到遠端的WebRTC連接。該接口提供了創建,保持,監控,關閉連接的方法的實現。 const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; if(!PeerConnection){ alert("peer,您的瀏覽器不支持訪問用戶媒體設備,請換一個瀏覽器") return; } _this.localVideo = document.getElementById('localVideo'); navigator.mediaDevices.getUserMedia({ audio: { channelCount: {ideal: 2,min: 1}, //雙聲道 echoCancellation: true, //回聲消除 autoGainControl: true, //修改麥克風輸入音量,自動增益 noiseSuppression: true //消除背景噪聲 } // video: { // width: 400, // height: 500 // } ,video: false }).then(async stream => { await _this.initPeer(iceType,PeerConnection) _this.intervalAddIce() //成功打開音視頻流 try { _this.localVideo.srcObject = stream; } catch (error) { _this.localVideo.src = await window.URL.createObjectURL(stream); } stream.getTracks().forEach( async track => { await _this.rtcPeer.addTrack(track, stream); }); if (!offerSdp) { console.log('創建本地SDP'); const offer = await _this.rtcPeer.createOffer(); await _this.rtcPeer.setLocalDescription(offer); console.log(`傳輸發起方本地SDP`,offer); await _this.ws.send(_this.getMsgObj(offer)); } else { console.log('接收到發送方SDP'); await _this.rtcPeer.setRemoteDescription(offerSdp); console.log('創建接收方(應答)SDP'); const answer = await _this.rtcPeer.createAnswer(); console.log(`傳輸接收方(應答)SDP`); await _this.ws.send(_this.getMsgObj(answer)); await _this.rtcPeer.setLocalDescription(answer); } }).catch(error => { alert("無法開啟本地媒體源:"+error); }) },
創建連接PeerConnection,並通過信令服務器發送到對等端,以啟動與遠程對等端的新WebRTC連接
async initPeer(iceType,PeerConnection){ let _this = this _this.remoteVideo = document.getElementById('remoteVideo'); if(!_this.rtcPeer){ // var stun = "stun:134.175.163.78:3478" // var turn = "turn:134.175.163.78:3478" var stun = "stun:120.24.202.127:3478" var turn = "turn:120.24.202.127:3478" var peerConfig = { "iceServers": [{ "urls": stun }, { "urls": turn, "username": "admin", "credential": "123456" }] }; _this.rtcPeer = new PeerConnection(peerConfig); _this.rtcPeer.onicecandidate = async (e) => { if (e.candidate) { console.log("搜集並發送候選人") await _this.ws.send(_this.getMsgObj({ type: iceType+'_ice', iceCandidate: e.candidate })); }else{ console.log("候選人收集完成") } }; _this.rtcPeer.ontrack = async(e) => { console.log("ontrack",e) if (e && e.streams) { _this.remoteVideo.srcObject = await e.streams[0]; } }; } },
我部署到服務器的,演示地址,https://www.xsport.site/webrtc/
源碼地址 https://gitee.com/suruozhong/webrtc-demo
修改成你的,websocket服務地址,turn穿透服務地址
npm run build,打包完把dist文件夾扔到服務器,就可以音視頻通話了
演示效果:
以下是打包完放到服務器后,手機和電腦進行的音視頻通話
用兩個端打開地址,然后一個端輸入對方的fromIm,撥打。另外一端不需要做什么,如果提示要授權獲取攝像頭和麥克風,點允許即可