WebRTC实时音视频通讯
先看一段视频演示
简介
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,拨打。另外一端不需要做什么,如果提示要授权获取摄像头和麦克风,点允许即可