feat: flutter优化滑动不跟手
This commit is contained in:
@@ -233,15 +233,15 @@ class _ControllerHomeState extends State<ControllerHome> {
|
|||||||
RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
RTCVideoViewObjectFit.RTCVideoViewObjectFitContain,
|
||||||
),
|
),
|
||||||
RemoteTouchView(
|
RemoteTouchView(
|
||||||
onTouch: (x, y) => _controller
|
// 禁用离散的 TOUCH/SWIPE/LONG_PRESS 指令,改用实时的 onMotionEvent 以解决重复操作问题。
|
||||||
?.sendControlCommand(ControlCommands.touch(x, y)),
|
// 原始的动作流已包含完整的触摸过程,被控端系统会自动识别单击、滑动和长按。
|
||||||
onSwipe: (x1, y1, x2, y2, d) => _controller
|
onTouch: (x, y) {},
|
||||||
?.sendControlCommand(
|
onSwipe: (x1, y1, x2, y2, d) {},
|
||||||
ControlCommands.swipe(x1, y1, x2, y2, d)),
|
onLongPress: (x, y) {},
|
||||||
onKey: (k, a) => _controller
|
onKey: (k, a) => _controller
|
||||||
?.sendControlCommand(ControlCommands.key(k, a)),
|
?.sendControlCommand(ControlCommands.key(k, a)),
|
||||||
onLongPress: (x, y) => _controller
|
onMotionEvent: (a, x, y) => _controller
|
||||||
?.sendControlCommand(ControlCommands.longPress(x, y)),
|
?.sendControlCommand(ControlCommands.motionEvent(a, x, y)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -45,4 +45,13 @@ class ControlCommands {
|
|||||||
'x': x,
|
'x': x,
|
||||||
'y': y,
|
'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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||||||
|
|
||||||
import '../config/ice_servers.dart';
|
import '../config/ice_servers.dart';
|
||||||
@@ -49,6 +50,10 @@ class WebRtcController {
|
|||||||
'iceServers': kIceServers,
|
'iceServers': kIceServers,
|
||||||
'sdpSemantics': 'unified-plan',
|
'sdpSemantics': 'unified-plan',
|
||||||
'iceCandidatePoolSize': 10,
|
'iceCandidatePoolSize': 10,
|
||||||
|
// 增强复杂网络下的稳定性,参考 Android 端配置。
|
||||||
|
'continualGatheringPolicy': 'gatherContinually',
|
||||||
|
'iceTransportsType': 'all',
|
||||||
|
'tcpCandidatePolicy': 'enabled',
|
||||||
};
|
};
|
||||||
|
|
||||||
_pc = await createPeerConnection(configuration);
|
_pc = await createPeerConnection(configuration);
|
||||||
@@ -64,8 +69,11 @@ class WebRtcController {
|
|||||||
init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly),
|
init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 创建控制用 DataChannel(有序)。
|
// 创建控制用 DataChannel。
|
||||||
final dcInit = RTCDataChannelInit()..ordered = true;
|
// 使用非可靠、无序模式以降低延迟,解决“不跟手”问题。
|
||||||
|
final dcInit = RTCDataChannelInit()
|
||||||
|
..ordered = false
|
||||||
|
..maxRetransmits = 0;
|
||||||
_dataChannel = await _pc!.createDataChannel(kDataChannelLabel, dcInit);
|
_dataChannel = await _pc!.createDataChannel(kDataChannelLabel, dcInit);
|
||||||
_setupDataChannel(_dataChannel!);
|
_setupDataChannel(_dataChannel!);
|
||||||
|
|
||||||
@@ -166,6 +174,7 @@ class WebRtcController {
|
|||||||
/// 通过 DataChannel 发送控制指令(JSON 字符串)。
|
/// 通过 DataChannel 发送控制指令(JSON 字符串)。
|
||||||
void sendControlCommand(String commandJson) {
|
void sendControlCommand(String commandJson) {
|
||||||
if (_dataChannel?.state == RTCDataChannelState.RTCDataChannelOpen) {
|
if (_dataChannel?.state == RTCDataChannelState.RTCDataChannelOpen) {
|
||||||
|
debugPrint('WebRtcController: Sending command -> $commandJson');
|
||||||
_dataChannel!.send(RTCDataChannelMessage(commandJson));
|
_dataChannel!.send(RTCDataChannelMessage(commandJson));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class RemoteTouchView extends StatefulWidget {
|
|||||||
) onSwipe;
|
) onSwipe;
|
||||||
final void Function(int keyCode, int action) onKey;
|
final void Function(int keyCode, int action) onKey;
|
||||||
final void Function(double x, double y) onLongPress;
|
final void Function(double x, double y) onLongPress;
|
||||||
|
final void Function(int action, double x, double y) onMotionEvent;
|
||||||
|
|
||||||
const RemoteTouchView({
|
const RemoteTouchView({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -34,6 +35,7 @@ class RemoteTouchView extends StatefulWidget {
|
|||||||
required this.onSwipe,
|
required this.onSwipe,
|
||||||
required this.onKey,
|
required this.onKey,
|
||||||
required this.onLongPress,
|
required this.onLongPress,
|
||||||
|
required this.onMotionEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -77,9 +79,11 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
|||||||
// 捕获物理按键(外接键盘 / 遥控器等)。
|
// 捕获物理按键(外接键盘 / 遥控器等)。
|
||||||
onKeyEvent: (node, event) {
|
onKeyEvent: (node, event) {
|
||||||
if (event is KeyDownEvent) {
|
if (event is KeyDownEvent) {
|
||||||
|
debugPrint('RemoteTouchView: KeyDown - ${event.logicalKey.debugName} (${event.logicalKey.keyId})');
|
||||||
widget.onKey(event.logicalKey.keyId, 0);
|
widget.onKey(event.logicalKey.keyId, 0);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
} else if (event is KeyUpEvent) {
|
} else if (event is KeyUpEvent) {
|
||||||
|
debugPrint('RemoteTouchView: KeyUp - ${event.logicalKey.debugName} (${event.logicalKey.keyId})');
|
||||||
widget.onKey(event.logicalKey.keyId, 1);
|
widget.onKey(event.logicalKey.keyId, 1);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
@@ -93,17 +97,37 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
|||||||
_startPosition = event.localPosition;
|
_startPosition = event.localPosition;
|
||||||
_currentPosition = event.localPosition;
|
_currentPosition = event.localPosition;
|
||||||
_startTime = DateTime.now();
|
_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, () {
|
_longPressTimer = Timer(_longPressTimeout, () {
|
||||||
final size = context.size;
|
final size = context.size;
|
||||||
if (_currentPosition == null || size == null) return;
|
if (_currentPosition == null || size == null) return;
|
||||||
_isLongPressed = true;
|
_isLongPressed = true;
|
||||||
final x = _toRelativeX(_currentPosition!, size);
|
final x = _toRelativeX(_currentPosition!, size);
|
||||||
final y = _toRelativeY(_currentPosition!, size);
|
final y = _toRelativeY(_currentPosition!, size);
|
||||||
|
debugPrint('RemoteTouchView: LongPress - relX: ${x.toStringAsFixed(3)}, relY: ${y.toStringAsFixed(3)}');
|
||||||
widget.onLongPress(x, y);
|
widget.onLongPress(x, y);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPointerMove: (event) {
|
onPointerMove: (event) {
|
||||||
_currentPosition = event.localPosition;
|
_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) {
|
if (_longPressTimer != null && _startPosition != null) {
|
||||||
final dx = (event.localPosition.dx - _startPosition!.dx).abs();
|
final dx = (event.localPosition.dx - _startPosition!.dx).abs();
|
||||||
final dy = (event.localPosition.dy - _startPosition!.dy).abs();
|
final dy = (event.localPosition.dy - _startPosition!.dy).abs();
|
||||||
@@ -114,6 +138,15 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
|||||||
},
|
},
|
||||||
onPointerUp: (event) {
|
onPointerUp: (event) {
|
||||||
_cancelLongPress();
|
_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 逻辑)。
|
// 长按已触发则不再派发单击 / 滑动事件(对应 Android 端 isLongPressed 逻辑)。
|
||||||
if (_isLongPressed) {
|
if (_isLongPressed) {
|
||||||
_startPosition = null;
|
_startPosition = null;
|
||||||
@@ -121,7 +154,6 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
|||||||
_startTime = null;
|
_startTime = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final size = context.size;
|
|
||||||
if (_startPosition == null || size == null || _startTime == null) {
|
if (_startPosition == null || size == null || _startTime == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -135,8 +167,10 @@ class _RemoteTouchViewState extends State<RemoteTouchView> {
|
|||||||
final dy = (endY - startY).abs();
|
final dy = (endY - startY).abs();
|
||||||
|
|
||||||
if (duration < 200 && dx < 0.02 && dy < 0.02) {
|
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);
|
widget.onTouch(startX, startY);
|
||||||
} else {
|
} 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));
|
widget.onSwipe(startX, startY, endX, endY, max(duration, 1));
|
||||||
}
|
}
|
||||||
_startPosition = null;
|
_startPosition = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user