1 |
- var Q=Object.defineProperty;var ee=(n,e,t)=>e in n?Q(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var v=(n,e,t)=>(ee(n,typeof e!="symbol"?e+"":e,t),t);import{d as H,k as E,s as te,o as B,x as V,X as g,a6 as _,t as se,A as ie,f as w,u as h,a9 as re,P as ae,r as j,e as O,w as A,Y as C,aa as N,Z as y,E as x,F as W,a2 as ne,a7 as oe}from"./vue-bbe2430e.js";import{K as J,bu as ce,ac as le,V as q,aJ as he,s as pe,G as U,bv as de,b2 as ue,a0 as T,U as F,bj as me,a3 as D,bk as ge}from"./radical-08b8d2dc.js";import{j as fe}from"./gb28181-745bb8c1.js";const ve=J({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}}}),we={class:"video-container"},Se=["id","srcObject"],be=H({__name:"batch",props:{path:{},stream:{}},setup(n){const e=E();te(()=>{e.value&&(e.value.addEventListener("playing",()=>{}),e.value.addEventListener("pause",()=>{}),e.value.addEventListener("ended",()=>{}),e.value.addEventListener("error",()=>{}))});const t=E(!0);return B(()=>{ce(e.value,"canplay",()=>{t.value=!1})}),V(()=>{}),(s,a)=>(g(),_("div",we,[se(w(h(le),{class:"loading",size:"large"},null,512),[[ie,t.value]]),re("video",{ref_key:"videoEle",ref:e,class:"video",id:"video-"+s.path,srcObject:s.stream,autoplay:"",playsinline:""},null,8,Se),ae(s.$slots,"default",{},void 0,!0)]))}});const z=q(be,[["__scopeId","data-v-b2e00bc2"]]);class ye{constructor(e){v(this,"ws",null);v(this,"pc",null);v(this,"localStream",null);v(this,"subscribedStreams",new Set);v(this,"videoSenders",new Map);v(this,"streamToTransceiver",new Map);v(this,"eventListeners",new Map);v(this,"wsUrl");v(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 s={iceTransportPolicy:"all",bundlePolicy:"max-bundle",rtcpMuxPolicy:"require",iceCandidatePoolSize:1};this.pc=new RTCPeerConnection(s);const a=this.pc.addTransceiver("video",{direction:"sendrecv"});this.videoSenders.set("placeholder",a.sender),this.log("已向 PeerConnection 添加占位轨道","info"),this.setupPeerConnectionEventHandlers();const i=await this.pc.createOffer();await this.pc.setLocalDescription(i),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=s=>{this.log(`WebSocket 错误: ${s}`,"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],s=this.videoSenders.get("placeholder");s&&(await s.replaceTrack(t),this.log("已用真实轨道替换占位视频轨道","success")),this.videoSenders.delete("placeholder"),this.videoSenders.set(e,s);const a=await this.pc.createOffer();return await this.pc.setLocalDescription(a),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(a=>a.stop()),this.localStream=null);const s=await this.pc.createOffer();return await this.pc.setLocalDescription(s),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(i=>{i&&this.subscribedStreams.add(i)});const s=[];t.forEach(i=>{if(!this.subscribedStreams.has(i)){const u=this.streamToTransceiver.get(i);u&&(u.direction="inactive",this.log(`已将移除流 ${i} 的转接器设置为 inactive`,"info"),this.streamToTransceiver.delete(i)),s.push(i),this.emit("streamRemoved",{streamPath:i})}}),s.length>0&&await this.sendUnsubscribeSignal(s);const a=Array.from(this.subscribedStreams).filter(i=>!t.has(i));if(this.log(`新流路径: ${a.join(", ")}`,"info"),a.length>0){const i=this.pc.getTransceivers().filter(S=>S.direction==="inactive");this.log(`可用转接器数量: ${i.length}`,"info");const u=[...a];for(;u.length>0&&i.length>0;)u.pop(),i.pop().direction="recvonly";const m=u.length;if(m>0){this.log(`添加 ${m} 个新视频转接器`,"info");for(let S=0;S<m;S++)this.pc.addTransceiver("video",{direction:"recvonly"})}const I=await this.pc.createOffer();await this.pc.setLocalDescription(I),this.sendMessage({type:"subscribe",streamList:a,offer:this.pc.localDescription.sdp}),this.log(`订阅了 ${a.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 s=this.eventListeners.get(e),a=s.indexOf(t);a!==-1&&s.splice(a,1)}emit(e,t){if(!this.eventListeners.has(e))return;const s=this.eventListeners.get(e);for(const a of s)a(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 s={};e.track.onunmute=()=>{this.log(`轨道已 unmute: ${e.track.kind}/${e.track.id}`,"success")};const a=setInterval(async()=>{if(!this.pc||this.pc.connectionState!=="connected"){this.log("连接状态变更,停止统计收集","info"),clearInterval(a);return}try{(await this.pc.getStats(e.track)).forEach(u=>{if(u.type==="inbound-rtp"&&u.kind===e.track.kind){const m=u.packetsReceived||0;(s[e.track.id]||0)!==m&&(s[e.track.id]=m)}})}catch(i){this.log(`获取统计信息错误: ${i.message}`,"error")}},5e3);if(e.track.kind==="video"&&e.streams[0]){const i=e.streams[0].id;this.streamToTransceiver.set(i,t),this.emit("streamAdded",{streamId:i,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 s=new RTCSessionDescription({type:"answer",sdp:t.sdp});await this.pc.setRemoteDescription(s),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 s=()=>{this.pc.iceGatheringState==="complete"&&(this.pc.removeEventListener("icegatheringstatechange",s),t())};this.pc.addEventListener("icegatheringstatechange",s)}}),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 p=null;function Ie(n=!1){const e=j([]),t=E([]),s=()=>e.map(c=>c.path),a=async()=>{if(await u(),!!p)try{await p.subscribeToStreams(s()),`${s().length}`}catch{}},i=function(c){let r=String(c.getMilliseconds());return"padStart"in String.prototype&&(r=r.toString().padStart(3,"0")),`${c.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/,"$1")}:${r}`},u=async()=>new Promise(async c=>{if(p)return c();p=new ye("/media"),p.on("log",r=>console[["info","debug","warn","error"].indexOf(r.level)!==-1?r.level:"info"](i(r.time),r.message)),p.on("error",r=>console.log(`客户端错误: ${r.message}`)),p.on("connected",()=>{c()}),p.on("disconnected",()=>{p=null;const r=e.length;e.length=0,e.push(...new Array(r).fill(0).map(()=>({path:"",stream:void 0}))),t.value.length=0}),p.on("publishStarted",r=>{`${r.streamPath}`}),p.on("publishStopped",r=>{`${r.streamPath}`}),p.on("streamList",r=>{`${r.streams.length}`,r.streams.length===0&&(e.length=0)}),p.on("streamAdded",r=>{`${r.streamId}`,e.some(f=>f.path===r.streamId)&&(t.value.push({id:r.streamId,stream:r.stream}),S())}),p.on("streamRemoved",r=>{`${r.streamId}`});try{await p.connect()}catch{p=null}}),m=async({deviceItem:c,streamItem:r,path:b},f)=>{if(n&&c){if(!c.parentId||!c.deviceId){console.error("GB28181设备信息不完整,无法更新流");return}e[f].path=b,e[f].deviceItem=c}else r&&(e[f].path=b,e[f].streamItem=r);if(!t.value.some(R=>b===R.id)){await a();return}},I=async c=>{e[c].path="",delete e[c].deviceItem,delete e[c].streamItem,delete e[c].stream,await a()},S=()=>{e.forEach(c=>{var r;c.stream=(r=t.value.find(b=>b.id===c.path))==null?void 0:r.stream})};return{webrtc:!0,streamList:e,updateItem:m,delItem:I,resetStreamList:async c=>{e.length=0,e.push(...c),t.value.length=0,await a()},closeAll:()=>{p&&(p.disconnect(),p=null,e.length=0,t.value.length=0)}}}const ke=J({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 fe(-1),e=K(n);this.setGroupTree(e)}}});function K(n){return n.map(e=>{const t=K(e.children),s=e.channels.map(i=>({...i,key:`${i==null?void 0:i.channelId}_${i==null?void 0:i.deviceId}_${i.id}`,isChannel:!0})),a=[...t.map(i=>({...i})),...s];return{...e,key:`${(e==null?void 0:e.key)||e.id}`,children:a}})}const Ce={class:"text-center"},$e={key:1,class:"video-player-placeholder"},Te=H({__name:"content",props:{type:{default:"device"}},setup(n){const e=n,t=ke(),s=ve(),a=O({get:()=>s.curScreenNum,set:l=>s.setCurScreenNum(l)}),i=[{label:"单屏",value:1},{label:"四分屏",value:4},{label:"九分屏",value:9},{label:"十六分屏",value:16}],u=O(()=>{switch(a.value){case 1:return 24;case 4:return 12;case 9:return 8;case 16:return 6;default:return 24}}),m=()=>document.querySelector(".screen-list"),I=E(null),{toggle:S,isFullscreen:L}=he(I),k=E(!1),c=()=>{try{if(typeof RTCRtpReceiver<"u"&&RTCRtpReceiver.getCapabilities){const l=RTCRtpReceiver.getCapabilities("video");l&&l.codecs&&l.codecs.some(o=>o.mimeType.toLowerCase().includes("h265")||o.mimeType.toLowerCase().includes("hevc"))||(k.value=!0)}else k.value=!0}catch(l){console.warn("检测 H265 支持时发生错误:",l),k.value=!0}};B(()=>{c()});const r=Ie(e.type==="device");s.setMultiPlayRef(r);const{updateItem:b,streamList:f,resetStreamList:R,closeAll:X,webrtc:Y}=r,$=j({selectNum:1});A(a,async()=>{$.selectNum=0,e.type==="device"?(s.setActiveInfo({parentId:"",parentName:"",deviceId:"",name:""}),t.setCurPlayGroupId(-2)):s.setActiveInfo({path:"",type:""});const l=new Array(a.value).fill({}).map(()=>e.type==="device"?{path:"",deviceItem:{},stream:void 0}:{path:"",type:"",streamItem:{},stream:void 0});await R(l)},{immediate:!0});let G=!1;const Z=(l,d)=>{if($.selectNum=d,e.type!=="stream"&&e.type==="device"&&l.deviceItem&&"deviceId"in l.deviceItem){const o=l.deviceItem;s.setActiveInfo({parentId:o.parentId??"",deviceId:o.deviceId??"",parentName:o.parentName??"",name:o.name??""}),G=!0}};V(()=>X());const{getActiveInfo:P}=pe(s);return A(()=>{var l;return(l=P.value)==null?void 0:l.deviceId},()=>{if(e.type==="device"){const l=P.value,d=`${l.parentId}/${l.deviceId}`;if(f.some(o=>o.path===d)&&!G)return U.warn("当前设备已在分屏中!");b({path:d,deviceItem:l},$.selectNum)}G=!1}),A(()=>P.value.path,l=>{const d=P.value;if(e.type==="stream"&&l){if(f.some(o=>o.path===d.path))return U.warn("当前设备流已在分屏中!");b({path:d.path,streamItem:d},$.selectNum)}}),(l,d)=>(g(),_("div",Ce,[k.value?(g(),C(h(de),{key:0,type:"warning",message:"当前浏览器不支持 H265 解码",description:"如需观看 H265 视频请使用最新版的 Chrome 浏览器,确保显卡驱动已安装最新版本","show-icon":"",closable:"",class:"mb-12px",onClose:d[0]||(d[0]=o=>k.value=!1)})):N("",!0),w(h(ue),{value:a.value,"onUpdate:value":d[1]||(d[1]=o=>a.value=o),buttonStyle:"solid",options:i,optionType:"button"},null,8,["value"]),w(h(F),{onClick:h(S)},{default:y(()=>[w(h(T),{icon:"ant-design:fullscreen-outlined",class:"v-text-bottom"}),d[2]||(d[2]=x("全屏 "))]),_:1},8,["onClick"]),w(h(ge),{gutter:4,class:"screen-list mt-10px",ref_key:"screenRef",ref:I},{default:y(()=>[h(L)?(g(),C(h(F),{key:0,onClick:h(S),type:"link",class:"absolute right-2 top-2 z-10 text-white"},{default:y(()=>[w(h(T),{icon:"ant-design:fullscreen-exit-outlined",class:"v-text-bottom"}),d[3]||(d[3]=x(" 退出全屏 "))]),_:1},8,["onClick"])):N("",!0),(g(!0),_(W,null,ne(h(f),(o,M)=>(g(),C(h(me),{span:u.value,key:M,class:oe(["mt-4px flex-center",{active:a.value>1&&$.selectNum===M&&!h(L),isFullscreen:h(L)}]),onClick:()=>Z(o,M)},{default:y(()=>["path"in o&&o.path?(g(),_(W,{key:0},[h(Y)?(g(),C(z,{key:0,path:o.path,stream:o.stream},{default:y(()=>[o.path?(g(),C(h(D),{key:0,class:"position-absolute bottom-0 left-50% cursor-pointer",title:o.path,getPopupContainer:m},{default:y(()=>[w(h(T),{icon:"octicon:ellipsis-16",size:20,color:"#dedede",hoverColor:"#a275d9"})]),_:2},1032,["title"])):N("",!0)]),_:2},1032,["path","stream"])):(g(),C(z,{key:1,format:"ws-flv",path:o.path,stream:o.stream},{default:y(()=>[o.path?(g(),C(h(D),{key:0,class:"position-absolute bottom-0 left-50% cursor-pointer",title:o.path,getPopupContainer:m},{default:y(()=>[w(h(T),{icon:"octicon:ellipsis-16",size:20,color:"#dedede",hoverColor:"#a275d9"})]),_:2},1032,["title"])):N("",!0)]),_:2},1032,["path","stream"]))],64)):(g(),_("div",$e,[w(h(D),{title:"当前无信号,请先选择通道",class:"no-stream-tip cursor-pointer",getPopupContainer:m,placement:"top"},{default:y(()=>[w(h(T),{icon:"pepicons-pop:television-play-off",size:a.value===1?50:24},null,8,["size"])]),_:1})]))]),_:2},1032,["span","class","onClick"]))),128))]),_:1},512)]))}});const Ne=q(Te,[["__scopeId","data-v-97a02355"]]);export{Ne as C,ke as a,ve as u};
|