- 新增 /connection 路由,从全局选中设备 SN 读取并渲染远程控制页 - 首页镜像操作改为静默下发 mirror 命令并跳转远程控制页 - 更新 AGENTS.md 版本管理策略为使用最新稳定版本 - 调整 pubspec.lock 中 hooks 与 record_use 版本
154 lines
4.9 KiB
Dart
154 lines
4.9 KiB
Dart
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
||
|
||
import '../data/connection_providers.dart';
|
||
import '../data/webrtc_orchestrator.dart';
|
||
import '../domain/connection_models.dart';
|
||
|
||
/// 远程控制会话状态(供 UI 渲染)。
|
||
class ConnectionSessionState {
|
||
final ConnectionState state;
|
||
final String? errorMessage;
|
||
final bool hasVideo;
|
||
|
||
const ConnectionSessionState({
|
||
this.state = ConnectionState.idle,
|
||
this.errorMessage,
|
||
this.hasVideo = false,
|
||
});
|
||
|
||
ConnectionSessionState copyWith({
|
||
ConnectionState? state,
|
||
String? errorMessage,
|
||
bool? hasVideo,
|
||
}) {
|
||
return ConnectionSessionState(
|
||
state: state ?? this.state,
|
||
errorMessage: errorMessage ?? this.errorMessage,
|
||
hasVideo: hasVideo ?? this.hasVideo,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 远程控制控制器:管理连接状态机(idle → connecting → connected → ended)。
|
||
class ConnectionController extends Notifier<ConnectionSessionState> {
|
||
WebRtcOrchestrator? _orchestrator;
|
||
|
||
@override
|
||
ConnectionSessionState build() {
|
||
return const ConnectionSessionState();
|
||
}
|
||
|
||
/// 目标设备 SN(页面初始化时设置)。
|
||
String? _targetSn;
|
||
set targetSn(String? value) => _targetSn = value;
|
||
|
||
/// 远程屏幕渲染器(供页面 RTCVideoView 渲染)。
|
||
RTCVideoRenderer? get remoteRenderer => _orchestrator?.remoteRenderer;
|
||
|
||
/// 发起连接。
|
||
Future<void> connect() async {
|
||
final sn = _targetSn;
|
||
if (sn == null || sn.isEmpty) {
|
||
state = const ConnectionSessionState(
|
||
state: ConnectionState.error,
|
||
errorMessage: '缺少目标设备',
|
||
);
|
||
return;
|
||
}
|
||
|
||
final signaling = ref.read(signalingClientProvider);
|
||
final clientName = ref.read(clientNameProvider);
|
||
|
||
state = const ConnectionSessionState(state: ConnectionState.connecting);
|
||
|
||
_orchestrator = WebRtcOrchestrator(
|
||
signaling: signaling,
|
||
targetSn: sn,
|
||
clientName: clientName,
|
||
);
|
||
_orchestrator!.onConnectionState = (connected) {
|
||
_onRtcState(connected);
|
||
};
|
||
|
||
signaling.onMessage = (message) {
|
||
// 先透传给 WebRTC 编排器处理(ANSWER/ICE 等),确保握手能继续
|
||
_orchestrator?.handleSignal(message);
|
||
// 仅"明确被拒"才进入错误态;TARGET_OFFLINE 可能因握手时序/目标会话短暂未就绪
|
||
// 而误报(如设备端刚连上信令、正在应答),不立即判定失败,避免闪断"设备不在线"。
|
||
if (message.type == SignalType.connectionRejected) {
|
||
state = ConnectionSessionState(
|
||
state: ConnectionState.error,
|
||
errorMessage: '连接被拒绝',
|
||
);
|
||
}
|
||
};
|
||
signaling.onError = (error) {
|
||
state = ConnectionSessionState(
|
||
state: ConnectionState.error,
|
||
errorMessage: error,
|
||
);
|
||
};
|
||
|
||
try {
|
||
await signaling.connect(signalServerUrl, targetSn: sn);
|
||
await _orchestrator!.start();
|
||
} catch (e) {
|
||
state = ConnectionSessionState(
|
||
state: ConnectionState.error,
|
||
errorMessage: '连接失败: $e',
|
||
);
|
||
}
|
||
}
|
||
|
||
void _onRtcState(bool connected) {
|
||
if (connected) {
|
||
state = const ConnectionSessionState(
|
||
state: ConnectionState.connected,
|
||
hasVideo: true,
|
||
);
|
||
} else if (state.state == ConnectionState.connected) {
|
||
state = const ConnectionSessionState(state: ConnectionState.ended);
|
||
}
|
||
}
|
||
|
||
/// 发送点击(相对坐标 0~1)。
|
||
void sendTap(double relX, double relY) {
|
||
debugPrint('[touch] TAP rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
|
||
_orchestrator?.sendTap(relX, relY);
|
||
}
|
||
|
||
void sendLongPress(double relX, double relY) {
|
||
debugPrint('[touch] LONG_PRESS rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
|
||
_orchestrator?.sendLongPress(relX, relY);
|
||
}
|
||
|
||
void sendSwipe(double relX1, double relY1, double relX2, double relY2) {
|
||
debugPrint('[touch] SWIPE rel=(${relX1.toStringAsFixed(3)}, ${relY1.toStringAsFixed(3)}) -> (${relX2.toStringAsFixed(3)}, ${relY2.toStringAsFixed(3)})');
|
||
_orchestrator?.sendSwipe(relX1, relY1, relX2, relY2);
|
||
}
|
||
|
||
/// 发送实时触摸事件(MOTION_EVENT,跟手用)。action:0=DOWN,1=UP,2=MOVE。
|
||
void sendMotionEvent(int action, double relX, double relY) {
|
||
debugPrint('[touch] MOTION action=$action rel=(${relX.toStringAsFixed(3)}, ${relY.toStringAsFixed(3)})');
|
||
_orchestrator?.sendMotionEvent(action, relX, relY);
|
||
}
|
||
|
||
void sendKey(int keyCode) {
|
||
_orchestrator?.sendKey(keyCode);
|
||
}
|
||
|
||
void sendSetResolution({int width = 0, int height = 0, int fps = 0}) {
|
||
_orchestrator?.sendSetResolution(width: width, height: height, fps: fps);
|
||
}
|
||
|
||
/// 关闭并清理。
|
||
Future<void> disconnect() async {
|
||
await _orchestrator?.dispose();
|
||
_orchestrator = null;
|
||
ref.read(signalingClientProvider).dispose();
|
||
state = const ConnectionSessionState(state: ConnectionState.ended);
|
||
}
|
||
}
|