From 17a9295982d9f9f92c1f283d1ef23577b4e219d8 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Mon, 10 Nov 2025 10:40:05 +0800 Subject: [PATCH 1/2] [fix] resolve display selection failure issue --- control.js | 63 ++++++++++++++++++++++++++++++++++++++---- index.html | 27 ++++++++++-------- manifest.json | 2 +- web_client.js | 76 +++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 138 insertions(+), 30 deletions(-) diff --git a/control.js b/control.js index b4b766e..8bc0516 100644 --- a/control.js +++ b/control.js @@ -205,9 +205,15 @@ } sendDisplayId(id) { + // 确保 id 是有效数字 + const numericId = typeof id === "number" && Number.isFinite(id) ? id : parseInt(id, 10); + if (isNaN(numericId) || !Number.isFinite(numericId)) { + console.warn("sendDisplayId: Invalid display_id:", id); + return; + } const action = { type: ControlType.display_id, - display_id: id | 0, + display_id: numericId | 0, }; this.send(action); } @@ -299,6 +305,11 @@ return; } + // Skip if clicking inside panel area + if (this.isInsidePanel(event.clientX, event.clientY)) { + return; + } + // Skip if dragging panel if (this.state.draggingPanel) { return; @@ -353,6 +364,11 @@ return; } + // Skip if moving inside panel area + if (this.isInsidePanel(event.clientX, event.clientY)) { + return; + } + // Skip if dragging panel if (this.state.draggingPanel) { return; @@ -451,6 +467,12 @@ } onPointerUp(event) { + // Skip if releasing inside panel area + if (this.isInsidePanel(event.clientX, event.clientY)) { + this.elements.video?.releasePointerCapture?.(event.pointerId ?? 0); + return; + } + // 移动端模式下,触摸结束不触发点击事件 if (this.state.isMobile && event.pointerType === "touch") { this.elements.video?.releasePointerCapture?.(event.pointerId ?? 0); @@ -485,6 +507,11 @@ if (now - this.state.lastWheelAt < 50) return; this.state.lastWheelAt = now; + // Skip if wheeling inside panel area + if (this.isInsidePanel(event.clientX, event.clientY)) { + return; + } + this.ensureVideoRect(); if (!this.state.videoRect) return; @@ -517,12 +544,18 @@ return; } + const touch = event.touches[0]; + + // Skip if touching inside panel area + if (this.isInsidePanel(touch.clientX, touch.clientY)) { + return; + } + // Skip if pinch zoom is active, dragging panel, or if two touches (pinch gesture) if (this.state.pinchZoomActive || this.state.draggingPanel || event.touches.length === 2) { return; } - const touch = event.touches[0]; event.preventDefault(); // 移动端模式下,触摸视频区域不触发点击事件 @@ -554,12 +587,18 @@ return; } + const touch = event.touches[0]; + + // Skip if moving inside panel area + if (this.isInsidePanel(touch.clientX, touch.clientY)) { + return; + } + // Skip if pinch zoom is active, dragging panel, or if two touches (pinch gesture) if (this.state.pinchZoomActive || this.state.draggingPanel || event.touches.length === 2) { return; } - const touch = event.touches[0]; event.preventDefault(); this.ensureVideoRect(); @@ -638,6 +677,18 @@ ); } + isInsidePanel(clientX, clientY) { + const panel = document.getElementById("connected-panel"); + if (!panel) return false; + const rect = panel.getBoundingClientRect(); + return ( + clientX >= rect.left && + clientX <= rect.right && + clientY >= rect.top && + clientY <= rect.bottom + ); + } + updateNormalizedFromClient(clientX, clientY) { if (!this.state.videoRect) return; this.state.normalizedPos = { @@ -1136,7 +1187,7 @@ if (keyboardToggleMouse) { keyboardToggleMouse.addEventListener("touchstart", (e) => { e.stopPropagation(); - }); + }, { passive: true }); keyboardToggleMouse.addEventListener("click", (e) => { e.stopPropagation(); }); @@ -1146,7 +1197,7 @@ if (this.elements.virtualMouseMinimize) { this.elements.virtualMouseMinimize.addEventListener("touchstart", (e) => { e.stopPropagation(); - }); + }, { passive: true }); this.elements.virtualMouseMinimize.addEventListener("click", (e) => { e.stopPropagation(); }); @@ -1174,7 +1225,7 @@ if (keyboardClose) { keyboardClose.addEventListener("touchstart", (e) => { e.stopPropagation(); - }); + }, { passive: true }); keyboardClose.addEventListener("click", (e) => { e.stopPropagation(); }); diff --git a/index.html b/index.html index 85cb0cb..1b946ac 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@ CrossDesk Web Client + -
- - -
-
- - -
-
- - -
+
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/manifest.json b/manifest.json index 909fd86..08e4b68 100644 --- a/manifest.json +++ b/manifest.json @@ -10,7 +10,7 @@ "icons": [ { "src": "favicon.ico", - "sizes": "any", + "sizes": "32x32", "type": "image/x-icon" }, { diff --git a/web_client.js b/web_client.js index 8911f3c..35b18aa 100644 --- a/web_client.js +++ b/web_client.js @@ -38,6 +38,8 @@ let pc = null; let clientId = "000000"; let heartbeatTimer = null; let lastPongAt = Date.now(); +let trackIndex = 0; // Track index for display_id (0, 1, 2, ...) +const trackMap = new Map(); // Map - stores tracks by their display_id index const websocket = new WebSocket(CONFIG.signalingUrl); @@ -219,7 +221,16 @@ function createPeerConnection() { peer.ontrack = ({ track, streams }) => { if (track.kind !== "video" || !elements.video) return; + + // Use track index as display_id (0, 1, 2, ...) + const currentIndex = trackIndex; + trackIndex++; + + // Store track in map + trackMap.set(currentIndex, track); + if (!elements.video.srcObject) { + // First track: create new stream const stream = streams && streams[0] ? streams[0] : new MediaStream([track]); elements.video.srcObject = stream; elements.video.muted = true; @@ -232,23 +243,37 @@ function createPeerConnection() { // Wait for first frame to be decoded before hiding connecting overlay hideConnectingOverlayOnFirstFrame(); } else { + // Additional track: add to existing stream elements.video.srcObject.addTrack(track); } if (!elements.displaySelect) return; - const trackId = track.id || ""; - if (!trackId) return; - + + // Remove placeholder option "候选画面 ID..." when first track arrives + if (currentIndex === 0) { + const placeholderOption = Array.from(elements.displaySelect.options).find( + (opt) => opt.value === "" + ); + if (placeholderOption) { + placeholderOption.remove(); + } + } + + // Check if option with this index already exists const existingOption = Array.from(elements.displaySelect.options).find( - (opt) => opt.value === trackId + (opt) => opt.value === String(currentIndex) ); if (!existingOption) { const option = document.createElement("option"); - option.value = trackId; - option.textContent = trackId; + option.value = String(currentIndex); + option.textContent = track.id || `Display ${currentIndex}`; elements.displaySelect.appendChild(option); } - elements.displaySelect.value = trackId; + // Only set default value for the first track (index 0) + // Don't auto-switch when additional tracks arrive + if (currentIndex === 0 && !elements.displaySelect.value) { + elements.displaySelect.value = String(currentIndex); + } }; peer.ondatachannel = (event) => { @@ -398,6 +423,12 @@ function disconnect() { updateStatus(elements.iceState, ""); updateStatus(elements.signalingState, ""); updateStatus(elements.dataChannelState, "closed"); + // Reset track index and clear display select options + trackIndex = 0; + trackMap.clear(); + if (elements.displaySelect) { + elements.displaySelect.innerHTML = ''; + } // Reset status LEDs and hide indicator updateStatusLed(elements.connectionStatusLed, false, true); updateStatusLed(elements.connectedStatusLed, false, false); @@ -497,10 +528,33 @@ function enableDataChannelUi(enabled) { function setDisplayId() { if (!elements.displaySelect) return; const raw = elements.displaySelect.value.trim(); - if (!raw) return; + if (!raw) { + // 如果值为空,不发送(保持原有行为) + return; + } const parsed = parseInt(raw, 10); - const numericValue = Number.isFinite(parsed) ? parsed : 0; - control.sendDisplayId(numericValue); + // 检查解析结果:如果解析失败(NaN)或者不是有效数字,不发送 + if (isNaN(parsed) || !Number.isFinite(parsed)) { + console.warn("setDisplayId: Invalid display_id value:", raw); + return; + } + + // Switch video track to the selected display_id + const selectedTrack = trackMap.get(parsed); + if (selectedTrack && elements.video) { + // Don't stop tracks - just replace the stream + // Stopping tracks makes them unusable + const newStream = new MediaStream([selectedTrack]); + elements.video.srcObject = newStream; + elements.video.muted = true; + 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 = true; + } + + control.sendDisplayId(parsed); } @@ -1142,7 +1196,7 @@ if (elements.connectedOverlay) { document.addEventListener("touchmove", onTouchMove); document.addEventListener("touchend", onTouchEnd); - }); + }, { passive: false }); } From fbcc4872827d6223efeef6085f30a49b4f17ba25 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Mon, 10 Nov 2025 15:09:51 +0800 Subject: [PATCH 2/2] [feat] enable audio stream autoplay --- index.html | 1 + web_client.js | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/index.html b/index.html index 1b946ac..fb525ef 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ x5-video-player-fullscreen="true" muted > +