跨境派

跨境派

跨境派,专注跨境行业新闻资讯、跨境电商知识分享!

当前位置:首页 > 卖家故事 > Vue中webSocket+webRtc实现多人会议,webRtc实现

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

时间:2024-04-13 15:25:37 来源:网络cs 作者:淼淼 栏目:卖家故事 阅读:

标签: 实现  会议 
阅读本书更多章节>>>>

前提

已经搭建好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 = new RTCPeerConnection();    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          })        })      } else if (res.data.type === "offer") {        /**接收到offer,将对方sdp保存到对应的连接中,发送应答信息*/        connection.setRemoteDescription(new RTCSessionDescription(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          })        })      } else if (res.data.type === "answer") {        /**接收到应答信息,保存sdp在本地对应的连接中*/        connection.setRemoteDescription(new RTCSessionDescription(res.data.description));      } else if (res.data.type === "candidate") {        /**接收到他人的候选信息,保存在本地对应的连接中*/        const candidate = new RTCIceCandidate({          sdpMid: res.data.sdpMid,          sdpMLineIndex: res.data.label,          candidate: res.data.candidate,        });        connection.addIceCandidate(candidate).catch((error) => {          console.log(error);        });      } else if (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]      } else if (res.data.type === "change") {        /**有人修改了媒体源,关闭他的连接*/        data.connectList.value?.[res.data.sendId]?.close()        data.usersList.value[res.data.sendId].mediaStream = undefined        delete 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监听到的,但是我在实际使用中并没有监听到,如果可以的话,就不用频繁的关闭建立连接,还要再研究下
在这里插入图片描述

阅读本书更多章节>>>>

本文链接:https://www.kjpai.cn/gushi/2024-04-13/158128.html,文章来源:网络cs,作者:淼淼,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。

文章评论