0


Vue中webSocket+webRtc实现多人会议,webRtc实现

前提

已经搭建好

websocket

双端通信(可以先模拟),用于实时交换双方信息。交换的信息也就是所谓的信令。实现

webRtc

进行多人会议,屏幕共享、摄像头共享。

我这里定义的websocket信息格式如下

发给某个人,下面会用【消息格式one】指代

{"body":{},"code":"10003",//自定义标识(我自定义区分消息来源用的)"data":{"description":{"type":"answer","sdp":"v=0\r\no=- 700908093190320106 2 IN IP4..."},//需要交换的信息"meetId":"852229c8c454453da6e0b5e99a8407c8",//会议id"pageNum":0,"pageSize":0,"receiveId":"ed986a7b3dbb407e846f76fad909f07d",//接收人Id"sendId":"c0f1094a363949f88f618f5edb5ecaf8",//发送人Id"type":"answer"//信息分类},"msg":"meetingMessage","success":true}

发给会议中所有人,下面会用【消息格式all】指代

{"body":{},"code":"10003","data":{"meetId":"852229c8c454453da6e0b5e99a8407c8",//会议id"pageNum":0,"pageSize":0,"sendId":"c0f1094a363949f88f618f5edb5ecaf8",//发送人Id"type":"new"//信息分类},"msg":"meetingMessage","success":true}

简单说明逻辑

当用户

A

进入会议时,向所有人发送【消息格式all】,通知有人加入了会议,然后其他人(取一人

B

代指)将主动与

A

取得联系。

  • B创建一个专门与A交流的webRtc连接( new RTCPeerConnection(undefined))。将打开的媒体流流加载到连接中
  • B创建完这个webRtc连接后生成一个请求连接的信息通过【消息格式one】发给A,这里面有Bsdp信息,并且自己也存一份,发送建立连接请求webRtc中叫offer
  • 然后A收到offer时,也创建一个专门与B交流的webRtc连接( new RTCPeerConnection(undefined))。然后将B的信息存下来,再生成自己的信息发给B,这里面有Asdp信息,webRtc中这个过程叫应答answer
  • 创建的webRtc连接的时候会使用一个监听器,能监听自己的candidate候选信息有没有制作完,这里面是ice的信息。AB都要监听,制作完后发给对方,对方再存到webRtc连接中,到此双方连接完成。
  • 当一方的媒体源改变时(关闭/打开 麦克风/摄像头/共享桌面),通知其他人连接过期,然后进行以上步骤进行重新连接(除了加入的媒体流不一样,其他一样)

代码参考

打开页面告诉其他人加入会议,这个调用的接口,后台用

webSocket

发给了其他人

onMounted(async()=>{/**打开页面告诉其他人加入*/
    meetingInfoApi.sentMessage({type:'new',meetId: props.id,//这个是会议的id,我这是个组件,从父组件传过来的sendId: data.userInfo.value.id,//这个是获取的登录人的id,作为唯一标识用})})

监听

webSocket

返回,我这里用了一个对象用来存跟会议中其他人沟通的

webRtc

连接,如果只是一对一,可以声明一个存连接的变量就行
这个是声明的变量

const cameraVideo =ref(null);//video标签的ref引用const connectList =ref({}),//用来存跟其他人连接的rtc连接const mediaStream =ref(),//用来存媒体信息const usersList=ref(),//用来存其他用户信息

工具方法,看

connectList 

中有没有请求连接人的专属连接,没有就创建一个

/**有用户请求连接,生成对应的本地连接保存下来,下次直接用*/getConnection(userId){let connection = data.connectList.value?.[userId];if(!connection){let cof ={iceServers:[// 目前免费STUN 服务器{urls:'stun:stun.voipbuster.com ',},]}
    connection =newRTCPeerConnection();

    connection.ontrack=(event)=>{
      methods.onAddStream(event, userId);}

    console.log("监听ice");
    connection.onicecandidate=(event)=>{if(event.candidate){//生成完自己的候选信息后发给这个连接对应的人
        meetingInfoApi.sentMessage({type:"candidate",meetId: props.id,sendId: data.userInfo.value.id,receiveId: userId,label: event.candidate.sdpMLineIndex,sdpMid: event.candidate.sdpMid,candidate: event.candidate.candidate,})}else{
        console.log("End of candidates.");}}//加载媒体流
    data.mediaStream.value?.getTracks()?.forEach(track=>{
      connection.addTrack(track, data.mediaStream.value)})
    data.platformStream.value?.getTracks()?.forEach(track=>{
      connection.addTrack(track, data.platformStream.value)})

    data.connectList.value[userId]= connection;}return connection;},/**有媒体流传过来时在video中播放*/onAddStream(event, userId){if(event && event.streams.length >0){//之后会测试怎么传媒体标识,用来区分是桌面共享还是摄像头,然后显示在不同的位置
        cameraVideo.value.srcObject = event.streams[0];}},

这里是监听

websocket

发送消息的,是服务器主动给前端发的

//监听接收消息
window.addEventListener('receive',function(event){let res =JSON.parse(event.detail)if(res && res.success && res.code ==="10003"&& props.drawer){let connection = methods.getConnection(res.data.sendId)//用户列表增加一个人let send = data.usersList.value?.[res.data.sendId];if(!send){
      data.usersList.value[res.data.sendId]={id: res.data.sendId,name: res.data.sendName,};}if(connection){/**有新用户加入,主动发送offer进行连接*/if(res.data.type ==="new"){let offerOptions ={offerToReceiveAudio:true,offerToReceiveVideo:true,}
        connection.createOffer(offerOptions).then((sessionDescription)=>{
          connection.setLocalDescription(sessionDescription)
          meetingInfoApi.sentMessage({meetId: props.id,sendId: data.userInfo.value.id,receiveId: res.data.sendId,type:'offer',description: sessionDescription
          })})}elseif(res.data.type ==="offer"){/**接收到offer,将对方sdp保存到对应的连接中,发送应答信息*/
        connection.setRemoteDescription(newRTCSessionDescription(res.data.description));
        connection.createAnswer().then((sessionDescription)=>{
          connection.setLocalDescription(sessionDescription)
          meetingInfoApi.sentMessage({meetId: props.id,sendId: data.userInfo.value.id,receiveId: res.data.sendId,type:'answer',description: sessionDescription
          })})}elseif(res.data.type ==="answer"){/**接收到应答信息,保存sdp在本地对应的连接中*/
        connection.setRemoteDescription(newRTCSessionDescription(res.data.description));}elseif(res.data.type ==="candidate"){/**接收到他人的候选信息,保存在本地对应的连接中*/const candidate =newRTCIceCandidate({sdpMid: res.data.sdpMid,sdpMLineIndex: res.data.label,candidate: res.data.candidate,});
        connection.addIceCandidate(candidate).catch((error)=>{
          console.log(error);});}elseif(res.data.type ==="leave"){/**有人离开,关闭他的连接*/
        data.connectList.value?.[res.data.sendId]?.close()delete data.usersList.value[res.data.sendId]delete data.connectList.value[res.data.sendId]}elseif(res.data.type ==="change"){/**有人修改了媒体源,关闭他的连接*/
        data.connectList.value?.[res.data.sendId]?.close()
        data.usersList.value[res.data.sendId].mediaStream =undefineddelete data.connectList.value[res.data.sendId]}}}})

下面是发送媒体示例

当按钮状态发生变化时调用

mediaChange(){let  muteClose = data.muteClose.value//麦克风let  cameraClose = data.cameraClose.value//摄像头let  platformClose = data.platformClose.value//桌面共享//关闭所有连接if(data.connectList.value){for(let valueKey in data.connectList.value){
        data.connectList.value[valueKey]?.close()}
      data.connectList.value ={}
    
      meetingInfoApi.sentMessage({type:'change',meetId: props.id,sendId: data.userInfo.value.id,})}//关闭媒体if((muteClose || cameraClose)&& data.mediaStream.value){
      data.mediaStream.value.getTracks().forEach(track=>{
        track.stop()});
      data.mediaStream.value =null;}if(platformClose && data.platformStream.value){
      data.platformStream.value.getTracks().forEach(track=>{
        track.stop()});
      data.platformStream.value =null;}if(!(muteClose && cameraClose && platformClose)){if((!muteClose ||!cameraClose)&&!data.mediaStream.value){
        methods.getMedia()}if(!platformClose &&!data.platformStream.value){
        methods.getDisplay()}//只要有一个没有关闭,就通知所有人进行重新连接
      meetingInfoApi.sentMessage({type:'new',meetId: props.id,sendId: data.userInfo.value.id,})}},

打开麦克风/摄像头

getMedia(){let muteClose = data.muteClose.value
   let cameraClose = data.cameraClose.value
   let cof ={video: cameraClose ?false: data.enumerateDevicesVideoCheck.value ?{exact: data.enumerateDevicesVideoCheck.value}:undefined,audio: muteClose ?false: data.enumerateDevicesAudioInputCheck.value ?{exact: data.enumerateDevicesAudioInputCheck.value}:undefined,}
   navigator.mediaDevices.getUserMedia(cof).then(stream=>{
         data.mediaStream.value = stream;}).catch(error=> console.log(`无法获取摄像头/麦克风:${error}`));},

打开屏幕共享

getDisplay(){
   navigator.mediaDevices.getDisplayMedia({video:true,audio:true}).then(stream=>{
      data.platformStream.value = stream;
      cameraVideo.value.srcObject = data.platformStream.value;}).catch(error=> console.log(`无法获取屏幕共享:${error}`));},

**根据官方的描述,对等端建立连接后任意一方进行

addTrack

时,另一方是可以通过

onTrack

监听到的,但是我在实际使用中并没有监听到,如果可以的话,就不用频繁的关闭建立连接,还要再研究下**
在这里插入图片描述

标签: vue.js websocket webrtc

本文转载自: https://blog.csdn.net/Amiel_Tian/article/details/135868291
版权归原作者 叶落_无秋 所有, 如有侵权,请联系我们删除。

“Vue中webSocket+webRtc实现多人会议,webRtc实现”的评论:

还没有评论