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,拨打。另外一端不需要做什么,如果提示要授权获取摄像头和麦克风,点允许即可

 

 

 

 

 

posted @ 2020-07-16 15:12  suruozhong  阅读(2913)  评论(5编辑  收藏  举报