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

这篇具有很好参考价值的文章主要介绍了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 = 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监听到的,但是我在实际使用中并没有监听到,如果可以的话,就不用频繁的关闭建立连接,还要再研究下
vue手机端实现视频会议,VUE,vue.js,websocket,webrtc文章来源地址https://www.toymoban.com/news/detail-856155.html

到了这里,关于Vue中webSocket+webRtc实现多人会议,webRtc实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Web端Webrtc,SIP,RTSP/RTMP,硬件端,MCU/SFU融合视频会议系统方案分析

    Web端视频融合,会议互通已经是视频会议应用的大趋势,一是目前企业有大量的老视频会议硬件设,二新业务又需要Web端支持视频会议监控直播需求,迫切需要一个融合对接的方案,即能把老的设备用起来,又能对接新的业务系统,加入Web视频参会互动。 分析: 1,老视频设

    2024年04月25日
    浏览(42)
  • 基于ssm+vue.js+uniapp小程序的会议管理系统附带文章和源代码部署视频讲解等

    🌞 博主介绍 :✌CSDN特邀作者、985计算机专业毕业、某互联网大厂高级全栈开发程序员、码云/掘金/华为云/阿里云/InfoQ/StackOverflow/github等平台优质作者、专注于Java、小程序、前端、python等技术领域和毕业项目实战,以及程序定制化开发、全栈讲解、就业辅导、面试辅导、简

    2024年04月12日
    浏览(50)
  • 手机平板摄像头如何给电脑用来开视频会议

    Iriun Webcam EV虚拟摄像头 钉钉会议 手机平板摄像头如何给电脑用来开视频会议 1.下载软件 手机端和电脑端都下载这个软件,连接同一局域网打开软件连接好 另外一款软件Iriun 也是一样操作 2.打开钉钉会议,设置摄像头,选择你当前的虚拟摄像头即可

    2024年02月08日
    浏览(90)
  • WebSocket+Vue实现简易多人聊天室 以及 对异步调用的理解

    代码仓库:github   HTTP是不支持长连接的,WebSocket是一种通信协议,提供了在单一、长连接上进行全双工通信的方式。它被设计用于在Web浏览器和Web服务器之间实现,但也可以用于任何需要实时通信的应用程序。使用ws作为协议标识符,如果需要加密则使用wss作为协议标识符

    2024年01月17日
    浏览(56)
  • SpringBoot+WebSocket+Vue+PeerJs实现WebRTC视频通话功能,Vue视频通话,web视频通话,webrtc视频通话

    博主正在担任一款电商app的全栈开发,其中涉及到一个 视频通话 功能。但是由于业务需求及成本考虑,不能使用第三方提供的SDK进行开发。所以博主选择使用PeerJs+WebSocket来实现这个功能。 WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定

    2024年02月08日
    浏览(49)
  • 腾讯会议录制视频全攻略,让会议记录更轻松

    随着远程办公和线上教学的兴起,腾讯会议已成为一种常见的在线会议工具,用于实现远程办公、在线教育和协作。然而,许多用户不知道如何记录这些重要的会议,特别是希望将其保留作为会议纪要或培训资料。在本文中,我们将探讨腾讯会议录制视频怎么操作,并详细介

    2024年02月05日
    浏览(50)
  • 私有网络的安全保障,WorkPlus Meet内网视频会议助力企业高效会议

    在企业内部沟通与协作中,视频会议成为了一种必不可少的沟通方式。然而,传统的互联网视频会议往往受制于网络不稳定因素,给企业带来不便与困扰。WorkPlus Meet作为一款专注内网视频会议的软件,致力于为企业打造高效、稳定的内网视频会议体验。 WorkPlus Meet相较于传统

    2024年02月06日
    浏览(57)
  • 视频会议产品对比分析

    内网视频会议系统如何选择?有很多单位为了保密,只能使用内部网络,无法连接互联网,那些SaaS视频会议就无法使用。在内网的优秀视频会议也有很多可供选择,以下是几个常用的: 1. 宝利通:它支持多种终端,包括PC、移动设备和传统视频终端设备。其特点: 多种终端

    2024年02月06日
    浏览(45)
  • WorkPlus Meet私有化视频会议软件-构建安全高效的内网会议体验

    在企业内部,高效的会议协作是推动团队协同和工作效率的关键。而内网会议系统成为了构建安全高效的内部会议体验的必要工具。作为一家领先的内网会议系统,WorkPlus Meet以其卓越的性能和智能化的功能,助力企业实现高效安全的内部会议体验。 为什么选择WorkPlus Meet作为

    2024年01月22日
    浏览(56)
  • 远程视频会议卡顿!如何改善企业网络连接质量?

    要将不同分公司/店铺的监控画面汇总到服务器或者平台系统上,却由于地理位置过于分散,而且监控部署环境复杂多样,不同分公司/店铺部署的网络也不一样,有些甚至还是家用网络,并且不一定具备公网IP,要统一管理和连接起来十分有难度,无法稳定传输和互联互通,就

    2024年01月16日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包