From 3e46e1e4f3071dc53f5cd8ec19667d02c06aefe4 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Tue, 14 Jul 2026 20:55:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20flutter=E4=BC=98=E5=8C=96=E6=BB=91?= =?UTF-8?q?=E5=8A=A8=E4=B8=8D=E8=B7=9F=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webrtc_controller_flutter/lib/main.dart | 14 ++++---- .../lib/utils/control_commands.dart | 9 +++++ .../lib/webrtc/webrtc_controller.dart | 13 +++++-- .../lib/widgets/remote_touch_view.dart | 36 ++++++++++++++++++- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/webrtc_controller_flutter/lib/main.dart b/webrtc_controller_flutter/lib/main.dart index d86c3e3..0045c27 100644 --- a/webrtc_controller_flutter/lib/main.dart +++ b/webrtc_controller_flutter/lib/main.dart @@ -233,15 +233,15 @@ class _ControllerHomeState extends State { RTCVideoViewObjectFit.RTCVideoViewObjectFitContain, ), RemoteTouchView( - onTouch: (x, y) => _controller - ?.sendControlCommand(ControlCommands.touch(x, y)), - onSwipe: (x1, y1, x2, y2, d) => _controller - ?.sendControlCommand( - ControlCommands.swipe(x1, y1, x2, y2, d)), + // 禁用离散的 TOUCH/SWIPE/LONG_PRESS 指令,改用实时的 onMotionEvent 以解决重复操作问题。 + // 原始的动作流已包含完整的触摸过程,被控端系统会自动识别单击、滑动和长按。 + onTouch: (x, y) {}, + onSwipe: (x1, y1, x2, y2, d) {}, + onLongPress: (x, y) {}, onKey: (k, a) => _controller ?.sendControlCommand(ControlCommands.key(k, a)), - onLongPress: (x, y) => _controller - ?.sendControlCommand(ControlCommands.longPress(x, y)), + onMotionEvent: (a, x, y) => _controller + ?.sendControlCommand(ControlCommands.motionEvent(a, x, y)), ), ], ), diff --git a/webrtc_controller_flutter/lib/utils/control_commands.dart b/webrtc_controller_flutter/lib/utils/control_commands.dart index 442b0f0..9308527 100644 --- a/webrtc_controller_flutter/lib/utils/control_commands.dart +++ b/webrtc_controller_flutter/lib/utils/control_commands.dart @@ -45,4 +45,13 @@ class ControlCommands { 'x': x, 'y': y, }); + + /// 原始 MotionEvent 指令,用于实现“实时跟手”。 + /// action: 0=DOWN, 1=UP, 2=MOVE(对应 Android MotionEvent.ACTION_XXX)。 + static String motionEvent(int action, double x, double y) => jsonEncode({ + 'action': 'MOTION_EVENT', + 'motionAction': action, + 'x': x, + 'y': y, + }); } diff --git a/webrtc_controller_flutter/lib/webrtc/webrtc_controller.dart b/webrtc_controller_flutter/lib/webrtc/webrtc_controller.dart index 149af38..8cd88cf 100644 --- a/webrtc_controller_flutter/lib/webrtc/webrtc_controller.dart +++ b/webrtc_controller_flutter/lib/webrtc/webrtc_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import '../config/ice_servers.dart'; @@ -49,6 +50,10 @@ class WebRtcController { 'iceServers': kIceServers, 'sdpSemantics': 'unified-plan', 'iceCandidatePoolSize': 10, + // 增强复杂网络下的稳定性,参考 Android 端配置。 + 'continualGatheringPolicy': 'gatherContinually', + 'iceTransportsType': 'all', + 'tcpCandidatePolicy': 'enabled', }; _pc = await createPeerConnection(configuration); @@ -64,8 +69,11 @@ class WebRtcController { init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly), ); - // 创建控制用 DataChannel(有序)。 - final dcInit = RTCDataChannelInit()..ordered = true; + // 创建控制用 DataChannel。 + // 使用非可靠、无序模式以降低延迟,解决“不跟手”问题。 + final dcInit = RTCDataChannelInit() + ..ordered = false + ..maxRetransmits = 0; _dataChannel = await _pc!.createDataChannel(kDataChannelLabel, dcInit); _setupDataChannel(_dataChannel!); @@ -166,6 +174,7 @@ class WebRtcController { /// 通过 DataChannel 发送控制指令(JSON 字符串)。 void sendControlCommand(String commandJson) { if (_dataChannel?.state == RTCDataChannelState.RTCDataChannelOpen) { + debugPrint('WebRtcController: Sending command -> $commandJson'); _dataChannel!.send(RTCDataChannelMessage(commandJson)); } } diff --git a/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart b/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart index 8faf396..8bb0125 100644 --- a/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart +++ b/webrtc_controller_flutter/lib/widgets/remote_touch_view.dart @@ -27,6 +27,7 @@ class RemoteTouchView extends StatefulWidget { ) onSwipe; final void Function(int keyCode, int action) onKey; final void Function(double x, double y) onLongPress; + final void Function(int action, double x, double y) onMotionEvent; const RemoteTouchView({ super.key, @@ -34,6 +35,7 @@ class RemoteTouchView extends StatefulWidget { required this.onSwipe, required this.onKey, required this.onLongPress, + required this.onMotionEvent, }); @override @@ -77,9 +79,11 @@ class _RemoteTouchViewState extends State { // 捕获物理按键(外接键盘 / 遥控器等)。 onKeyEvent: (node, event) { if (event is KeyDownEvent) { + debugPrint('RemoteTouchView: KeyDown - ${event.logicalKey.debugName} (${event.logicalKey.keyId})'); widget.onKey(event.logicalKey.keyId, 0); return KeyEventResult.handled; } else if (event is KeyUpEvent) { + debugPrint('RemoteTouchView: KeyUp - ${event.logicalKey.debugName} (${event.logicalKey.keyId})'); widget.onKey(event.logicalKey.keyId, 1); return KeyEventResult.handled; } @@ -93,17 +97,37 @@ class _RemoteTouchViewState extends State { _startPosition = event.localPosition; _currentPosition = event.localPosition; _startTime = DateTime.now(); + + final size = context.size; + if (size != null) { + final rx = _toRelativeX(event.localPosition, size); + final ry = _toRelativeY(event.localPosition, size); + debugPrint('RemoteTouchView: PointerDown - relX: ${rx.toStringAsFixed(3)}, relY: ${ry.toStringAsFixed(3)}'); + widget.onMotionEvent(0, rx, ry); + } + _longPressTimer = Timer(_longPressTimeout, () { final size = context.size; if (_currentPosition == null || size == null) return; _isLongPressed = true; final x = _toRelativeX(_currentPosition!, size); final y = _toRelativeY(_currentPosition!, size); + debugPrint('RemoteTouchView: LongPress - relX: ${x.toStringAsFixed(3)}, relY: ${y.toStringAsFixed(3)}'); widget.onLongPress(x, y); }); }, onPointerMove: (event) { _currentPosition = event.localPosition; + + final size = context.size; + if (size != null) { + final rx = _toRelativeX(event.localPosition, size); + final ry = _toRelativeY(event.localPosition, size); + // Move 事件较多,可以考虑控制日志频率,这里直接打印 + debugPrint('RemoteTouchView: PointerMove - relX: ${rx.toStringAsFixed(3)}, relY: ${ry.toStringAsFixed(3)}'); + widget.onMotionEvent(2, rx, ry); + } + if (_longPressTimer != null && _startPosition != null) { final dx = (event.localPosition.dx - _startPosition!.dx).abs(); final dy = (event.localPosition.dy - _startPosition!.dy).abs(); @@ -114,6 +138,15 @@ class _RemoteTouchViewState extends State { }, onPointerUp: (event) { _cancelLongPress(); + + final size = context.size; + if (size != null) { + final rx = _toRelativeX(event.localPosition, size); + final ry = _toRelativeY(event.localPosition, size); + debugPrint('RemoteTouchView: PointerUp - relX: ${rx.toStringAsFixed(3)}, relY: ${ry.toStringAsFixed(3)}'); + widget.onMotionEvent(1, rx, ry); + } + // 长按已触发则不再派发单击 / 滑动事件(对应 Android 端 isLongPressed 逻辑)。 if (_isLongPressed) { _startPosition = null; @@ -121,7 +154,6 @@ class _RemoteTouchViewState extends State { _startTime = null; return; } - final size = context.size; if (_startPosition == null || size == null || _startTime == null) { return; } @@ -135,8 +167,10 @@ class _RemoteTouchViewState extends State { final dy = (endY - startY).abs(); if (duration < 200 && dx < 0.02 && dy < 0.02) { + debugPrint('RemoteTouchView: Detected Touch - x: ${startX.toStringAsFixed(3)}, y: ${startY.toStringAsFixed(3)}'); widget.onTouch(startX, startY); } else { + debugPrint('RemoteTouchView: Detected Swipe - from (${startX.toStringAsFixed(3)}, ${startY.toStringAsFixed(3)}) to (${endX.toStringAsFixed(3)}, ${endY.toStringAsFixed(3)}), duration: ${max(duration, 1)}ms'); widget.onSwipe(startX, startY, endX, endY, max(duration, 1)); } _startPosition = null;