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