feat(remote): 修复键盘输入映射并重构连接流程
- Android端注入按键时显式设置SOURCE_KEYBOARD,修复IME丢弃事件导致输入框无法输入文字的问题 - Flutter端新增Flutter逻辑键到Android keyCode的转换映射,避免功能键误触发 - 重构RemoteController,分离信令连接与远程控制逻辑,支持设备列表选择与配对码兑换 - AuthRepository接口新增getBindings、getOnlineDevices、redeemPairingCode方法 - DioAuthRepository实现401自动刷新token重试机制 - ConnectionSessionState扩展设备列表、在线状态、配对码等状态字段
This commit is contained in:
@@ -14,20 +14,31 @@ import 'webrtc_controller.dart';
|
||||
/// 对外暴露连接/断开/发送指令等高层接口(对应 Android 端 MainActivity 的流程)。
|
||||
class RemoteController {
|
||||
final String serverUrl;
|
||||
final String targetDeviceId;
|
||||
final AuthRepository authRepository;
|
||||
String? token;
|
||||
|
||||
/// 目标被控端设备ID,在发起远程控制时指定。
|
||||
String? targetDeviceId;
|
||||
|
||||
String? authType;
|
||||
String? authValue;
|
||||
|
||||
late final SignalingClient _signaling;
|
||||
SignalingClient? _signaling;
|
||||
WebRtcController? _webRtc;
|
||||
Timer? _statsTimer;
|
||||
|
||||
/// 本地设备ID(由服务端 REGISTER_SUCCESS 下发)。
|
||||
String? _myDeviceId;
|
||||
|
||||
/// 信令是否已注册成功(拿到本机 deviceId)。
|
||||
bool get registered => _myDeviceId != null;
|
||||
|
||||
/// 本机设备ID(未注册时为 null)。
|
||||
String? get myDeviceId => _myDeviceId;
|
||||
|
||||
/// 是否已发起远程控制(WebRTC 会话存在)。
|
||||
bool get controlling => _webRtc != null;
|
||||
|
||||
/// 信令状态变化(如"正在连接…"、"已连接…)。
|
||||
void Function(String status)? onStatusChanged;
|
||||
|
||||
@@ -80,20 +91,23 @@ class RemoteController {
|
||||
/// 强制下线(4003):用于跳回登录。
|
||||
void Function()? onForceLogout;
|
||||
|
||||
/// 信令注册成功(下发本机 deviceId),此时可发起远程控制。
|
||||
void Function(String myDeviceId)? onRegistered;
|
||||
|
||||
RemoteController({
|
||||
required this.serverUrl,
|
||||
required this.targetDeviceId,
|
||||
required this.authRepository,
|
||||
this.targetDeviceId,
|
||||
this.token,
|
||||
this.authType,
|
||||
this.authValue,
|
||||
});
|
||||
|
||||
/// 发起连接:先确保 accessToken,成功后携带 Bearer 建立 WebSocket,
|
||||
/// 待 REGISTER_SUCCESS 拿到本机 deviceId 再建立 WebRTC 并创建 Offer。
|
||||
Future<void> connect({String? authType, String? authValue}) async {
|
||||
this.authType = authType;
|
||||
this.authValue = authValue;
|
||||
/// 仅建立信令连接:确保 accessToken 后携带 Bearer 建立 WebSocket,
|
||||
/// 待 REGISTER_SUCCESS 拿到本机 deviceId 即完成,**不会**发起远程控制。
|
||||
///
|
||||
/// 发起远程控制请在注册成功后调用 [startControl]。
|
||||
Future<void> connectSignaling() async {
|
||||
onStatusChanged?.call('状态: 正在连接信令服务器...');
|
||||
|
||||
try {
|
||||
@@ -104,20 +118,22 @@ class RemoteController {
|
||||
return;
|
||||
}
|
||||
|
||||
_signaling = SignalingClient(serverUrl: serverUrl, token: token);
|
||||
_signaling.onConnected = () {
|
||||
final signaling = SignalingClient(serverUrl: serverUrl, token: token);
|
||||
_signaling = signaling;
|
||||
signaling.onConnected = () {
|
||||
onStatusChanged?.call('状态: 已连接信令服务器,等待注册...');
|
||||
};
|
||||
_signaling.onMessage = _handleSignalMessage;
|
||||
_signaling.onDisconnected = () {
|
||||
signaling.onMessage = _handleSignalMessage;
|
||||
signaling.onDisconnected = () {
|
||||
_myDeviceId = null;
|
||||
onStatusChanged?.call('状态: 已断开连接');
|
||||
onDisconnected?.call();
|
||||
};
|
||||
_signaling.onError = (error) {
|
||||
signaling.onError = (error) {
|
||||
onStatusChanged?.call('状态: 连接错误 - $error');
|
||||
onConnectionFailed?.call(error);
|
||||
};
|
||||
_signaling.onTokenExpired = () async {
|
||||
signaling.onTokenExpired = () async {
|
||||
try {
|
||||
await authRepository.refresh();
|
||||
token = authRepository.accessToken;
|
||||
@@ -127,11 +143,38 @@ class RemoteController {
|
||||
onTokenExpired?.call();
|
||||
}
|
||||
};
|
||||
_signaling.onForceLogout = () {
|
||||
signaling.onForceLogout = () {
|
||||
onStatusChanged?.call('状态: 账号已在其他位置登录,已强制下线');
|
||||
onForceLogout?.call();
|
||||
};
|
||||
_signaling.connect();
|
||||
signaling.connect();
|
||||
}
|
||||
|
||||
/// 发起远程控制:在信令注册成功后调用,建立 WebRTC 并向目标设备创建 Offer。
|
||||
///
|
||||
/// [target] 目标被控端设备ID;[authType]/[authValue] 为被控端鉴权方式。
|
||||
void startControl({
|
||||
required String target,
|
||||
String? authType,
|
||||
String? authValue,
|
||||
}) {
|
||||
if (!registered) {
|
||||
onConnectionFailed?.call('信令未就绪,请稍后重试');
|
||||
return;
|
||||
}
|
||||
if (_webRtc != null) return;
|
||||
targetDeviceId = target;
|
||||
this.authType = authType;
|
||||
this.authValue = authValue;
|
||||
_initWebRtc();
|
||||
}
|
||||
|
||||
/// 停止远程控制但保持信令连接(便于重新选择设备发起连接)。
|
||||
Future<void> stopControl() async {
|
||||
_statsTimer?.cancel();
|
||||
_statsTimer = null;
|
||||
await _webRtc?.close();
|
||||
_webRtc = null;
|
||||
}
|
||||
|
||||
/// 确保 accessToken 有效:若已有则校验,失效则用 refreshToken 刷新。
|
||||
@@ -150,20 +193,22 @@ class RemoteController {
|
||||
}
|
||||
|
||||
void _reconnect() {
|
||||
_signaling.disconnect();
|
||||
_signaling.connect();
|
||||
_signaling?.disconnect();
|
||||
_signaling?.connect();
|
||||
}
|
||||
|
||||
void _initWebRtc() {
|
||||
final deviceId = _myDeviceId;
|
||||
if (deviceId == null) {
|
||||
final signaling = _signaling;
|
||||
final target = targetDeviceId;
|
||||
if (deviceId == null || signaling == null || target == null) {
|
||||
onStatusChanged?.call('状态: 未获取到本机设备ID,连接中止');
|
||||
return;
|
||||
}
|
||||
_webRtc = WebRtcController(
|
||||
signaling: _signaling,
|
||||
signaling: signaling,
|
||||
deviceId: deviceId,
|
||||
targetDeviceId: targetDeviceId,
|
||||
targetDeviceId: target,
|
||||
authType: authType,
|
||||
authValue: authValue,
|
||||
);
|
||||
@@ -195,10 +240,10 @@ class RemoteController {
|
||||
final type = message.type?.toUpperCase();
|
||||
if (type == 'REGISTER_SUCCESS') {
|
||||
// 服务端下发本机 deviceId,作为后续 OFFER 的 fromDeviceId。
|
||||
_myDeviceId = message.fromDeviceId;
|
||||
_initWebRtc();
|
||||
// 拉取可连接的被控端绑定列表(仅已绑定设备)。
|
||||
_loadBindings();
|
||||
final deviceId = message.fromDeviceId;
|
||||
_myDeviceId = deviceId;
|
||||
onStatusChanged?.call('状态: 已连接信令服务器 ($deviceId)');
|
||||
if (deviceId != null) onRegistered?.call(deviceId);
|
||||
// 尝试用服务端 TURN 凭证覆盖默认 ICE 配置。
|
||||
_loadTurnCredentials();
|
||||
return;
|
||||
@@ -239,27 +284,6 @@ class RemoteController {
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// 拉取本机可连接的被控端绑定列表(仅已绑定设备),供 UI 提示。
|
||||
Future<void> _loadBindings() async {
|
||||
try {
|
||||
final data = await authRepository.bindings();
|
||||
final list = (data['bindings'] as List?) ?? [];
|
||||
if (list.isNotEmpty) {
|
||||
final uids = list.map((e) {
|
||||
if (e is Map) {
|
||||
return (e['deviceUid'] ?? e['deviceId'] ?? '').toString();
|
||||
}
|
||||
return e.toString();
|
||||
}).where((s) => s.isNotEmpty).join(', ');
|
||||
if (uids.isNotEmpty) {
|
||||
onStatusChanged?.call('已绑定设备: $uids');
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// 绑定列表拉取失败不影响主流程。
|
||||
}
|
||||
}
|
||||
|
||||
/// 拉取 TURN 短期凭证,覆盖默认 ICE 配置(服务端开启时)。
|
||||
Future<void> _loadTurnCredentials() async {
|
||||
final data = await authRepository.turnCredentials();
|
||||
@@ -297,12 +321,11 @@ class RemoteController {
|
||||
Future<void> sendStreamMode(int mode) =>
|
||||
_webRtc?.sendStreamMode(mode) ?? Future.value();
|
||||
|
||||
/// 断开连接并释放资源。
|
||||
/// 断开连接并释放资源(同时关闭 WebRTC 与信令)。
|
||||
Future<void> disconnect() async {
|
||||
_statsTimer?.cancel();
|
||||
_statsTimer = null;
|
||||
await _webRtc?.close();
|
||||
_webRtc = null;
|
||||
_signaling.disconnect();
|
||||
await stopControl();
|
||||
_signaling?.disconnect();
|
||||
_signaling = null;
|
||||
_myDeviceId = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user