关于webRTC的学习记录

915 阅读4分钟

因为公司把这个预研任务交给我才开始学习的webRTC,在此之前我对它一无所知,中间的艰辛过程就不描述了。虽然有前辈给我的demo,但是我学习来还是非常吃力(在此我体会到了英语的重要性...)。下面是我为了自己做的webRTC学习总结以便于自己复习。

项目背景

公司需要做一个基于webRTC的直播、点播页面,方便做视频监控和视频回放,并且不需要做NAT穿透,分配给我的任务是做一个关于webRTC直播的demo页面,要实现接收码流(直播)、断开码流、多屏播放功能。我拿到了一份cto几年前写的纯js的webRTCdemo,里面通过ajax请求来做接口通讯,里面封装的方法非常多,我花了三天时间才看明白...公司要求我改成基于webSocket的方式来做通讯,借此我学习了一下webSocket...后来发现用webSocket果然比使用ajax简单许多(少了发送请求的封装方法操作,接收也简单了许多)。

技术背景

webRTC的技术背景我就不做赘述了,本人小白一个,描述的也不好,借此搬来前辈写的,希望可乐爸前辈勿怪。juejin.cn/post/684490…

学习笔记

首先我前端需要创建一个RTCPeerConnection对象,然后创建一个offer,再将offer添加到本地描述里。

navigator.mediaDevices.getUserMedia(mediaConstraints)方法是用来获取当前设备的摄像头和麦克风的,谷歌浏览器有的未设置默认允许的话应该会弹出申请获取摄像头和麦克风权限。实际上这个是用来向对端发送本地的视频和音频信息的,但我们公司项目不需要客户端发送码流,只需要客户端接收码流即可。但是如果不使用这个方法执行RTCPeerConnection.addTrack()方法的话,localDescription里面关于本地浏览器支持的码流编码格式等内容将会是空的。所以我百度了一下关于怎样在本地描述里添加本地浏览器支持的码流编码格式等内容的方法,于是我加了RTCPeerConnection.addTransceiver('audio');RTCPeerConnection.addTransceiver('video');

创建完RTCPeerConnection对象之后我需要添加它对应的诸多回调函数

其中,最重要的是RTCPeerConnection.ontrack:

这个回调是我已经获取到了流媒体服务器发给我的码流,我需要将其添加到对应的video标签中完成播放。 另一个重要的回调是RTCPeerConnection.onicecandidate:
这个回调是获取到本地信令地址时触发的回调,这时我需要将candidate发送给流媒体服务器

接着我需要要将LocalDescription作为SDP发送给流媒体服务器。

处理回复

我将打包好的数据发送给流媒体服务器之后,要再处理服务器给我的回复

其他的一些回调都是异常处理,全部都是销毁RTCPeerConnection对象 服务器会给我返回一个response,我拿到对应的消息类型之后针对类型做后续操作,如果给我返回的是远程描述SDP,那么我需要创建一个RTCSessionDescription对象: let desc = new RTCSessionDescription(JSON.parse(data.msgContent.SDP)); 并且将这个对象添加到RTCPeerConnection对象中:RTCPeerConnection[index].setRemoteDescription(desc); 如果给我返回的是新的信令地址iceCandidate,那么我需要创建一个IceCandidate对象并添加到RTCPeerConnection对象中: let candidate = new RTCIceCandidate(JSON.parse(data.msgContent.candidate)); RTCPeerConnection[index].addIceCandidate(candidate).catch(e=>{console.log(e);}); 注意,添加远程信令地址时必须要先添加远程描述,否则会报错 这之后RTC连接就建立完毕了。

断开码流

断开码流我会向服务器发送一个断开请求,接着销毁当前的RTCPeerConnection对象中的所有回调,再销毁当前的RTCPeerConnection对象,最后停止video的播放

const { dispatch,streamID} = this.props;
dispatch({
  type:'videoDemo/BreakBitStream',payload:{
    streamID:streamID[ind],
    index:ind
  },
});
const videoList = this.state.videoList;
videoList[ind].isGetBitStream =  false;
this.setState({
  videoList,
});
document.getElementById(`received_video${ind}`).srcObject.getTracks().forEach(track => track.stop());
RTCList[index].ontrack = null;
RTCList[index].onremovetrack = null;
RTCList[index].onremovestream = null;
RTCList[index].onicecandidate = null;
RTCList[index].oniceconnectionstatechange = null;
RTCList[index].onsignalingstatechange = null;
RTCList[index].onicegatheringstatechange = null;
RTCList[index].onnegotiationneeded = null;
if(payload.onBreak){
  payload.onBreak();
}
RTCList[index].close();
RTCList[index] = null;
const message = {
    'seqId': 1,
    'msgType':'notify',
    'msgId':'closeRealStreamNtf',
    'msgContent':{
      'streamID':payload.streamID+'',
    },
  }
ws.send(JSON.stringify(message));