From 3d80f627a8b1aaadfd48fca4669e88c43693131d Mon Sep 17 00:00:00 2001 From: yexuejc <1107047387@qq.com> Date: Wed, 4 Mar 2026 21:56:42 +0800 Subject: [PATCH] =?UTF-8?q?build(dist):=20=E6=9B=B4=E6=96=B0HTML=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E4=B8=BA=E5=8E=8B=E7=BC=A9=E7=89=88=E6=9C=AC=E7=9A=84?= =?UTF-8?q?JavaScript=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将control.js替换为control.min.js - 将web_client.js替换为web_client.min.js - 减少页面加载时的脚本文件大小 - 优化生产环境下的资源加载性能 --- control.min.js | 1 + index.html | 4 ++-- web_client.min.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 control.min.js create mode 100644 web_client.min.js diff --git a/control.min.js b/control.min.js new file mode 100644 index 0000000..c6bd82b --- /dev/null +++ b/control.min.js @@ -0,0 +1 @@ +(function(){const m={mouse:0,keyboard:1,audio_capture:2,host_infomation:3,display_id:4},n={move:0,left_down:1,left_up:2,right_down:3,right_up:4,middle_down:5,middle_up:6,wheel_vertical:7,wheel_horizontal:8},r=l=>Math.max(0,Math.min(1,l)),v=l=>{if(!l||!l.tagName)return!1;const t=l.tagName.toLowerCase();if(t==="textarea")return!0;if(t!=="input")return!1;const e=(l.getAttribute("type")||"text").toLowerCase();return!["checkbox","radio","button","submit","reset"].includes(e)};class T{constructor(){this.dataChannel=null,this.elements={video:document.getElementById("video"),mediaContainer:document.getElementById("media"),videoContainer:document.getElementById("video-container"),virtualMouse:document.getElementById("virtual-mouse"),virtualMouseHeader:document.getElementById("virtual-mouse-header"),virtualLeftBtn:document.getElementById("virtual-left-btn"),virtualRightBtn:document.getElementById("virtual-right-btn"),virtualScrollUp:document.getElementById("virtual-scroll-up"),virtualScrollDown:document.getElementById("virtual-scroll-down"),virtualTouchpad:null,virtualDragHandle:document.getElementById("virtual-mouse-drag-handle"),mobileModeSelector:document.getElementById("mobile-mode-selector"),mouseControlMode:document.getElementById("mouse-control-mode"),virtualKeyboard:document.getElementById("virtual-keyboard"),keyboardHeader:document.getElementById("keyboard-header"),keyboardToggleMouse:document.getElementById("keyboard-toggle-mouse"),keyboardToggle:document.getElementById("keyboard-toggle"),keyboardClose:document.getElementById("keyboard-close"),virtualMouseMinimize:document.getElementById("virtual-mouse-minimize"),virtualMouseRestore:document.getElementById("virtual-mouse-restore")},this.virtualKeyTimers=new Map,this.virtualScrollTimers=new Map,this.state={pointerLocked:!1,normalizedPos:{x:.5,y:.5},lastPointerPos:null,lastWheelAt:0,touchpadStart:null,draggingVirtualMouse:!1,dragOffset:{x:0,y:0},draggingVirtualKeyboard:!1,keyboardDragOffset:{x:0,y:0},draggingPanel:!1,pointerLockToastTimer:null,videoRect:null,gestureActive:!1,gestureButton:null,gestureStart:null,isMobile:!1,mobileControlMode:"absolute",touchActive:!1,touchStartPos:null,touchLastPos:null,pinchZoomActive:!1,initialPinchDistance:0,initialScale:1,currentScale:1,lastDoubleTapTime:0,initialPinchCenter:null,initialTranslateX:0,initialTranslateY:0,currentTranslateX:0,currentTranslateY:0,virtualMouseMinimized:!1},this.onPointerLockChange=this.onPointerLockChange.bind(this),this.onPointerLockError=this.onPointerLockError.bind(this),this.onPointerDown=this.onPointerDown.bind(this),this.onPointerMove=this.onPointerMove.bind(this),this.onPointerUp=this.onPointerUp.bind(this),this.onPointerCancel=this.onPointerCancel.bind(this),this.onWheel=this.onWheel.bind(this),this.onTouchStartFallback=this.onTouchStartFallback.bind(this),this.onTouchMoveFallback=this.onTouchMoveFallback.bind(this),this.onTouchEndFallback=this.onTouchEndFallback.bind(this),this.onVirtualLeftStart=this.onVirtualLeftStart.bind(this),this.onVirtualRightStart=this.onVirtualRightStart.bind(this),this.onVirtualButtonMove=this.onVirtualButtonMove.bind(this),this.onVirtualButtonEnd=this.onVirtualButtonEnd.bind(this),this.onDragHandleTouchStart=this.onDragHandleTouchStart.bind(this),this.onDragHandleTouchMove=this.onDragHandleTouchMove.bind(this),this.onDragHandleTouchEnd=this.onDragHandleTouchEnd.bind(this),this.onDragHandleClick=this.onDragHandleClick.bind(this),this.onKeyboardDragHandleTouchStart=this.onKeyboardDragHandleTouchStart.bind(this),this.onKeyboardDragHandleTouchMove=this.onKeyboardDragHandleTouchMove.bind(this),this.onKeyboardDragHandleTouchEnd=this.onKeyboardDragHandleTouchEnd.bind(this),this.onPinchStart=this.onPinchStart.bind(this),this.onPinchMove=this.onPinchMove.bind(this),this.onPinchEnd=this.onPinchEnd.bind(this),this.init()}init(){const{video:t}=this.elements;if(!t){console.warn("CrossDeskControl: video element not found");return}t.style.pointerEvents="auto",t.tabIndex=0,this.bindPointerLockEvents(),this.bindPointerListeners(),this.bindKeyboardListeners(),this.setupVirtualMouse(),this.setupVirtualKeyboard()}setDataChannel(t){this.dataChannel=t}isChannelOpen(){return this.dataChannel&&this.dataChannel.readyState==="open"}send(t){if(!this.isChannelOpen())return!1;try{const e=JSON.stringify(t);return this.dataChannel.send(e),!0}catch(e){return console.error("CrossDeskControl: failed to send action",e),!1}}sendMouseAction({x:t,y:e,flag:s,scroll:i=0}){if(this.isDraggingAnyElement())return;const o=typeof s=="string"?n[s]??n.move:s|0,a={type:m.mouse,mouse:{x:r(t),y:r(e),s:i|0,flag:o}};this.send(a)}sendKeyboardAction(t,e){const s={type:m.keyboard,keyboard:{key_value:t|0,flag:e?0:1}};this.send(s)}sendAudioCapture(t){const e={type:m.audio_capture,audio_capture:!!t};this.send(e)}sendDisplayId(t){const e=typeof t=="number"&&Number.isFinite(t)?t:parseInt(t,10);if(isNaN(e)||!Number.isFinite(e)){console.warn("sendDisplayId: Invalid display_id:",t);return}const s={type:m.display_id,display_id:e|0};this.send(s)}sendRawMessage(t){if(!this.isChannelOpen())return!1;try{return this.dataChannel.send(t),!0}catch(e){return console.error("CrossDeskControl: failed to send raw message",e),!1}}bindPointerLockEvents(){document.addEventListener("pointerlockchange",this.onPointerLockChange),document.addEventListener("pointerlockerror",this.onPointerLockError),document.addEventListener("keydown",t=>{t.ctrlKey&&t.key==="Escape"&&document.exitPointerLock?.()})}onPointerLockChange(){this.state.pointerLocked=document.pointerLockElement===this.elements.video,this.state.pointerLocked?this.state.videoRect=this.elements.video?.getBoundingClientRect()??null:(this.state.videoRect=null,this.showPointerLockToast("\u5DF2\u9000\u51FA\u9F20\u6807\u9501\u5B9A\uFF0C\u6309 Esc \u6216\u70B9\u51FB\u89C6\u9891\u91CD\u65B0\u9501\u5B9A\uFF08\u91CA\u653E\u53EF\u6309 Ctrl+Esc\uFF09",3e3))}onPointerLockError(){this.showPointerLockToast("\u9F20\u6807\u9501\u5B9A\u5931\u8D25",2500)}bindPointerListeners(){const{video:t}=this.elements;if(t){try{t.style.touchAction="none"}catch{}t.addEventListener("pointerdown",this.onPointerDown,{passive:!1}),document.addEventListener("pointermove",this.onPointerMove,{passive:!1}),document.addEventListener("pointerup",this.onPointerUp,{passive:!1}),document.addEventListener("pointercancel",this.onPointerCancel),t.addEventListener("wheel",this.onWheel,{passive:!1}),window.PointerEvent||(t.addEventListener("touchstart",this.onTouchStartFallback,{passive:!1}),document.addEventListener("touchmove",this.onTouchMoveFallback,{passive:!1}),document.addEventListener("touchend",this.onTouchEndFallback,{passive:!1}),document.addEventListener("touchcancel",this.onTouchEndFallback,{passive:!1}))}}onPointerDown(t){const e=typeof t.button=="number"?t.button:0;if(e<0)return;const s=t.target;if(!(s&&(s.closest("#panel-collapsed-bar")||s.closest("#connected-panel")))&&!this.isInsidePanel(t.clientX,t.clientY)&&!this.state.draggingPanel){if(this.state.isMobile&&t.pointerType==="touch"&&!this.state.pinchZoomActive){if(t.preventDefault?.(),this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(t.clientX,t.clientY)&&(this.state.mobileControlMode==="absolute"?(this.updateNormalizedFromClient(t.clientX,t.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})):(this.state.touchActive=!0,this.state.touchStartPos={x:t.clientX,y:t.clientY},this.state.touchLastPos={x:t.clientX,y:t.clientY})),this.elements.video&&t.pointerId!==void 0&&t.pointerId!==null)try{this.elements.video.setPointerCapture(t.pointerId)}catch{}return}if(t.preventDefault?.(),this.state.lastPointerPos={x:t.clientX,y:t.clientY},this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(t.clientX,t.clientY)&&this.requestPointerLock(),this.elements.video&&t.pointerId!==void 0&&t.pointerId!==null)try{this.elements.video.setPointerCapture(t.pointerId)}catch{}this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:this.buttonToFlag(e,!0)})}}onPointerMove(t){const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel"))||this.isInsidePanel(t.clientX,t.clientY)||this.state.draggingPanel||this.state.pinchZoomActive)return;if(this.state.isMobile&&t.pointerType==="touch"&&this.state.touchActive&&this.state.mobileControlMode==="relative"){if(t.preventDefault?.(),this.ensureVideoRect(),!this.state.videoRect||!this.state.touchLastPos)return;const h=t.clientX-this.state.touchLastPos.x,u=t.clientY-this.state.touchLastPos.y,d=h/this.state.videoRect.width,c=u/this.state.videoRect.height;this.state.normalizedPos.x=r(this.state.normalizedPos.x+d),this.state.normalizedPos.y=r(this.state.normalizedPos.y+c),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}),this.state.touchLastPos={x:t.clientX,y:t.clientY};return}if(this.state.isMobile&&t.pointerType==="touch"&&this.state.mobileControlMode==="absolute"){if(t.preventDefault?.(),this.ensureVideoRect(),!this.state.videoRect||!this.isInsideVideo(t.clientX,t.clientY))return;this.updateNormalizedFromClient(t.clientX,t.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move});return}if(!this.state.pointerLocked&&!this.state.lastPointerPos)return;const s=this.state.pointerLocked?t.movementX:t.clientX-(this.state.lastPointerPos?.x??t.clientX),i=this.state.pointerLocked?t.movementY:t.clientY-(this.state.lastPointerPos?.y??t.clientY);if(this.state.pointerLocked||(this.state.lastPointerPos={x:t.clientX,y:t.clientY}),this.ensureVideoRect(),!this.state.videoRect)return;if(this.state.pointerLocked){this.state.normalizedPos.x=r(this.state.normalizedPos.x+s/this.state.videoRect.width),this.state.normalizedPos.y=r(this.state.normalizedPos.y+i/this.state.videoRect.height),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move});return}if(!this.isInsideVideo(t.clientX,t.clientY))return;const o=(t.clientX-this.state.videoRect.left)/this.state.videoRect.width,a=(t.clientY-this.state.videoRect.top)/this.state.videoRect.height;this.state.normalizedPos={x:r(o),y:r(a)},this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})}onPointerUp(t){if(this.isInsidePanel(t.clientX,t.clientY)){this.elements.video?.releasePointerCapture?.(t.pointerId??0);return}if(this.state.isMobile&&t.pointerType==="touch"){this.elements.video?.releasePointerCapture?.(t.pointerId??0),this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null;return}const e=typeof t.button=="number"?t.button:0;this.elements.video?.releasePointerCapture?.(t.pointerId??0),this.state.lastPointerPos=null,this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:this.buttonToFlag(e,!1)})}onPointerCancel(){this.state.lastPointerPos=null,this.state.isMobile&&(this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null)}onWheel(t){const e=Date.now();if(e-this.state.lastWheelAt<50||(this.state.lastWheelAt=e,this.isInsidePanel(t.clientX,t.clientY))||(this.ensureVideoRect(),!this.state.videoRect))return;let s=this.state.normalizedPos;if(!this.state.pointerLocked){if(!this.isInsideVideo(t.clientX,t.clientY))return;s={x:(t.clientX-this.state.videoRect.left)/this.state.videoRect.width,y:(t.clientY-this.state.videoRect.top)/this.state.videoRect.height}}this.sendMouseAction({x:s.x,y:s.y,flag:t.deltaY===0?n.wheel_horizontal:n.wheel_vertical,scroll:t.deltaY||t.deltaX}),t.preventDefault()}onTouchStartFallback(t){if(!t.touches?.length)return;const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel")))return;const s=t.touches[0];this.isInsidePanel(s.clientX,s.clientY)||this.state.pinchZoomActive||this.state.draggingPanel||t.touches.length===2||(t.preventDefault(),this.ensureVideoRect(),this.state.videoRect&&this.isInsideVideo(s.clientX,s.clientY)&&(this.state.mobileControlMode==="absolute"?(this.updateNormalizedFromClient(s.clientX,s.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})):(this.state.touchActive=!0,this.state.touchStartPos={x:s.clientX,y:s.clientY},this.state.touchLastPos={x:s.clientX,y:s.clientY})))}onTouchMoveFallback(t){if(!t.touches?.length)return;const e=t.target;if(e&&(e.closest("#panel-collapsed-bar")||e.closest("#connected-panel")))return;const s=t.touches[0];if(!this.isInsidePanel(s.clientX,s.clientY)&&!(this.state.pinchZoomActive||this.state.draggingPanel||t.touches.length===2)&&(t.preventDefault(),this.ensureVideoRect(),!!this.state.videoRect)){if(this.state.mobileControlMode==="absolute")this.isInsideVideo(s.clientX,s.clientY)&&(this.updateNormalizedFromClient(s.clientX,s.clientY),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}));else if(this.state.touchActive&&this.state.touchLastPos){const i=s.clientX-this.state.touchLastPos.x,o=s.clientY-this.state.touchLastPos.y,a=i/this.state.videoRect.width,h=o/this.state.videoRect.height;this.state.normalizedPos.x=r(this.state.normalizedPos.x+a),this.state.normalizedPos.y=r(this.state.normalizedPos.y+h),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move}),this.state.touchLastPos={x:s.clientX,y:s.clientY}}}}onTouchEndFallback(t){this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null}buttonToFlag(t,e){const s={0:{down:n.left_down,up:n.left_up},1:{down:n.middle_down,up:n.middle_up},2:{down:n.right_down,up:n.right_up}},i=s[t]||s[0];return e?i.down:i.up}requestPointerLock(){try{this.elements.video?.requestPointerLock?.()}catch(t){console.warn("CrossDeskControl: requestPointerLock failed",t)}}ensureVideoRect(){const{video:t}=this.elements;t&&(this.state.videoRect=t.getBoundingClientRect())}isInsideVideo(t,e){const s=this.state.videoRect;return s?t>=s.left&&t<=s.right&&e>=s.top&&e<=s.bottom:!1}isInsidePanel(t,e){const s=document.getElementById("connected-panel");if(!s)return!1;const i=s.getBoundingClientRect();return t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}updateNormalizedFromClient(t,e){this.state.videoRect&&(this.state.normalizedPos={x:r((t-this.state.videoRect.left)/this.state.videoRect.width),y:r((e-this.state.videoRect.top)/this.state.videoRect.height)})}bindKeyboardListeners(){document.addEventListener("keydown",t=>{this.isChannelOpen()&&(t.repeat||v(t.target)||this.sendKeyboardAction(t.keyCode??0,!0))}),document.addEventListener("keyup",t=>{this.isChannelOpen()&&(v(t.target)||this.sendKeyboardAction(t.keyCode??0,!1))})}setupVirtualMouse(){const t=window.matchMedia("(hover: hover) and (pointer: fine)").matches;if(this.state.isMobile=!t,t){this.elements.virtualMouse&&(this.elements.virtualMouse.style.pointerEvents="none"),this.elements.mobileModeSelector&&(this.elements.mobileModeSelector.style.display="none"),this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.style.display="none");return}if(this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.style.display="flex"),this.elements.mobileModeSelector&&(this.elements.mobileModeSelector.style.display="flex"),this.elements.mouseControlMode&&(this.elements.mouseControlMode.addEventListener("change",e=>{this.state.mobileControlMode=e.target.value}),this.state.mobileControlMode=this.elements.mouseControlMode.value),this.elements.virtualLeftBtn?.addEventListener("touchstart",this.onVirtualLeftStart,{passive:!1}),this.elements.virtualRightBtn?.addEventListener("touchstart",this.onVirtualRightStart,{passive:!1}),document.addEventListener("touchmove",this.onVirtualButtonMove,{passive:!1}),document.addEventListener("touchend",this.onVirtualButtonEnd,{passive:!1}),document.addEventListener("touchcancel",this.onVirtualButtonEnd,{passive:!1}),this.elements.virtualScrollUp){const e=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollUp,"up",!0)},s=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollUp,"up",!1)};this.elements.virtualScrollUp.addEventListener("mousedown",e,{passive:!1}),this.elements.virtualScrollUp.addEventListener("mouseup",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("mouseleave",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchstart",e,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchend",s,{passive:!1}),this.elements.virtualScrollUp.addEventListener("touchcancel",s,{passive:!1})}if(this.elements.virtualScrollDown){const e=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollDown,"down",!0)},s=i=>{i.preventDefault(),this.handleVirtualScrollPress(this.elements.virtualScrollDown,"down",!1)};this.elements.virtualScrollDown.addEventListener("mousedown",e,{passive:!1}),this.elements.virtualScrollDown.addEventListener("mouseup",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("mouseleave",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchstart",e,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchend",s,{passive:!1}),this.elements.virtualScrollDown.addEventListener("touchcancel",s,{passive:!1})}this.bindVirtualMouseDragging(),this.bindVirtualKeyboardDragging(),this.elements.virtualMouseMinimize&&this.elements.virtualMouseMinimize.addEventListener("click",e=>{e.stopPropagation(),this.minimizeVirtualMouse()}),this.elements.virtualMouseRestore&&this.elements.virtualMouseRestore.addEventListener("click",e=>{e.stopPropagation(),this.restoreVirtualMouse()}),this.state.isMobile&&this.elements.video&&(this.elements.video.addEventListener("touchstart",this.onPinchStart,{passive:!1}),document.addEventListener("touchmove",this.onPinchMove,{passive:!1}),document.addEventListener("touchend",this.onPinchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onPinchEnd,{passive:!1}))}setupVirtualKeyboard(){window.matchMedia("(hover: hover) and (pointer: fine)").matches&&this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="none"),this.elements.keyboardToggleMouse&&(this.elements.keyboardToggleMouse.style.display="block",this.elements.keyboardToggleMouse.addEventListener("click",()=>{this.toggleVirtualKeyboard()})),this.elements.keyboardToggle&&this.elements.keyboardToggle.addEventListener("click",()=>{this.toggleVirtualKeyboard()}),this.elements.keyboardClose&&this.elements.keyboardClose.addEventListener("click",()=>{this.hideVirtualKeyboard()}),document.querySelectorAll(".keyboard-key").forEach(s=>{const i=a=>{a.preventDefault(),this.handleVirtualKeyPress(s,!0)},o=a=>{a.preventDefault(),this.handleVirtualKeyPress(s,!1),setTimeout(()=>{document.activeElement===s&&s.blur(),s.style.backgroundColor="",s.style.transform="",s.style.boxShadow=""},0)};s.addEventListener("mousedown",i),s.addEventListener("mouseup",o),s.addEventListener("mouseleave",o),s.addEventListener("touchstart",i,{passive:!1}),s.addEventListener("touchend",o,{passive:!1}),s.addEventListener("touchcancel",o,{passive:!1}),s._keyboardKeyRef=s})}toggleVirtualKeyboard(){if(!this.elements.virtualKeyboard)return;this.elements.virtualKeyboard.style.display!=="none"?this.hideVirtualKeyboard():this.showVirtualKeyboard()}showVirtualKeyboard(){this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="block")}hideVirtualKeyboard(){this.elements.virtualKeyboard&&(this.elements.virtualKeyboard.style.display="none")}handleVirtualKeyPress(t,e){if(!this.isChannelOpen())return;const s=parseInt(t.getAttribute("data-keycode"),10);if(!isNaN(s))if(e){this.stopVirtualKeyRepeat(t),this.sendKeyboardAction(s,!0),t.style.backgroundColor="rgba(180, 180, 180, 0.95)",t.style.transform="scale(0.92)",t.style.boxShadow="0 1px 2px rgba(0, 0, 0, 0.3)";const i={longPressTimer:null,repeatTimer:null};i.longPressTimer=setTimeout(()=>{i.repeatTimer=setInterval(()=>{this.isChannelOpen()&&(this.sendKeyboardAction(s,!0),setTimeout(()=>{this.sendKeyboardAction(s,!1)},30))},100)},300),this.virtualKeyTimers.set(t,i)}else this.stopVirtualKeyRepeat(t),this.sendKeyboardAction(s,!1),setTimeout(()=>{document.activeElement===t&&t.blur(),t.style.backgroundColor="",t.style.transform="",t.style.boxShadow="",t.style.outline=""},0)}stopVirtualKeyRepeat(t){const e=this.virtualKeyTimers.get(t);e&&(e.longPressTimer&&clearTimeout(e.longPressTimer),e.repeatTimer&&clearInterval(e.repeatTimer),this.virtualKeyTimers.delete(t))}emitVirtualWheel(t="up"){const e=t==="up"?-1:1;this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.wheel_vertical,scroll:e})}handleVirtualScrollPress(t,e,s){if(this.isChannelOpen())if(s){this.stopVirtualScrollRepeat(t),this.emitVirtualWheel(e),t.style.backgroundColor="rgba(180, 180, 180, 0.95)",t.style.transform="scale(0.92)",t.style.boxShadow="0 1px 2px rgba(0, 0, 0, 0.3)";const i={longPressTimer:null,repeatTimer:null};i.longPressTimer=setTimeout(()=>{i.repeatTimer=setInterval(()=>{this.isChannelOpen()&&this.emitVirtualWheel(e)},100)},300),this.virtualScrollTimers.set(t,i)}else this.stopVirtualScrollRepeat(t),setTimeout(()=>{document.activeElement===t&&t.blur(),t.style.backgroundColor="",t.style.transform="",t.style.boxShadow="",t.style.outline=""},0)}stopVirtualScrollRepeat(t){const e=this.virtualScrollTimers.get(t);e&&(e.longPressTimer&&clearTimeout(e.longPressTimer),e.repeatTimer&&clearInterval(e.repeatTimer),this.virtualScrollTimers.delete(t))}onVirtualLeftStart(t){const e=t.touches?.[0];e&&(t.preventDefault(),this.ensureVideoRect(),this.state.gestureActive=!0,this.state.gestureButton={down:n.left_down,up:n.left_up},this.state.gestureStart={x:e.clientX,y:e.clientY,normalizedX:this.state.normalizedPos.x,normalizedY:this.state.normalizedPos.y},this.elements.virtualLeftBtn&&(this.elements.virtualLeftBtn.style.backgroundColor="var(--primary-color)",this.elements.virtualLeftBtn.style.color="#fff"),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.left_down}))}onVirtualRightStart(t){const e=t.touches?.[0];e&&(t.preventDefault(),this.ensureVideoRect(),this.state.gestureActive=!0,this.state.gestureButton={down:n.right_down,up:n.right_up},this.state.gestureStart={x:e.clientX,y:e.clientY,normalizedX:this.state.normalizedPos.x,normalizedY:this.state.normalizedPos.y},this.elements.virtualRightBtn&&(this.elements.virtualRightBtn.style.backgroundColor="var(--primary-color)",this.elements.virtualRightBtn.style.color="#fff"),this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.right_down}))}onVirtualButtonMove(t){if(!this.state.gestureActive||!this.state.gestureStart)return;const e=t.touches?.[0];if(!e||(t.preventDefault(),this.ensureVideoRect(),!this.state.videoRect))return;const s=2,i=e.clientX-this.state.gestureStart.x,o=e.clientY-this.state.gestureStart.y,a=this.state.gestureStart.normalizedX+i/this.state.videoRect.width*s,h=this.state.gestureStart.normalizedY+o/this.state.videoRect.height*s;this.state.normalizedPos={x:r(a),y:r(h)},this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:n.move})}onVirtualButtonEnd(t){if(!this.state.gestureActive)return;t.preventDefault?.();const e=this.state.gestureButton?.up??n.left_up;this.sendMouseAction({x:this.state.normalizedPos.x,y:this.state.normalizedPos.y,flag:e}),e===n.left_up&&this.elements.virtualLeftBtn?(this.elements.virtualLeftBtn.style.backgroundColor="",this.elements.virtualLeftBtn.style.color="",document.activeElement===this.elements.virtualLeftBtn&&this.elements.virtualLeftBtn.blur()):e===n.right_up&&this.elements.virtualRightBtn&&(this.elements.virtualRightBtn.style.backgroundColor="",this.elements.virtualRightBtn.style.color="",document.activeElement===this.elements.virtualRightBtn&&this.elements.virtualRightBtn.blur()),this.state.gestureActive=!1,this.state.gestureButton=null,this.state.gestureStart=null}bindVirtualMouseDragging(){const{virtualMouse:t,virtualMouseHeader:e,videoContainer:s}=this.elements;if(!t||!e||!s)return;e.addEventListener("touchstart",this.onDragHandleTouchStart,{passive:!1}),document.addEventListener("touchmove",this.onDragHandleTouchMove,{passive:!1}),document.addEventListener("touchend",this.onDragHandleTouchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onDragHandleTouchEnd,{passive:!1});const i=document.getElementById("keyboard-toggle-mouse");i&&(i.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),i.addEventListener("click",o=>{o.stopPropagation()})),this.elements.virtualMouseMinimize&&(this.elements.virtualMouseMinimize.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),this.elements.virtualMouseMinimize.addEventListener("click",o=>{o.stopPropagation()}))}bindVirtualKeyboardDragging(){const{virtualKeyboard:t,keyboardHeader:e,keyboardClose:s,videoContainer:i}=this.elements;!t||!e||!i||(e.addEventListener("touchstart",this.onKeyboardDragHandleTouchStart,{passive:!1}),document.addEventListener("touchmove",this.onKeyboardDragHandleTouchMove,{passive:!1}),document.addEventListener("touchend",this.onKeyboardDragHandleTouchEnd,{passive:!1}),document.addEventListener("touchcancel",this.onKeyboardDragHandleTouchEnd,{passive:!1}),s&&(s.addEventListener("touchstart",o=>{o.stopPropagation()},{passive:!0}),s.addEventListener("click",o=>{o.stopPropagation()})))}onDragHandleTouchStart(t){const e=t.touches?.[0];if(!e||!this.elements.virtualMouse)return;const s=t.target;if(s&&(s.id==="keyboard-toggle-mouse"||s.closest("#keyboard-toggle-mouse")||s.id==="virtual-mouse-minimize"||s.closest("#virtual-mouse-minimize")))return;t.preventDefault();const i=this.elements.virtualMouse.getBoundingClientRect();this.state.draggingVirtualMouse=!0,this.elements.virtualMouse.classList.add("dragging"),this.state.dragOffset={x:e.clientX-i.left,y:e.clientY-i.top}}onDragHandleTouchMove(t){if(!this.state.draggingVirtualMouse)return;const e=t.touches?.[0];if(!e||!this.elements.videoContainer||!this.elements.virtualMouse)return;t.preventDefault();const s=this.elements.videoContainer.getBoundingClientRect();let i=e.clientX-this.state.dragOffset.x-s.left,o=e.clientY-this.state.dragOffset.y-s.top;const a=Math.max(0,s.width-this.elements.virtualMouse.offsetWidth),h=Math.max(0,s.height-this.elements.virtualMouse.offsetHeight);i=Math.max(0,Math.min(i,a)),o=Math.max(0,Math.min(o,h)),this.elements.virtualMouse.style.left=`${i}px`,this.elements.virtualMouse.style.top=`${o}px`,this.elements.virtualMouse.style.bottom="auto",this.elements.virtualMouse.style.transform="none"}onDragHandleTouchEnd(){this.state.draggingVirtualMouse=!1,this.elements.virtualMouse&&this.elements.virtualMouse.classList.remove("dragging")}onDragHandleClick(t){t.stopPropagation(),this.elements.virtualMouse?.classList.toggle("minimized")}onKeyboardDragHandleTouchStart(t){const e=t.touches?.[0];if(!e||!this.elements.virtualKeyboard)return;const s=t.target;if(s&&(s.id==="keyboard-close"||s.closest("#keyboard-close")))return;t.preventDefault();const i=this.elements.virtualKeyboard.getBoundingClientRect();this.state.draggingVirtualKeyboard=!0,this.elements.virtualKeyboard.classList.add("dragging"),this.state.keyboardDragOffset={x:e.clientX-i.left,y:e.clientY-i.top}}onKeyboardDragHandleTouchMove(t){if(!this.state.draggingVirtualKeyboard)return;const e=t.touches?.[0];if(!e||!this.elements.videoContainer||!this.elements.virtualKeyboard)return;t.preventDefault();const s=this.elements.videoContainer.getBoundingClientRect();let i=e.clientX-this.state.keyboardDragOffset.x-s.left,o=e.clientY-this.state.keyboardDragOffset.y-s.top;const a=Math.max(0,s.width-this.elements.virtualKeyboard.offsetWidth),h=Math.max(0,s.height-this.elements.virtualKeyboard.offsetHeight);i=Math.max(0,Math.min(i,a)),o=Math.max(0,Math.min(o,h)),this.elements.virtualKeyboard.style.left=`${i}px`,this.elements.virtualKeyboard.style.top=`${o}px`,this.elements.virtualKeyboard.style.bottom="auto",this.elements.virtualKeyboard.style.transform="none"}onKeyboardDragHandleTouchEnd(){this.state.draggingVirtualKeyboard=!1,this.elements.virtualKeyboard&&this.elements.virtualKeyboard.classList.remove("dragging")}showPointerLockToast(t,e=2500){let s=document.getElementById("pointerlock-toast");s||(s=document.createElement("div"),s.id="pointerlock-toast",Object.assign(s.style,{position:"fixed",left:"50%",bottom:"24px",transform:"translateX(-50%)",background:"rgba(0,0,0,0.75)",color:"#fff",padding:"8px 12px",borderRadius:"6px",fontSize:"13px",zIndex:"9999",pointerEvents:"none",opacity:"1",transition:"opacity 0.2s"}),document.body.appendChild(s)),s.textContent=t,s.style.opacity="1",this.state.pointerLockToastTimer&&clearTimeout(this.state.pointerLockToastTimer),this.state.pointerLockToastTimer=setTimeout(()=>{s.style.opacity="0",this.state.pointerLockToastTimer=null},e)}handleExternalMouseEvent(t){if(!(!t||!t.type)&&!this.isDraggingAnyElement())switch(t.type){case"mousedown":this.onPointerDown(t);break;case"mouseup":this.onPointerUp(t);break;case"mousemove":this.onPointerMove(t);break;case"wheel":this.onWheel(t);break;default:break}}isDraggingAnyElement(){return this.state.draggingVirtualMouse||this.state.draggingVirtualKeyboard||this.state.draggingPanel}setDraggingPanel(t){this.state.draggingPanel=t}getTouchDistance(t,e){const s=e.clientX-t.clientX,i=e.clientY-t.clientY;return Math.sqrt(s*s+i*i)}getTouchCenter(t,e){return{x:(t.clientX+e.clientX)/2,y:(t.clientY+e.clientY)/2}}onPinchStart(t){if(!(t.target!==this.elements.video&&!this.elements.video?.contains(t.target))){if(t.touches.length===2){t.preventDefault(),t.stopPropagation(),this.state.pinchZoomActive=!0;const e=t.touches[0],s=t.touches[1];this.state.initialPinchDistance=this.getTouchDistance(e,s),this.state.initialScale=this.state.currentScale,this.state.initialPinchCenter=this.getTouchCenter(e,s),this.state.initialTranslateX=this.state.currentTranslateX,this.state.initialTranslateY=this.state.currentTranslateY,this.state.touchActive=!1,this.state.touchStartPos=null,this.state.touchLastPos=null}else if(t.touches.length===1&&!this.state.pinchZoomActive){const e=Date.now();e-this.state.lastDoubleTapTime<300?(t.preventDefault(),this.resetZoom(),this.state.lastDoubleTapTime=0):this.state.lastDoubleTapTime=e}}}onPinchMove(t){if(t.touches.length===2){if(!this.state.pinchZoomActive){this.state.pinchZoomActive=!0;const d=t.touches[0],c=t.touches[1];this.state.initialPinchDistance=this.getTouchDistance(d,c),this.state.initialScale=this.state.currentScale,this.state.initialPinchCenter=this.getTouchCenter(d,c),this.state.initialTranslateX=this.state.currentTranslateX,this.state.initialTranslateY=this.state.currentTranslateY}t.preventDefault(),t.stopPropagation();const e=t.touches[0],s=t.touches[1],i=this.getTouchDistance(e,s),o=this.getTouchCenter(e,s),a=i/this.state.initialPinchDistance,h=this.state.initialScale*a,u=Math.max(1,Math.min(3,h));if(this.state.currentScale=u,this.state.initialPinchCenter){const d=o.x-this.state.initialPinchCenter.x,c=o.y-this.state.initialPinchCenter.y;let g=this.state.initialTranslateX+d,p=this.state.initialTranslateY+c;if(this.elements.video&&this.elements.videoContainer&&(this.ensureVideoRect(),this.state.videoRect)){const y=this.state.videoRect.width,b=this.state.videoRect.height,L=y*u,x=b*u,P=Math.max(0,(L-y)/2),M=Math.max(0,(x-b)/2);g=Math.max(-P,Math.min(P,g)),p=Math.max(-M,Math.min(M,p))}this.state.currentTranslateX=g,this.state.currentTranslateY=p}this.elements.video&&(this.elements.video.style.transform=`scale(${u}) translate(${this.state.currentTranslateX}px, ${this.state.currentTranslateY}px)`,this.elements.video.style.transformOrigin="center center")}else this.state.pinchZoomActive&&t.touches.length<2&&(this.state.pinchZoomActive=!1,this.state.initialPinchDistance=0,this.state.initialPinchCenter=null)}onPinchEnd(t){this.state.pinchZoomActive&&t.touches.length<2&&(this.state.pinchZoomActive=!1,this.state.initialPinchDistance=0,this.state.initialPinchCenter=null)}minimizeVirtualMouse(){if(!(!this.elements.virtualMouse||this.state.virtualMouseMinimized)&&(this.state.virtualMouseMinimized=!0,this.elements.virtualMouse.classList.add("minimized-to-statusbar"),this.elements.virtualMouse.style.display="none",this.elements.virtualMouseRestore)){this.elements.virtualMouseRestore.style.display="inline-flex";const t=document.querySelector(".connection-status-group");if(t&&this.elements.virtualMouseRestore.parentElement){const e=t.getBoundingClientRect(),s=this.elements.virtualMouseRestore.parentElement.getBoundingClientRect(),i=e.left-s.left-48;this.elements.virtualMouseRestore.style.left=`${i}px`,this.elements.virtualMouseRestore.style.right="auto",this.elements.virtualMouseRestore.style.top="0",this.elements.virtualMouseRestore.style.transform="none"}}}restoreVirtualMouse(){!this.elements.virtualMouse||!this.state.virtualMouseMinimized||(this.state.virtualMouseMinimized=!1,this.elements.virtualMouse.classList.remove("minimized-to-statusbar"),this.elements.virtualMouse.style.display="flex",this.elements.virtualMouseRestore&&(this.elements.virtualMouseRestore.style.display="none"))}resetZoom(){this.state.currentScale=1,this.state.initialScale=1,this.state.currentTranslateX=0,this.state.currentTranslateY=0,this.state.initialTranslateX=0,this.state.initialTranslateY=0,this.elements.video&&(this.elements.video.style.transform="scale(1) translate(0, 0)",this.elements.video.style.transformOrigin="center center")}}const f=new T;window.CrossDeskControl=f,window.sendRemoteActionAt=(l,t,e,s)=>f.sendMouseAction({x:l,y:t,flag:e,scroll:s}),window.sendMouseEvent=l=>f.handleExternalMouseEvent(l)})(); diff --git a/index.html b/index.html index f4995c2..1a6c3c0 100644 --- a/index.html +++ b/index.html @@ -281,7 +281,7 @@ - - + + diff --git a/web_client.min.js b/web_client.min.js new file mode 100644 index 0000000..c9ab194 --- /dev/null +++ b/web_client.min.js @@ -0,0 +1 @@ +const elements={iceState:document.getElementById("ice-connection-state"),signalingState:document.getElementById("signaling-state"),dataChannelState:document.getElementById("datachannel-state"),displaySelect:document.getElementById("display-id"),connectBtn:document.getElementById("connect"),disconnectBtn:document.getElementById("disconnect"),media:document.getElementById("media"),video:document.getElementById("video"),audio:document.getElementById("audio"),connectionOverlay:document.getElementById("connection-overlay"),connectedOverlay:document.getElementById("connected-overlay"),connectedPanel:document.getElementById("connected-panel"),panelCollapsedBar:document.getElementById("panel-collapsed-bar"),connectingOverlay:document.getElementById("connecting-overlay"),connectingMessageText:document.getElementById("connecting-message-text"),connectionStatusLed:document.getElementById("connection-status-led"),connectionStatusIndicator:document.getElementById("connection-status-indicator"),connectedStatusLed:document.getElementById("connected-status-led"),disconnectConnected:document.getElementById("disconnect-connected"),serverConfigBtn:document.getElementById("server-config-btn"),serverConfigContainer:document.getElementById("server-config-container"),signalingServerInput:document.getElementById("signaling-server"),stunTurnServerInput:document.getElementById("stun-turn-server")},DEFAULT_CONFIG={signalingUrl:"wss://192.168.0.107:33333",iceServers:[{urls:["stun:192.168.0.107:33334"]},{urls:["turn:192.168.0.107:33334"],username:"crossdesk",credential:"crossdeskpw"}],heartbeatIntervalMs:3e3,heartbeatTimeoutMs:1e4,reconnectDelayMs:2e3,clientTag:"web"},CONFIG=Object.assign({},DEFAULT_CONFIG,window.CROSSDESK_CONFIG||{});function extractSignalingHostPort(){try{return new URL(CONFIG.signalingUrl).host}catch{return"192.168.0.107:33333"}}function extractStunTurnHostPort(){if(CONFIG.iceServers&&CONFIG.iceServers.length>0){const e=CONFIG.iceServers[0];if(e.urls){const t=Array.isArray(e.urls)?e.urls:[e.urls];if(t.length>0){const o=t[0],n=o.match(/stun:(.+):([0-9]+)/)||o.match(/turn:(.+):([0-9]+)/);if(n)return`${n[1]}:${n[2]}`}}}return"192.168.0.107:33334"}function initServerConfigInputs(){elements.signalingServerInput&&(elements.signalingServerInput.value=extractSignalingHostPort()),elements.stunTurnServerInput&&(elements.stunTurnServerInput.value=extractStunTurnHostPort())}function checkConnectButtonEnabled(){if(!elements.connectBtn)return;const e=getTransmissionId(),t=getTransmissionPwd();if(!e||!t){elements.connectBtn.disabled=!0;return}if(elements.serverConfigContainer&&elements.serverConfigContainer.style.display!=="none"){const n=elements.signalingServerInput?.value.trim(),a=elements.stunTurnServerInput?.value.trim();n&&a?elements.connectBtn.disabled=!1:elements.connectBtn.disabled=!0}else elements.connectBtn.disabled=!1}function toggleServerConfig(){if(!elements.serverConfigContainer)return;elements.serverConfigContainer.style.display==="none"?(initServerConfigInputs(),elements.serverConfigContainer.style.display="block"):elements.serverConfigContainer.style.display="none",checkConnectButtonEnabled()}function updateServerConfig(e,t){return!e||!t?!1:(CONFIG.signalingUrl=`wss://${e}`,CONFIG.iceServers=[{urls:[`stun:${t}`]},{urls:[`turn:${t}`],username:"crossdesk",credential:"crossdeskpw"}],window.CROSSDESK_CONFIG=CONFIG,!0)}function saveServerConfig(){if(!elements.signalingServerInput||!elements.stunTurnServerInput)return;const e=elements.signalingServerInput.value.trim(),t=elements.stunTurnServerInput.value.trim();if(!e||!t){alert("\u8BF7\u586B\u5199\u5B8C\u6574\u7684\u670D\u52A1\u5668\u5730\u5740");return}updateServerConfig(e,t)&&(elements.serverConfigContainer&&(elements.serverConfigContainer.style.display="none"),setTimeout(()=>{window.location.reload()},500))}const control=window.CrossDeskControl;let pc=null,clientId="000000",heartbeatTimer=null,lastPongAt=Date.now(),trackIndex=0;const trackMap=new Map;let websocket=null;function initWebSocket(){websocket||(websocket=new WebSocket(CONFIG.signalingUrl),websocket.addEventListener("message",e=>{if(typeof e.data!="string")return;const t=JSON.parse(e.data);if(t.type==="pong"){lastPongAt=Date.now();return}handleSignalingMessage(t)}),websocket.addEventListener("open",()=>{enableConnectButton(!0),sendLogin(),startHeartbeat()}),websocket.addEventListener("close",()=>{stopHeartbeat(),enableConnectButton(!1)}),websocket.addEventListener("error",()=>{stopHeartbeat(),scheduleReconnect()}))}function handleSignalingMessage(e){switch(e.type){case"login":clientId=e.user_id.split("@")[0];break;case"user_join_transmission":if(e.status==="failed"){let t="";e.reason==="No such transmission id"?t="\u6CA1\u6709\u8BE5\u8BBE\u5907":e.reason==="Incorrect password"&&(t="\u5BC6\u7801\u9519\u8BEF"),t&&elements.connectingOverlay&&elements.connectingMessageText&&(elements.connectingMessageText.textContent=t,elements.connectingOverlay.style.display="flex",setTimeout(()=>{elements.connectingOverlay&&(elements.connectingOverlay.style.display="none"),disconnect()},3e3))}break;case"offer":handleOffer(e);break;case"new_candidate_mid":if(!pc)return;pc.addIceCandidate(new RTCIceCandidate({sdpMid:e.mid,candidate:e.candidate})).catch(t=>console.error("Error adding ICE candidate",t));break;default:break}}function startHeartbeat(){stopHeartbeat(),lastPongAt=Date.now(),heartbeatTimer=setInterval(()=>{websocket.readyState===WebSocket.OPEN&&websocket.send(JSON.stringify({type:"ping",ts:Date.now()})),Date.now()-lastPongAt>CONFIG.heartbeatTimeoutMs&&scheduleReconnect()},CONFIG.heartbeatIntervalMs)}function stopHeartbeat(){heartbeatTimer&&(clearInterval(heartbeatTimer),heartbeatTimer=null)}function scheduleReconnect(){try{websocket&&websocket.close()}catch{}setTimeout(()=>window.location.reload(),CONFIG.reconnectDelayMs)}function sendLogin(){websocket.send(JSON.stringify({type:"login",user_id:CONFIG.clientTag}))}function handleOffer(e){pc=createPeerConnection(),pc.setRemoteDescription(e).then(()=>sendAnswer(pc)).catch(t=>console.error("Failed to handle offer",t))}function createPeerConnection(){const e={iceServers:CONFIG.iceServers,iceTransportPolicy:"all"},t=new RTCPeerConnection(e);t.addEventListener("iceconnectionstatechange",()=>{const n=t.iceConnectionState;updateStatus(elements.iceState,n);const a=n==="connected";updateStatusLed(elements.connectionStatusLed,a,!0),updateStatusLed(elements.connectedStatusLed,a,!1),n==="disconnected"||n==="failed"?elements.connectingOverlay&&elements.connectingMessageText&&(n==="disconnected"?elements.connectingMessageText.textContent="\u8FDE\u63A5\u5DF2\u65AD\u5F00...":n==="failed"&&(elements.connectingMessageText.textContent="\u8FDE\u63A5\u5931\u8D25..."),elements.connectingOverlay.style.display="flex"):(n==="connected"||n==="checking"||n==="completed")&&elements.connectingOverlay&&(n==="connected"||n==="completed")&&(elements.connectingOverlay.style.display="none")}),updateStatus(elements.iceState,t.iceConnectionState);const o=t.iceConnectionState==="connected";return updateStatusLed(elements.connectionStatusLed,o,!0),updateStatusLed(elements.connectedStatusLed,o,!1),t.addEventListener("signalingstatechange",()=>{updateStatus(elements.signalingState,t.signalingState)}),updateStatus(elements.signalingState,t.signalingState),t.onicecandidate=({candidate:n})=>{n&&websocket.send(JSON.stringify({type:"new_candidate_mid",transmission_id:getTransmissionId(),user_id:clientId,remote_user_id:getTransmissionId(),candidate:n.candidate,mid:n.sdpMid}))},t.ontrack=({track:n,streams:a})=>{if(n.kind==="audio"&&elements.audio){if(elements.audio.srcObject)elements.audio.srcObject.addTrack(n);else{const i=a&&a[0]?a[0]:new MediaStream([n]);elements.audio.srcObject=i,elements.audio.autoplay=!0,elements.audio.play().catch(l=>{console.log("Audio autoplay prevented:",l)})}return}if(n.kind!=="video"||!elements.video)return;const c=trackIndex;if(trackIndex++,trackMap.set(c,n),elements.video.srcObject)elements.video.srcObject.addTrack(n);else{const i=a&&a[0]?a[0]:new MediaStream([n]);elements.video.srcObject=i,elements.video.muted=!0,elements.video.setAttribute("playsinline","true"),elements.video.setAttribute("webkit-playsinline","true"),elements.video.setAttribute("x5-video-player-type","h5"),elements.video.setAttribute("x5-video-player-fullscreen","true"),elements.video.autoplay=!0,hideConnectingOverlayOnFirstFrame()}if(!elements.displaySelect)return;if(c===0){const i=Array.from(elements.displaySelect.options).find(l=>l.value==="");i&&i.remove()}if(!Array.from(elements.displaySelect.options).find(i=>i.value===String(c))){const i=document.createElement("option");i.value=String(c),i.textContent=n.id||`Display ${c}`,elements.displaySelect.appendChild(i)}c===0&&!elements.displaySelect.value&&(elements.displaySelect.value=String(c))},t.ondatachannel=n=>{const a=n.channel;control.setDataChannel(a),bindDataChannel(a)},t}function bindDataChannel(e){e.addEventListener("open",()=>{updateStatus(elements.dataChannelState,"open"),enableDataChannelUi(!0)}),e.addEventListener("close",()=>{updateStatus(elements.dataChannelState,"closed"),enableDataChannelUi(!1),control.setDataChannel(null)}),e.addEventListener("message",t=>{})}async function sendAnswer(e){await e.setLocalDescription(await e.createAnswer()),await waitIceGathering(e),websocket.send(JSON.stringify({type:"answer",transmission_id:getTransmissionId(),user_id:clientId,remote_user_id:getTransmissionId(),sdp:e.localDescription.sdp}))}function waitIceGathering(e){return e.iceGatheringState==="complete"?Promise.resolve():new Promise(t=>{e.addEventListener("icegatheringstatechange",()=>{e.iceGatheringState==="complete"&&t()})})}function getTransmissionId(){return document.getElementById("transmission-id").value.trim()}function getTransmissionPwd(){return document.getElementById("transmission-pwd").value.trim()}function sendJoinRequest(){websocket.send(JSON.stringify({type:"join_transmission",user_id:clientId,transmission_id:`${getTransmissionId()}@${getTransmissionPwd()}`}))}function sendLeaveRequest(){websocket.send(JSON.stringify({type:"user_leave_transmission",user_id:clientId,transmission_id:getTransmissionId()}))}function connect(){if(!(!elements.connectBtn||!elements.disconnectBtn||!elements.media)){if(!websocket){const e=elements.signalingServerInput?.value.trim(),t=elements.stunTurnServerInput?.value.trim();if(elements.serverConfigContainer&&elements.serverConfigContainer.style.display!=="none"&&e&&t&&!updateServerConfig(e,t)){alert("\u670D\u52A1\u5668\u914D\u7F6E\u65E0\u6548");return}initWebSocket();return}websocket.readyState===WebSocket.OPEN&&(saveServerConfig(),elements.connectBtn.style.display="none",elements.disconnectBtn.style.display="inline-block",elements.media.style.display="flex",elements.connectionOverlay&&(elements.connectionOverlay.style.display="none"),elements.connectedOverlay&&(elements.connectedOverlay.style.display="block",elements.connectedPanel&&(isPanelMinimized=!1,panelAlignment="left",elements.connectedPanel.classList.remove("minimized"),elements.connectedPanel.style.left="0",elements.connectedPanel.style.right="auto",hideConnectedPanel())),elements.connectingOverlay&&(elements.connectingOverlay.style.display="flex"),elements.connectingMessageText&&(elements.connectingMessageText.textContent="\u8FDE\u63A5\u4E2D..."),sendJoinRequest())}}function disconnect(){!elements.connectBtn||!elements.disconnectBtn||!elements.media||(elements.disconnectBtn.style.display="none",elements.connectBtn.style.display="inline-block",elements.media.style.display="none",elements.connectionOverlay&&(elements.connectionOverlay.style.display="flex"),elements.connectedOverlay&&(elements.connectedOverlay.style.display="none"),elements.connectingOverlay&&(elements.connectingOverlay.style.display="none"),panelHideTimer&&(clearTimeout(panelHideTimer),panelHideTimer=null),isPanelMinimized=!1,isDragging=!1,panelAlignment="left",elements.connectedPanel&&(elements.connectedPanel.classList.remove("minimized"),elements.connectedPanel.style.left="0",elements.connectedPanel.style.right="auto"),sendLeaveRequest(),teardownPeerConnection(),enableDataChannelUi(!1),updateStatus(elements.iceState,""),updateStatus(elements.signalingState,""),updateStatus(elements.dataChannelState,"closed"),trackIndex=0,trackMap.clear(),elements.displaySelect&&(elements.displaySelect.innerHTML=''),updateStatusLed(elements.connectionStatusLed,!1,!0),updateStatusLed(elements.connectedStatusLed,!1,!1),websocket&&(websocket.close(),websocket=null))}function hideConnectingOverlayOnFirstFrame(){if(!elements.video||!elements.connectingOverlay)return;if(elements.video.requestVideoFrameCallback){let t=null;const o=()=>{elements.connectingOverlay&&(elements.connectingOverlay.style.display="none"),t!==null&&elements.video.cancelVideoFrameCallback(t)};t=elements.video.requestVideoFrameCallback(o);return}const e=()=>{elements.connectingOverlay&&(elements.connectingOverlay.style.display="none"),elements.video.removeEventListener("loadeddata",e),elements.video.removeEventListener("canplay",e)};elements.video.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA?e():(elements.video.addEventListener("loadeddata",e,{once:!0}),elements.video.addEventListener("canplay",e,{once:!0}))}function teardownPeerConnection(){if(pc){try{pc.getSenders().forEach(e=>e.track?.stop?.())}catch{}pc.close(),pc=null,elements.video?.srcObject&&(elements.video.srcObject.getTracks().forEach(e=>e.stop()),elements.video.srcObject=null),elements.audio?.srcObject&&(elements.audio.srcObject.getTracks().forEach(e=>e.stop()),elements.audio.srcObject=null)}}function updateStatus(e,t){e&&(e.textContent=t||"")}function updateStatusLed(e,t,o=!0){e&&(t?(e.classList.remove("status-led-off"),e.classList.add("status-led-on"),o&&elements.connectionStatusIndicator&&(elements.connectionStatusIndicator.style.display="flex")):(e.classList.remove("status-led-on"),e.classList.add("status-led-off"),o&&elements.connectionStatusIndicator&&(elements.connectionStatusIndicator.style.display="none")))}function enableConnectButton(e){elements.connectBtn&&(e?checkConnectButtonEnabled():elements.connectBtn.disabled=!0)}function enableDataChannelUi(e){elements.displaySelect&&(elements.displaySelect.disabled=!e)}function setDisplayId(){if(!elements.displaySelect)return;const e=elements.displaySelect.value.trim();if(!e)return;const t=parseInt(e,10);if(isNaN(t)||!Number.isFinite(t)){console.warn("setDisplayId: Invalid display_id value:",e);return}const o=trackMap.get(t);if(o&&elements.video){const n=new MediaStream([o]);elements.video.srcObject=n,elements.video.muted=!0,elements.video.setAttribute("playsinline","true"),elements.video.setAttribute("webkit-playsinline","true"),elements.video.setAttribute("x5-video-player-type","h5"),elements.video.setAttribute("x5-video-player-fullscreen","true"),elements.video.autoplay=!0}control.sendDisplayId(t)}elements.connectBtn&&elements.connectBtn.addEventListener("click",connect),elements.disconnectBtn&&elements.disconnectBtn.addEventListener("click",disconnect),elements.disconnectConnected&&elements.disconnectConnected.addEventListener("click",disconnect),elements.displaySelect&&elements.displaySelect.addEventListener("change",setDisplayId),elements.serverConfigBtn&&elements.serverConfigBtn.addEventListener("click",toggleServerConfig),elements.signalingServerInput&&elements.signalingServerInput.addEventListener("input",checkConnectButtonEnabled),elements.stunTurnServerInput&&elements.stunTurnServerInput.addEventListener("input",checkConnectButtonEnabled),document.getElementById("transmission-id")&&document.getElementById("transmission-id").addEventListener("input",checkConnectButtonEnabled),document.getElementById("transmission-pwd")&&document.getElementById("transmission-pwd").addEventListener("input",checkConnectButtonEnabled),checkConnectButtonEnabled();let panelHideTimer=null;const PANEL_HIDE_DELAY=3e3;let isPanelMinimized=!1,isDragging=!1,dragStartX=0,dragStartY=0,panelStartLeft=0,panelStartTop=0,panelAlignment="left",panelCorner="top-left";const SNAP_THRESHOLD=20;function calculateExpandPosition(e,t,o,n){const d=window.innerWidth,i=window.innerHeight;let l=e,r=t,s="auto",u="auto",f="left",g="top";return e+400>d&&(e-400>=0?(l=e-400+o,s="auto",f="right"):(l="0",s="auto",f="left")),t+100>i&&(t-100>=0?(r=t-100+n,u="auto",g="bottom"):(r="auto",u="0",g="bottom")),{left:l,top:r,right:s,bottom:u,horizontalAlign:f,verticalAlign:g}}function togglePanelMinimize(){if(elements.connectedPanel){if(isPanelMinimized=!isPanelMinimized,isPanelMinimized){const e=elements.panelCollapsedBar.getBoundingClientRect();let t=e.left,o=e.top;const n=window.innerWidth,a=window.innerHeight,c=48;t=Math.max(0,Math.min(t,n-c)),o=Math.max(0,Math.min(o,a-c)),elements.connectedPanel.classList.add("minimized"),elements.connectedPanel.style.left=`${t}px`,elements.connectedPanel.style.top=`${o}px`,elements.connectedPanel.style.right="auto",elements.connectedPanel.style.bottom="auto",elements.connectedPanel.offsetHeight}else{const e=elements.connectedPanel.getBoundingClientRect(),t=e.left,o=e.top,n=e.width,a=e.height;elements.connectedPanel.classList.remove("minimized");const c=calculateExpandPosition(t,o,n,a);requestAnimationFrame(()=>{const d=elements.connectedPanel.offsetWidth,i=elements.connectedPanel.offsetHeight,l=window.innerWidth,r=window.innerHeight;let s=t,u=o,f="auto",g="auto",m="top-left";t+d>l&&(s="auto",f=0),o+i>r&&(u="auto",g=0),s!=="auto"&&(s=Math.max(0,Math.min(s,l-d))),u!=="auto"&&(u=Math.max(0,Math.min(u,r-i))),panelCorner=m,elements.connectedPanel.style.left=typeof s=="number"?`${s}px`:s,elements.connectedPanel.style.top=typeof u=="number"?`${u}px`:u,elements.connectedPanel.style.right=f,elements.connectedPanel.style.bottom=g,updatePanelAlignment()})}panelHideTimer&&(clearTimeout(panelHideTimer),panelHideTimer=null)}}function minimizePanel(){if(!elements.connectedPanel||isPanelMinimized)return;const e=elements.panelCollapsedBar.getBoundingClientRect();let t=e.left,o=e.top;const n=window.innerWidth,a=window.innerHeight,c=48;t=Math.max(0,Math.min(t,n-c)),o=Math.max(0,Math.min(o,a-c)),isPanelMinimized=!0,elements.connectedPanel.classList.add("minimized"),elements.connectedPanel.style.left=`${t}px`,elements.connectedPanel.style.top=`${o}px`,elements.connectedPanel.style.right="auto",elements.connectedPanel.style.bottom="auto",elements.connectedPanel.offsetHeight,updatePanelAlignment()}function updatePanelAlignment(){if(!elements.connectedPanel)return;const e=elements.connectedPanel.getBoundingClientRect(),t=window.innerWidth,o=e.left;t-e.right{const c=elements.connectedPanel.offsetWidth,d=elements.connectedPanel.offsetHeight,i=window.innerWidth,l=window.innerHeight;let r=t,s=o,u="auto",f="auto",g="top-left";t+c>i&&(r="auto",u=0),o+d>l&&(s="auto",f=0),r!=="auto"&&(r=Math.max(0,Math.min(r,i-c))),s!=="auto"&&(s=Math.max(0,Math.min(s,l-d))),panelCorner=g,elements.connectedPanel.style.left=typeof r=="number"?`${r}px`:r,elements.connectedPanel.style.top=typeof s=="number"?`${s}px`:s,elements.connectedPanel.style.right=u,elements.connectedPanel.style.bottom=f,updatePanelAlignment()})}function showConnectedPanel(){elements.connectedPanel&&(maximizePanel(),panelHideTimer&&(clearTimeout(panelHideTimer),panelHideTimer=null))}function hideConnectedPanel(){elements.connectedPanel&&(panelHideTimer=setTimeout(()=>{elements.connectedPanel&&!isPanelMinimized&&minimizePanel()},PANEL_HIDE_DELAY))}function startDrag(e){if(!elements.connectedPanel)return;isDragging=!0,control&&control.setDraggingPanel&&control.setDraggingPanel(!0);const t=e.touches?e.touches[0].clientX:e.clientX,o=e.touches?e.touches[0].clientY:e.clientY;dragStartX=t,dragStartY=o;const n=elements.connectedPanel.getBoundingClientRect();panelStartLeft=n.left,panelStartTop=n.top,e.preventDefault(),document.addEventListener("mousemove",onDrag),document.addEventListener("mouseup",stopDrag),document.addEventListener("touchmove",onDrag),document.addEventListener("touchend",stopDrag)}function onDrag(e){if(!isDragging||!elements.connectedPanel)return;e.preventDefault(),e.stopPropagation();const t=e.touches?e.touches[0].clientX:e.clientX,o=e.touches?e.touches[0].clientY:e.clientY,n=t-dragStartX,a=o-dragStartY,c=panelStartLeft+n,d=panelStartTop+a,i=elements.connectedPanel.offsetWidth,l=elements.connectedPanel.offsetHeight,r=window.innerWidth-i,s=window.innerHeight-l,u=Math.max(0,Math.min(c,r)),f=Math.max(0,Math.min(d,s));elements.connectedPanel.style.left=`${u}px`,elements.connectedPanel.style.top=`${f}px`,elements.connectedPanel.style.right="auto",elements.connectedPanel.style.bottom="auto";const g=window.innerWidth,m=u;g-u-i{t.clientY<=80?showConnectedPanel():!elements.connectedPanel?.matches(":hover")&&!isPanelMinimized&&hideConnectedPanel()}),elements.connectedOverlay.addEventListener("mouseleave",()=>{isPanelMinimized||hideConnectedPanel()}),elements.connectedPanel&&(elements.connectedPanel.addEventListener("mouseenter",()=>{isPanelMinimized||showConnectedPanel()}),elements.connectedPanel.addEventListener("mouseleave",()=>{isPanelMinimized||hideConnectedPanel()})),elements.panelCollapsedBar){let t=!1,o=0,n={x:0,y:0};elements.panelCollapsedBar.addEventListener("mousedown",a=>{a.stopPropagation(),a.preventDefault(),control&&control.setDraggingPanel&&control.setDraggingPanel(!0),t=!1,o=Date.now(),n.x=a.clientX,n.y=a.clientY;const c=i=>{i.stopPropagation();const l=Math.abs(i.clientX-n.x),r=Math.abs(i.clientY-n.y);(l>5||r>5)&&(t=!0,startDrag(i),document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",d))},d=i=>{i.stopPropagation(),document.removeEventListener("mousemove",c),document.removeEventListener("mouseup",d);const l=Date.now()-o,r=Math.abs(i.clientX-n.x),s=Math.abs(i.clientY-n.y);!t&&l<300&&r<=5&&s<=5?(control&&control.setDraggingPanel&&control.setDraggingPanel(!1),isPanelMinimized?togglePanelMinimize():minimizePanel()):control&&control.setDraggingPanel&&control.setDraggingPanel(!1),t=!1};document.addEventListener("mousemove",c),document.addEventListener("mouseup",d)}),elements.panelCollapsedBar.addEventListener("touchstart",a=>{a.stopPropagation(),a.preventDefault(),control&&control.setDraggingPanel&&control.setDraggingPanel(!0),t=!1,o=Date.now(),n.x=a.touches[0].clientX,n.y=a.touches[0].clientY;const c=i=>{i.stopPropagation();const l=Math.abs(i.touches[0].clientX-n.x),r=Math.abs(i.touches[0].clientY-n.y);(l>5||r>5)&&(t=!0,startDrag(i),document.removeEventListener("touchmove",c),document.removeEventListener("touchend",d))},d=i=>{i.stopPropagation(),document.removeEventListener("touchmove",c),document.removeEventListener("touchend",d);const l=Date.now()-o,r=i.changedTouches&&i.changedTouches[0]?i.changedTouches[0].clientX:n.x,s=i.changedTouches&&i.changedTouches[0]?i.changedTouches[0].clientY:n.y,u=Math.abs(r-n.x),f=Math.abs(s-n.y);!t&&l<300&&u<=5&&f<=5?(control&&control.setDraggingPanel&&control.setDraggingPanel(!1),isPanelMinimized?togglePanelMinimize():minimizePanel()):control&&control.setDraggingPanel&&control.setDraggingPanel(!1),t=!1};document.addEventListener("touchmove",c),document.addEventListener("touchend",d)},{passive:!1})}elements.video&&elements.video.addEventListener("click",t=>{(t.clientY<=80||t.target===elements.video)&&(isPanelMinimized?togglePanelMinimize():(showConnectedPanel(),hideConnectedPanel()))})}window.connect=connect,window.disconnect=disconnect,window.setDisplayId=setDisplayId,document.addEventListener("copy",e=>(e.preventDefault(),e.clipboardData.setData("text/plain",""),!1)),document.addEventListener("cut",e=>(e.preventDefault(),e.clipboardData.setData("text/plain",""),!1)),document.addEventListener("paste",e=>{const t=e.target;if(!(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA")))return e.preventDefault(),!1}),document.addEventListener("contextmenu",e=>{const t=e.target;if(!(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA")))return e.preventDefault(),!1}),document.addEventListener("selectstart",e=>{const t=e.target;if(!(t&&(t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.tagName==="SELECT")))return e.preventDefault(),!1}),document.addEventListener("dragstart",e=>(e.preventDefault(),!1));