group-cb2f525f.js 14 KB

1
  1. var y=Object.defineProperty;var k=(n,e,t)=>e in n?y(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var d=(n,e,t)=>(k(n,typeof e!="symbol"?e+"":e,t),t);import{K as v,bv as I,ac as $,V as T}from"./radical-d5778ee4.js";import{d as E,k as f,s as L,o as C,x as P,X as _,a6 as M,t as G,A as D,f as N,u as O,a9 as W,P as A,r as R}from"./vue-bbe2430e.js";import{j as U}from"./gb28181-8c8b3c56.js";const X=v({id:"screen-store",persist:{paths:["customInfo"]},state:()=>({curScreenNum:1,multiPlayRef:null,activeInfo:{parentId:"",deviceId:"",parentName:"",name:"",path:"",type:""},customInfo:{collapsed:!1,rowNum:1,screenNum:1,selectNum:0}}),getters:{getActiveInfo(){return this.activeInfo}},actions:{setCurScreenNum(n){this.curScreenNum=n},setMultiPlayRef(n){this.multiPlayRef=n},setActiveInfo(n){this.activeInfo=n},setCustomInfo(n){const e=Object.assign(this.customInfo,n);this.customInfo=e}}}),V={class:"video-container"},B=["id","srcObject"],F=E({__name:"batch",props:{path:{},stream:{}},setup(n){const e=f();L(()=>{e.value&&(e.value.addEventListener("playing",()=>{}),e.value.addEventListener("pause",()=>{}),e.value.addEventListener("ended",()=>{}),e.value.addEventListener("error",()=>{}))});const t=f(!0);return C(()=>{I(e.value,"canplay",()=>{t.value=!1})}),P(()=>{}),(i,o)=>(_(),M("div",V,[G(N(O($),{class:"loading",size:"large"},null,512),[[D,t.value]]),W("video",{ref_key:"videoEle",ref:e,class:"video",id:"video-"+i.path,srcObject:i.stream,autoplay:"",playsinline:""},null,8,B),A(i.$slots,"default",{},void 0,!0)]))}});const Q=T(F,[["__scopeId","data-v-b2e00bc2"]]);class j{constructor(e){d(this,"ws",null);d(this,"pc",null);d(this,"localStream",null);d(this,"subscribedStreams",new Set);d(this,"videoSenders",new Map);d(this,"streamToTransceiver",new Map);d(this,"eventListeners",new Map);d(this,"wsUrl");d(this,"pingInterval",null);const t=location.protocol==="https:"?"wss:":"ws:";this.wsUrl=e?`${t}//${e}/webrtc/batchv2`:`${t}//${location.host}/webrtc/batchv2`}async connect(){try{return this.log(`正在连接到 ${this.wsUrl}...`),this.ws=new WebSocket(this.wsUrl),new Promise((e,t)=>{if(!this.ws){t(new Error("WebSocket 未初始化"));return}this.ws.onopen=async()=>{this.log("WebSocket 连接已建立","success"),this.startPingInterval();const i={iceTransportPolicy:"all",bundlePolicy:"max-bundle",rtcpMuxPolicy:"require",iceCandidatePoolSize:1};this.pc=new RTCPeerConnection(i);const o=this.pc.addTransceiver("video",{direction:"sendrecv"});this.videoSenders.set("placeholder",o.sender),this.log("已向 PeerConnection 添加占位轨道","info"),this.setupPeerConnectionEventHandlers();const s=await this.pc.createOffer();await this.pc.setLocalDescription(s),this.sendMessage({type:"offer",sdp:this.pc.localDescription.sdp}),this.emit("connected",null),e()},this.ws.onmessage=this.handleWebSocketMessage.bind(this),this.ws.onclose=()=>{this.log("WebSocket 连接已关闭"),this.cleanup(),this.emit("disconnected",null),t(new Error("WebSocket 连接已关闭"))},this.ws.onerror=i=>{this.log(`WebSocket 错误: ${i}`,"error"),this.cleanup(),this.emit("error",{message:"WebSocket 错误"}),t(new Error("WebSocket 错误"))}})}catch(e){throw this.log(`连接错误: ${e.message}`,"error"),this.cleanup(),this.emit("error",{message:e.message}),e}}disconnect(){this.cleanup()}async startPublishing(e){try{if(!e)throw new Error("请输入有效的流路径");if(!this.pc||!this.ws)throw new Error("未连接到服务器");this.localStream=await navigator.mediaDevices.getUserMedia({video:!0,audio:!1});const t=this.localStream.getVideoTracks()[0],i=this.videoSenders.get("placeholder");i&&(await i.replaceTrack(t),this.log("已用真实轨道替换占位视频轨道","success")),this.videoSenders.delete("placeholder"),this.videoSenders.set(e,i);const o=await this.pc.createOffer();return await this.pc.setLocalDescription(o),await this.waitForIceGathering(),this.sendMessage({type:"publish",streamPath:e,offer:this.pc.localDescription.sdp}),this.log(`已开始发布到 ${e}`,"success"),this.emit("publishStarted",{streamPath:e}),Promise.resolve()}catch(t){throw this.log(`发布错误: ${t.message}`,"error"),this.emit("error",{message:t.message}),t}}async stopPublishing(e){try{if(!this.pc||!this.ws)throw new Error("未连接到服务器");const t=this.videoSenders.get(e);t&&(await t.replaceTrack(null),this.log("已移除视频轨道","info"),this.videoSenders.delete(e),this.videoSenders.set("placeholder",t)),this.localStream&&(this.localStream.getTracks().forEach(o=>o.stop()),this.localStream=null);const i=await this.pc.createOffer();return await this.pc.setLocalDescription(i),await this.waitForIceGathering(),this.sendMessage({type:"unpublish",streamPath:e}),this.log(`已停止发布到 ${e}`,"success"),this.emit("publishStopped",{streamPath:e}),Promise.resolve()}catch(t){throw this.log(`停止发布错误: ${t.message}`,"error"),this.emit("error",{message:t.message}),t}}getStreamList(){if(!this.ws){this.log("未连接到服务器","error");return}this.sendMessage({type:"getStreamList"}),this.log("已请求流列表","info")}async subscribeToStreams(e){try{if(!this.pc||!this.ws)throw new Error("未连接到服务器");if(e.length===0)throw new Error("请至少选择一个流");const t=new Set(this.subscribedStreams);this.subscribedStreams.clear(),e.forEach(s=>{s&&this.subscribedStreams.add(s)});const i=[];t.forEach(s=>{if(!this.subscribedStreams.has(s)){const h=this.streamToTransceiver.get(s);h&&(h.direction="inactive",this.log(`已将移除流 ${s} 的转接器设置为 inactive`,"info"),this.streamToTransceiver.delete(s)),i.push(s),this.emit("streamRemoved",{streamPath:s})}}),i.length>0&&await this.sendUnsubscribeSignal(i);const o=Array.from(this.subscribedStreams).filter(s=>!t.has(s));if(this.log(`新流路径: ${o.join(", ")}`,"info"),o.length>0){const s=this.pc.getTransceivers().filter(u=>u.direction==="inactive");this.log(`可用转接器数量: ${s.length}`,"info");const h=[...o];for(;h.length>0&&s.length>0;)h.pop(),s.pop().direction="recvonly";const g=h.length;if(g>0){this.log(`添加 ${g} 个新视频转接器`,"info");for(let u=0;u<g;u++)this.pc.addTransceiver("video",{direction:"recvonly"})}const m=await this.pc.createOffer();await this.pc.setLocalDescription(m),this.sendMessage({type:"subscribe",streamList:o,offer:this.pc.localDescription.sdp}),this.log(`订阅了 ${o.length} 个新流`,"success")}return this.log(`当前播放流总数: ${this.subscribedStreams.size}`,"success"),Promise.resolve()}catch(t){throw this.log(`播放流错误: ${t.message}`,"error"),this.emit("error",{message:t.message}),t}}async sendUnsubscribeSignal(e){if(!this.ws||!this.pc){this.log("未连接到服务器","error");return}if(e.length!==0)try{const t=await this.pc.createOffer();await this.pc.setLocalDescription(t),await this.waitForIceGathering(),this.sendMessage({type:"unsubscribe",streamList:e,offer:this.pc.localDescription.sdp}),this.log(`已为 ${e.length} 个流发送取消订阅信号`,"info")}catch(t){throw this.log(`发送取消订阅信号错误: ${t.message}`,"error"),t}}async unsubscribeFromStream(e){try{if(!this.pc||!this.ws)throw new Error("未连接到服务器");const t=this.streamToTransceiver.get(e);return t&&(t.direction="inactive",this.log(`已将 ${e} 的转接器设置为 inactive`,"info"),this.streamToTransceiver.delete(e),await this.sendUnsubscribeSignal([e])),this.subscribedStreams.delete(e),this.emit("streamRemoved",{streamPath:e}),this.log(`已从订阅列表移除 ${e}`,"info"),Promise.resolve()}catch(t){throw this.log(`取消订阅流错误: ${t.message}`,"error"),this.emit("error",{message:t.message}),t}}getLocalStream(){return this.localStream}getSubscribedStreams(){return Array.from(this.subscribedStreams)}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,[]),this.eventListeners.get(e).push(t)}off(e,t){if(!this.eventListeners.has(e))return;const i=this.eventListeners.get(e),o=i.indexOf(t);o!==-1&&i.splice(o,1)}emit(e,t){if(!this.eventListeners.has(e))return;const i=this.eventListeners.get(e);for(const o of i)o(t)}log(e,t="info"){this.emit("log",{message:e,level:t,time:new Date})}setupPeerConnectionEventHandlers(){this.pc&&(this.pc.onicecandidate=e=>{e.candidate?this.log("ICE 候选: "+e.candidate.candidate):this.log("ICE 收集完成")},this.pc.onicegatheringstatechange=()=>{this.log(`ICE 收集状态: ${this.pc.iceGatheringState}`),this.emit("iceStateChange",{state:this.pc.iceGatheringState})},this.pc.oniceconnectionstatechange=()=>{this.log(`ICE 连接状态: ${this.pc.iceConnectionState}`),this.emit("iceStateChange",{state:this.pc.iceConnectionState}),this.pc.iceConnectionState==="failed"&&this.log("ICE 连接失败","error")},this.pc.onconnectionstatechange=()=>{this.log(`连接状态已变更: ${this.pc.connectionState}`),this.emit("connectionStateChange",{state:this.pc.connectionState}),this.pc.connectionState==="connected"&&this.log("PeerConnection 建立成功","success")},this.pc.ontrack=this.handleTrackEvent.bind(this))}handleTrackEvent(e){this.log(`收到轨道: ${e.track.kind}/${e.track.id}`,"success");const t=e.transceiver;t||this.log(`未找到轨道 ${e.track.id} 的转接器`,"warn");const i={};e.track.onunmute=()=>{this.log(`轨道已 unmute: ${e.track.kind}/${e.track.id}`,"success")};const o=setInterval(async()=>{if(!this.pc||this.pc.connectionState!=="connected"){this.log("连接状态变更,停止统计收集","info"),clearInterval(o);return}try{(await this.pc.getStats(e.track)).forEach(h=>{if(h.type==="inbound-rtp"&&h.kind===e.track.kind){const g=h.packetsReceived||0;(i[e.track.id]||0)!==g&&(i[e.track.id]=g)}})}catch(s){this.log(`获取统计信息错误: ${s.message}`,"error")}},5e3);if(e.track.kind==="video"&&e.streams[0]){const s=e.streams[0].id;this.streamToTransceiver.set(s,t),this.emit("streamAdded",{streamId:s,stream:e.streams[0],track:e.track})}}async handleWebSocketMessage(e){const t=JSON.parse(e.data);if(this.log(`收到消息: ${t.type}`),"type"in t)switch(t.type){case"pong":this.log("收到 pong 响应","debug");break;case"answer":const i=new RTCSessionDescription({type:"answer",sdp:t.sdp});await this.pc.setRemoteDescription(i),this.log("远端描述已设置","success");break;case"error":this.log(`错误: ${t.message}`,"error"),this.emit("error",{message:t.message});break;case"streamList":this.log(`收到流列表,共 ${t.streams.length} 个流`,"info"),this.emit("streamList",{streams:t.streams});break}}sendMessage(e){if(!this.ws){this.log("未连接到服务器","error");return}this.ws.send(JSON.stringify(e))}async waitForIceGathering(e=2e3){return this.pc?Promise.race([new Promise(t=>{if(this.pc.iceGatheringState==="complete")t();else{const i=()=>{this.pc.iceGatheringState==="complete"&&(this.pc.removeEventListener("icegatheringstatechange",i),t())};this.pc.addEventListener("icegatheringstatechange",i)}}),new Promise(t=>setTimeout(t,e))]):Promise.reject(new Error("PeerConnection 未初始化"))}startPingInterval(){this.stopPingInterval(),this.pingInterval=setInterval(()=>{this.sendPing()},5e3),this.log("已启动 ping 定时器","debug")}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null,this.log("已停止 ping 定时器","debug"))}sendPing(){this.ws&&this.ws.readyState===WebSocket.OPEN&&(this.sendMessage({type:"ping"}),this.log("已发送 ping 消息","debug"))}cleanup(){this.stopPingInterval(),this.ws&&(this.ws.close(),this.ws=null),this.pc&&(this.pc.close(),this.pc=null),this.localStream&&(this.localStream.getTracks().forEach(e=>e.stop()),this.localStream=null),this.subscribedStreams.clear(),this.videoSenders.clear(),this.streamToTransceiver.clear(),this.log("连接已清理","info")}}let c=null;function Y(n=!1){const e=R([]),t=f([]),i=()=>e.map(a=>a.path),o=async()=>{if(await h(),!!c)try{await c.subscribeToStreams(i()),`${i().length}`}catch{}},s=function(a){let l=String(a.getMilliseconds());return"padStart"in String.prototype&&(l=l.toString().padStart(3,"0")),`${a.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/,"$1")}:${l}`},h=async()=>new Promise(async a=>{if(c)return a();const l=window.allEnv.media;let p="";l.startsWith("http")?p=l.replace("http://","").replace("https://",""):p=`${location.host}${l}`,c=new j(p),c.on("log",r=>console[["info","debug","warn","error"].indexOf(r.level)!==-1?r.level:"info"](s(r.time),r.message)),c.on("error",r=>console.log(`客户端错误: ${r.message}`)),c.on("connected",()=>{a()}),c.on("disconnected",()=>{c=null;const r=e.length;e.length=0,e.push(...new Array(r).fill(0).map(()=>({path:"",stream:void 0}))),t.value.length=0}),c.on("publishStarted",r=>{`${r.streamPath}`}),c.on("publishStopped",r=>{`${r.streamPath}`}),c.on("streamList",r=>{`${r.streams.length}`,r.streams.length===0&&(e.length=0)}),c.on("streamAdded",r=>{`${r.streamId}`,e.some(b=>b.path===r.streamId)&&(t.value.push({id:r.streamId,stream:r.stream}),u())}),c.on("streamRemoved",r=>{`${r.streamId}`});try{await c.connect()}catch{c=null}}),g=async({deviceItem:a,streamItem:l,path:p},r)=>{if(n&&a){if(!a.parentId||!a.deviceId){console.error("GB28181设备信息不完整,无法更新流");return}e[r].path=p,e[r].deviceItem=a}else l&&(e[r].path=p,e[r].streamItem=l);if(!t.value.some(w=>p===w.id)){await o();return}},m=async a=>{e[a].path="",delete e[a].deviceItem,delete e[a].streamItem,delete e[a].stream,await o()},u=()=>{e.forEach(a=>{var l;a.stream=(l=t.value.find(p=>p.id===a.path))==null?void 0:l.stream})};return{webrtc:!0,streamList:e,updateItem:g,delItem:m,resetStreamList:async a=>{e.length=0,e.push(...a),t.value.length=0,await o()},closeAll:()=>{c&&(c.disconnect(),c=null,e.length=0,t.value.length=0)}}}const Z=v({id:"app-group-store",state:()=>({groupTree:[],selectedChannel:[],curPlayGroupId:-2}),actions:{setCurPlayGroupId(n){this.curPlayGroupId=n},setGroupTree(n){this.groupTree=n},setSelChannel(n){this.selectedChannel=n},async fetchGroupTree(){const{data:n}=await U(-1),e=S(n);this.setGroupTree(e)}}});function S(n){return n.map(e=>{const t=S(e.children),i=e.channels.map(s=>({...s,key:`${s==null?void 0:s.channelId}_${s==null?void 0:s.deviceId}_${s.id}`,isChannel:!0})),o=[...t.map(s=>({...s})),...i];return{...e,key:`${(e==null?void 0:e.key)||e.id}`,children:o}})}export{Q as V,Z as a,Y as b,X as u};