手把手编写儿童手机远程监控App之vue3设计webrtc通信类
概述上节完成vue3项目中如何引入阿里图标的问题。其最终效果下图嘟宝是为监督孩子身边环境而创建的一套应用它能够实现后台驻留长连接随时接收嘟妈信令建立音视频通道点对点通信而不需要中转服务器。嘟妈是一套WEB 应用使用vue3编写能够兼容app、小程序开发 。而嘟宝采用原生Andorid开发能够获取最大权限保持在后台运行。目前为止嘟宝完成的基本功能设计包括MQTT 基础通信后台驻留SQLite数据存储二维码显示身份识别码开启自启动前台服务任务栏消息发送与点击高德定位实时更新webrtc点对点通信mAgentWeb控件封装解决全屏与获取视频权限问题嘟妈采用vue3编写它的核心需求已完成包含以下部分:完成登录注册页面完成二维码扫描完成与嘟宝绑定与嘟妈建立音视频通信查看嘟宝桌面共享视频项目地址在github上。嘟宝地址嘟妈地址在dobao.ts中混在许多内容如mqtt通信webrtc通信、pinna存储不利于代码耦合与重复利用。设计一个工具类dubaoclient用于webrtc通信设计 dubaoclientdubaoclient类位于src\utils\dubaoclient.ts文件中其初始实现如下exportclassdubaoclient{dubaoId:string;dumaId:string;constructor(dumaId:string,dubaoId:string){this.dubaoIddumaId;this.dumaIddubaoId;console.log(dubao constructor);}test(){console.log(dubao test);}}在dubao.ts中引用并测试该类import{dubaoclient}from/utils/dubaoconstclientnewdubaoclient(1,2)client.test();运行效果如下图所示传参在dumaclient构造函数中包含两个参数dubaoId、dumaId若存在很多参数如何解决构造函数传参问题。dubaoclient源码export classdubaoclient{dubaoId!:string;dumaId!:string;constructor(data:Partialdubaoclient){Object.assign(this,data)console.log(dubao constructor);}test(){console.log(dubaoId:,this.dubaoId);console.log(dumaId:,this.dumaId);}}dubao.ts引用import{dubaoclient}from/utils/dubaoconst clientnewdubaoclient({dubaoId:8206aa74-69c5-483e-83b5-16616e3030eb,dumaId:f1122aeb-f2b0-400d-9919-eddd2eaebaa2})client.test();演示结果回调函数dubaoclient产生事件后向数据传递给调用者这就需要用到回调函数export classdubaoclient{dubaoId!:string;dumaId!:string;peerConnection!:any dataChannel!:any private pushmsg!:(type:string,msg:string)Promisevoidconstructor(data:Partialdubaoclient,pushmsg:(type:string,msg:string)Promisevoid){Object.assign(this,data)this.pushmsgpushmsg console.log(dubao constructor);}test(){console.log(dubaoId:,this.dubaoId);console.log(dumaId:,this.dumaId);}}dubao.ts源码import{dubaoclient}from/utils/dubaoconst clientnewdubaoclient({dubaoId:8206aa74-69c5-483e-83b5-16616e3030eb,dumaId:f1122aeb-f2b0-400d-9919-eddd2eaebaa2},pushmsg)client.test();asyncfunctionpushmsg(code:any,d:any){console.log(pushmsg,code,d)}效果如下图重构dubaoclient源码export classdubaoclient{// dubaoId!: string;// dumaId!: string;private peerConnection!:any private dataChannel!:any private pushmsg!:(type:string,msg:string)Promisevoidconstructor(data:Partialdubaoclient,pushmsg:(type:string,msg:string)Promisevoid){Object.assign(this,data)this.pushmsgpushmsg console.log(dubao constructor);}test(){// console.log(dubaoId:, this.dubaoId);// console.log(dumaId:, this.dumaId);this.pushmsg(test,test message);}asyncstart(coturnurl:string){const configuration{iceServers:[{urls:coturnurl}],}let peerConnectionnewRTCPeerConnection(configuration)this.peerConnectionpeerConnection;// ✅ 创建数据通道必须在创建 Offer 之前let dataChannelpeerConnection.createDataChannel(chat,{ordered:true,// 保证顺序maxRetransmits:3,// 最大重传次数不设置则可靠传输// 或者使用 maxPacketLifeTime: 1000 // 最大存活时间毫秒})this.dataChanneldataChannel;// 监听数据通道事件dataChannel.onopen(){console.log( 数据通道已打开)dataChannel.send(Hello from offerer!)}dataChannel.onmessage(event:any){console.log(收到消息:,event.data)}dataChannel.onclose(){console.log(数据通道已关闭)}peerConnection.addEventListener(icecandidate,async(event:any){if(event.candidate){await this.pushmsg(ice,JSON.stringify(event.candidate))}})try{const localStreamawait navigator.mediaDevices.getUserMedia({video:true,audio:true,})localStream.getTracks().forEach((track){console.log(添加本地轨道:,track)// peerConnection.addTrack(track, localStream);})}catch(error){console.log(error)}// 2. 创建Offerconst offerawait peerConnection.createOffer({offerToReceiveAudio:true,offerToReceiveVideo:true,})await peerConnection.setLocalDescription(offer)await this.pushmsg(offer,JSON.stringify(offer))// 监听远程流视频通话场景peerConnection.addEventListener(track,(event:any){const remoteVideo:anydocument.getElementById(remoteVideo)console.log(remoteVideo.srcObject)if(remoteVideo.srcObject){const stream:anyremoteVideo.srcObjectif(event.track.kindvideo){try{stream.getVideoTracks()stream.removeTrack(stream.getVideoTracks()[0])}catch(error){console.log(error)}}remoteVideo.srcObject.addTrack(event.track)}else{remoteVideo.srcObjectnewMediaStream(event.streams[0])}remoteVideo.play()})// 连接状态变化监听peerConnection.addEventListener(connectionstatechange,(){console.log(Connection state:,peerConnection.connectionState)if(peerConnection.connectionStatedisconnected){this.start(coturnurl)}})}asyncsetRemoteDescription(data:any){await this.peerConnection.setRemoteDescription(newRTCSessionDescription(JSON.parse(data)))}asyncaddIceCandidate(data:any){await this.peerConnection.addIceCandidate(newRTCIceCandidate(JSON.parse(data)))}bye(){if(this.peerConnection){// 关闭所有发送器this.peerConnection.getSenders().forEach((sender:any){if(sender.track)sender.track.stop()})// 关闭连接this.peerConnection.close()this.peerConnectionnull}}}dubao.ts源码templatediv classcontainervan-nav-bar:titledubao?.dubaoName || 嘟宝left-text返回left-arrow click-leftgobacktemplate #rightdiv classnav-rightclickshowMenu truevan-icon nameellipsissize18//div/template/van-nav-barvan-action-sheet v-model:showshowMenu:actionsmenuActionscancel-text取消selectonMenuSelect/div classvideo-containervideo idremoteVideocontrols controlslistnoremoteplayback fullscreen/video/divdiv classvideo-controlsspan classcontrol-btnsvg classiconaria-hiddentrueuse xlink:href#icon-ziyuan/use/svg/spanspan classcontrol-btnsvg classiconaria-hiddentrueuse xlink:href#icon-guanbishengyin/use/svg/spanspan classcontrol-btnsvg classiconaria-hiddentrueuse xlink:href#icon-xiaoxi/use/svg/spanspan classcontrol-btnsvg classiconaria-hiddentrueuse xlink:href#icon-dingwei/use/svg/span/div/div/templatescript setup langtsimport{onMounted,onUnmounted,ref}fromvueimport{c}from/myconfigimport{useMQTTStore}from/store/mqttimport{useRoute,useRouter}fromvue-routerimport{useDubaoStore}from/store/dubaoimport{dubaoclient}from/utils/dubaoconst clientnewdubaoclient({},pushmsg)let storeuseDubaoStore()const routeuseRoute()const routeruseRouter()const{dubaoId}route.params const dubaostore.getDuBao(dubaoId as string)const mqttuseMQTTStore()const showMenuref(false)const menuActions[{name:呼叫,icon:phone-o,action:call},{name:切换相机,icon:replay,action:changecam},{name:远程桌面,icon:desktop-o,action:changescreen},{name:挂断,icon:cross,action:bye,color:#ee0a24},]let isMutedfalselet peerConnection:any let dataChannel:anynullmqtt.$subscribe(async(mutate:any,state){if(typeof mutate.events.newValueboolean){console.log(是布尔类型)return}let dataJSON.parse(state.datamsg)let msgJSON.parse(data.msg)switch(msg.code){caseanswer:// await peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(msg.data)))client.setRemoteDescription(msg.data);breakcaseice:// await peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(msg.data)))client.addIceCandidate(msg.data);default:break}})onMounted((){})onUnmounted((){client.bye()})functiongoback(){// history.back();router.push(/main)}asyncfunctionpushmsg(code:any,d:any){await mqtt.pushmsg(dubaoId as string,code,d)}functioncall(){// init()client.start(c.coturnurl);}/script演示效果如下