From 9b0e105647d31fee3b7e13e2d6c4d108ae81a70f Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Tue, 4 Aug 2026 02:40:23 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20WebRTC=20=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E7=A8=B3=E5=AE=9A=E6=80=A7=E4=B8=8E=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E9=87=8D=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复安卓端 401 拦截器线程问题,确保 Toast 在主线程执行 - 修复 Web 端按键/触摸重复触发:增加按下状态跟踪,防止悬停触发动作和长按重复 - 修复 Web 端指针取消时未释放触摸状态导致被控端卡死 - 修复 TURN 未启用时返回 401 导致客户端异常登出,改为返回 enabled=false - 修复后台管理端 401 后路由守卫跳转失败 - 增加控制指令调试日志用于排查问题 --- .../activity/main/MainActivity.java | 13 ++++++++---- .../src/components/ControlBar.vue | 18 +++++++++++++++- .../src/components/RemoteScreen.vue | 20 +++++++++++++++--- .../src/services/WebRtcController.js | 21 +++++++++++++++++++ WebRTCSignalServer/docs/CLIENT_API.md | 4 +++- .../controller/ClientController.java | 15 ++++++++++++- WebRTCSignalServerWeb/src/api/request.ts | 8 ++++--- 7 files changed, 86 insertions(+), 13 deletions(-) diff --git a/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java b/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java index b4b4fe1..681e85f 100644 --- a/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java +++ b/WebRTCController/app/src/main/java/com/ttstd/controller/activity/main/MainActivity.java @@ -214,10 +214,11 @@ public class MainActivity extends BaseMvvmActivity 直接回登录页(与 WebSocket 4001/4003 行为一致)。 - UnauthorizedInterceptor.setHandler(msg -> { + // 注意:该回调运行在 OkHttp 拦截器线程,必须切回主线程再操作 Toast / UI。 + UnauthorizedInterceptor.setHandler(msg -> runOnUiThread(() -> { Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show(); logoutAndReset(); - }); + })); // 令牌就绪 -> 建立信令 WebSocket viewModel.getAccessToken().observe(this, token -> { @@ -303,7 +304,9 @@ public class MainActivity extends BaseMvvmActivity items) { for (BindingItem item : items) { int idx = -1; @@ -323,7 +326,9 @@ public class MainActivity extends BaseMvvmActivity Boolean.compare(b.isOnline(), a.isOnline())); } - /** 设备列表为空时显示占位提示。 */ + /** + * 设备列表为空时显示占位提示。 + */ private void updateDeviceEmpty() { boolean empty = deviceList.isEmpty(); binding.tvDeviceEmpty.setVisibility(empty ? View.VISIBLE : View.GONE); diff --git a/WebRTCControllerWeb/src/components/ControlBar.vue b/WebRTCControllerWeb/src/components/ControlBar.vue index c63d42a..04f91f0 100644 --- a/WebRTCControllerWeb/src/components/ControlBar.vue +++ b/WebRTCControllerWeb/src/components/ControlBar.vue @@ -29,11 +29,19 @@ const selectedResolution = ref(0); const fpsOptions = ref([15, 24, 30, 60]); const selectedFps = ref(0); // 0 表示尚未同步到被控端当前帧率 +// 记录当前处于按下状态的按键:pointerleave/pointerup 在指针未按下时也会触发, +// 若无此跟踪,鼠标仅扫过按钮就会向被控端发送孤立的 KEY UP。 +const pressedButtons = new Set(); + function pressDown(code) { if (!store.dataChannelOpen) return; + if (pressedButtons.has(code)) return; + pressedButtons.add(code); sendKeyDown(code); } function pressUp(code) { + if (!pressedButtons.has(code)) return; + pressedButtons.delete(code); if (!store.dataChannelOpen) return; sendKeyUp(code); } @@ -71,18 +79,26 @@ function lookupKeyCode(e) { } // 固定按键:按下/抬起分离(如电源键需要真实按下-抬起配对) +// pressedKeys 跟踪已按下的物理键,避免长按自动重复触发重复 DOWN、 +// 以及无对应 DOWN 的孤立 UP(如在窗口外按下、回到窗口内才抬起)。 +const pressedKeys = new Set(); + function onKeyCaptureDown(e) { if (!store.dataChannelOpen) return; const m = lookupKeyCode(e); if (!m) return; e.preventDefault(); + if (pressedKeys.has(m.code)) return; // 浏览器长按自动重复,忽略 + pressedKeys.add(m.code); sendKeyDown(m.code); } function onKeyCaptureUp(e) { - if (!store.dataChannelOpen) return; const m = lookupKeyCode(e); if (!m) return; e.preventDefault(); + if (!pressedKeys.has(m.code)) return; + pressedKeys.delete(m.code); + if (!store.dataChannelOpen) return; sendKeyUp(m.code); } diff --git a/WebRTCControllerWeb/src/components/RemoteScreen.vue b/WebRTCControllerWeb/src/components/RemoteScreen.vue index 8ff4248..10cc18b 100644 --- a/WebRTCControllerWeb/src/components/RemoteScreen.vue +++ b/WebRTCControllerWeb/src/components/RemoteScreen.vue @@ -45,6 +45,9 @@ const TOUCH_SLOP = 0.02; const SAMPLE_MS = 16; let startX = 0, startY = 0, startTime = 0, isLongPressed = false, longTimer = null, lastMoveTs = 0; +// 指针是否处于按下状态:未按下时 pointermove/pointerup 不得产生任何指令, +// 否则鼠标仅悬停划过画面就会向被控端发送大量 ACTION_MOVE。 +let isDown = false; function clearLong() { if (longTimer) { clearTimeout(longTimer); longTimer = null; } @@ -53,6 +56,7 @@ function clearLong() { function onDown(e) { if (!active.value) return; overlayRef.value.setPointerCapture && overlayRef.value.setPointerCapture(e.pointerId); + isDown = true; isLongPressed = false; const p = mapRelative(e.clientX, e.clientY); startX = p.x; startY = p.y; startTime = Date.now(); lastMoveTs = 0; @@ -65,7 +69,7 @@ function onDown(e) { } function onMove(e) { - if (!active.value) return; + if (!active.value || !isDown) return; const p = mapRelative(e.clientX, e.clientY); if (Math.abs(p.x - startX) > TOUCH_SLOP || Math.abs(p.y - startY) > TOUCH_SLOP) clearLong(); const now = Date.now(); @@ -76,7 +80,8 @@ function onMove(e) { } function onUp(e) { - if (!active.value) return; + if (!active.value || !isDown) return; + isDown = false; clearLong(); const p = mapRelative(e.clientX, e.clientY); sendMotionEvent(1, p.x, p.y); // ACTION_UP @@ -88,6 +93,15 @@ function onUp(e) { else sendSwipe(startX, startY, p.x, p.y, Math.max(dur, 1)); } } + +function onCancel() { + clearLong(); + // 按下过程中指针被取消(如系统手势接管):补发 ACTION_UP 释放,避免被控端卡在按下态。 + if (isDown && active.value) { + isDown = false; + sendMotionEvent(1, startX, startY); // ACTION_UP + } +}