From d43c0331b61623b41fd945323f7362580541a044 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Tue, 14 Jul 2026 16:40:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9B=AE=E6=A0=87=E4=B8=8D=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF=E6=97=B6=E8=BF=9B=E8=A1=8C=E6=8F=90=E7=A4=BA=E5=92=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ttstd/controller/MainActivity.java | 18 ++++++++ .../handler/SignalWebSocketHandler.java | 41 +++++++++++++++++-- .../lib/controller/remote_controller.dart | 8 ++++ webrtc_controller_flutter/lib/main.dart | 17 ++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/WebRTCController/app/src/main/java/com/ttstd/controller/MainActivity.java b/WebRTCController/app/src/main/java/com/ttstd/controller/MainActivity.java index cc66828..ac2beb5 100644 --- a/WebRTCController/app/src/main/java/com/ttstd/controller/MainActivity.java +++ b/WebRTCController/app/src/main/java/com/ttstd/controller/MainActivity.java @@ -269,9 +269,27 @@ public class MainActivity extends AppCompatActivity { case "ICE_CANDIDATE": handleIceCandidate(message); break; + case "TARGET_OFFLINE": + handleTargetOffline(message); + break; } } + private void handleTargetOffline(SignalMessage message) { + String offlineId = message.getToDeviceId(); + String payload = message.getPayload(); + String text = (payload != null && !payload.isEmpty()) + ? payload + : "目标被控端不在线,请确认设备已开启并连接服务器"; + String finalText = text; + runOnUiThread(() -> { + tvStatus.setText("状态: " + finalText); + Toast.makeText(MainActivity.this, finalText, Toast.LENGTH_LONG).show(); + updateUI(false); + }); + Log.w(TAG, "Target device " + offlineId + " is offline"); + } + private void handleAnswer(SignalMessage message) { try { JsonObject payload = gson.fromJson(message.getPayload(), JsonObject.class); diff --git a/WebRTCSignalServer/src/main/java/com/ttstd/signaling/handler/SignalWebSocketHandler.java b/WebRTCSignalServer/src/main/java/com/ttstd/signaling/handler/SignalWebSocketHandler.java index 9678c3a..8c42a64 100644 --- a/WebRTCSignalServer/src/main/java/com/ttstd/signaling/handler/SignalWebSocketHandler.java +++ b/WebRTCSignalServer/src/main/java/com/ttstd/signaling/handler/SignalWebSocketHandler.java @@ -57,13 +57,17 @@ public class SignalWebSocketHandler extends TextWebSocketHandler { break; case "OFFER": case "ANSWER": + // 关键信令:目标不在线时回送 TARGET_OFFLINE 提醒发送方 + forwardMessage(signalMessage, true); + break; case "ICE_CANDIDATE": case "CONTROL_COMMAND": - forwardMessage(signalMessage); + // 普通信令:目标不在线时静默丢弃,不返回提示 + forwardMessage(signalMessage, false); break; default: - // 其他消息类型直接转发 - forwardMessage(signalMessage); + // 其他消息类型直接转发,目标不在线时静默丢弃 + forwardMessage(signalMessage, false); break; } } catch (Exception e) { @@ -122,7 +126,7 @@ public class SignalWebSocketHandler extends TextWebSocketHandler { sendToSession(session, response); } - private void forwardMessage(SignalMessage message) { + private void forwardMessage(SignalMessage message, boolean notifyOffline) { String toDeviceId = message.getToDeviceId(); if (toDeviceId == null) { logger.warn("Cannot forward message: toDeviceId is null"); @@ -132,6 +136,10 @@ public class SignalWebSocketHandler extends TextWebSocketHandler { WebSocketSession targetSession = sessionManager.getSession(toDeviceId); if (targetSession == null || !targetSession.isOpen()) { logger.warn("Target device {} is not online", toDeviceId); + // 仅 OFFER / ANSWER 在目标不在线时回送 TARGET_OFFLINE,其余类型静默丢弃 + if (notifyOffline) { + notifySenderTargetOffline(message, toDeviceId); + } return; } @@ -144,6 +152,31 @@ public class SignalWebSocketHandler extends TextWebSocketHandler { } } + /** + * 目标被控端不在线时,向发送方(主控端)回送 TARGET_OFFLINE 提醒。 + */ + private void notifySenderTargetOffline(SignalMessage message, String offlineDeviceId) { + String fromDeviceId = message.getFromDeviceId(); + if (fromDeviceId == null) { + logger.warn("Cannot notify offline state: fromDeviceId is null"); + return; + } + + WebSocketSession senderSession = sessionManager.getSession(fromDeviceId); + if (senderSession == null || !senderSession.isOpen()) { + logger.warn("Sender {} session not found, cannot notify target offline", fromDeviceId); + return; + } + + Map response = new HashMap<>(); + response.put("type", "TARGET_OFFLINE"); + response.put("toDeviceId", offlineDeviceId); + response.put("payload", "目标被控端(" + offlineDeviceId + ")不在线,请确认设备已开启并连接到信令服务器"); + + sendToSession(senderSession, response); + logger.info("Notified sender {} that target {} is offline", fromDeviceId, offlineDeviceId); + } + private void sendToSession(WebSocketSession session, Object data) { try { String json = objectMapper.writeValueAsString(data); diff --git a/webrtc_controller_flutter/lib/controller/remote_controller.dart b/webrtc_controller_flutter/lib/controller/remote_controller.dart index b640bb1..64592ed 100644 --- a/webrtc_controller_flutter/lib/controller/remote_controller.dart +++ b/webrtc_controller_flutter/lib/controller/remote_controller.dart @@ -30,6 +30,9 @@ class RemoteController { /// ICE 连接断开(用于提示用户并返回连接设置)。 void Function(String message)? onIceDisconnected; + /// 目标被控端不在线(服务器回送 TARGET_OFFLINE,用于提示用户并复位 UI)。 + void Function(String message)? onTargetOffline; + /// 远端视频渲染器就绪。 void Function(RTCVideoRenderer renderer)? onRemoteStream; @@ -92,6 +95,11 @@ class RemoteController { final payload = jsonDecode(message.payload!) as Map; _webRtc?.handleIceCandidate(payload); break; + case 'TARGET_OFFLINE': + final text = message.payload ?? '目标被控端不在线,请确认设备已开启并连接服务器'; + onStatusChanged?.call('状态: $text'); + onTargetOffline?.call(text); + break; } } diff --git a/webrtc_controller_flutter/lib/main.dart b/webrtc_controller_flutter/lib/main.dart index fb1146a..d86c3e3 100644 --- a/webrtc_controller_flutter/lib/main.dart +++ b/webrtc_controller_flutter/lib/main.dart @@ -99,6 +99,9 @@ class _ControllerHomeState extends State { _controller!.onIceDisconnected = (message) { _onIceDisconnected(message); }; + _controller!.onTargetOffline = (message) { + _onTargetOffline(message); + }; _controller!.onRemoteStream = (renderer) { setState(() => _renderer = renderer); renderer.addListener(_onRendererUpdate); @@ -130,6 +133,20 @@ class _ControllerHomeState extends State { await _disconnect(); } + /// 目标被控端不在线:提示用户并复位到连接设置面板。 + Future _onTargetOffline(String message) async { + final scaffoldCtx = _scaffoldKey.currentState?.context; + if (mounted && scaffoldCtx != null) { + ScaffoldMessenger.of(scaffoldCtx).showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(seconds: 3), + ), + ); + } + setState(() => _connected = false); + } + Future _disconnect() async { await _controller?.disconnect(); _controller = null;