Files
VibeCoding/webrtc_controller_flutter/lib/features/connection/data/remote_controller.dart
tongtongstudio d5e66a1777 docs(webrtc_controller_flutter): 更新项目文档以反映重构后的架构
AGENTS.md 与 README.md 同步更新:根据实际代码结构重写目录树、技术栈、架构分层及编码规范,移除旧版内联示例并补充新的开发约定与代码生成命令。
2026-08-03 16:12:44 +08:00

309 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'dart:convert';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import '../../../core/network/api_exception.dart';
import '../../../core/proto/control_message.pb.dart';
import '../../auth/domain/auth_repository.dart';
import '../domain/signal_message.dart';
import 'signaling_client.dart';
import 'webrtc_controller.dart';
/// 控制端编排器:组合信令客户端与 WebRTC 控制器,
/// 对外暴露连接/断开/发送指令等高层接口(对应 Android 端 MainActivity 的流程)。
class RemoteController {
final String serverUrl;
final String targetDeviceId;
final AuthRepository authRepository;
String? token;
String? authType;
String? authValue;
late final SignalingClient _signaling;
WebRtcController? _webRtc;
Timer? _statsTimer;
/// 本地设备ID由服务端 REGISTER_SUCCESS 下发)。
String? _myDeviceId;
/// 信令状态变化(如"正在连接…"、"已连接…)。
void Function(String status)? onStatusChanged;
/// WebRTC 连接建立(可开始远程控制)。
void Function()? onConnectionEstablished;
/// 连接失败。
void Function(String error)? onConnectionFailed;
/// 连接断开。
void Function()? onDisconnected;
/// ICE 连接断开(用于提示用户并返回连接设置)。
void Function(String message)? onIceDisconnected;
/// 目标被控端不在线(服务器回送 TARGET_OFFLINE用于提示用户并复位 UI
void Function(String message)? onTargetOffline;
/// 被控端拒绝了连接请求(用于提示用户并复位 UI
void Function(String message)? onConnectionRejected;
/// 远端视频渲染器就绪。
void Function(RTCVideoRenderer renderer)? onRemoteStream;
/// 统计信息刷新(每秒一次)。
void Function(String stats)? onStats;
/// 自编码解码纹理已就绪textureId >= 0
void Function(int textureId)? onSelfCodecReady;
/// 自编码解码器已释放(连接断开时)。
void Function()? onSelfCodecLost;
/// 被控端上报当前生效的串流模式。
void Function(int mode)? onStreamModeReport;
/// 被控端/解码器上报分辨率。
void Function(int width, int height)? onResolutionReported;
/// 被控端上报当前采集帧率与支持的帧率档位。
void Function(int width, int height, int fps, List<int> supportedFps)?
onFpsReport;
/// 当前平台不支持原生硬解时回调。
void Function()? onSelfCodecNotSupported;
/// 令牌失效4001用于触发刷新重连。
void Function()? onTokenExpired;
/// 强制下线4003用于跳回登录。
void Function()? onForceLogout;
RemoteController({
required this.serverUrl,
required this.targetDeviceId,
required this.authRepository,
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;
onStatusChanged?.call('状态: 正在连接信令服务器...');
try {
token = await _ensureToken();
} catch (e) {
onStatusChanged?.call('状态: 认证失败 - $e');
onConnectionFailed?.call(e.toString());
return;
}
_signaling = SignalingClient(serverUrl: serverUrl, token: token);
_signaling.onConnected = () {
onStatusChanged?.call('状态: 已连接信令服务器,等待注册...');
};
_signaling.onMessage = _handleSignalMessage;
_signaling.onDisconnected = () {
onStatusChanged?.call('状态: 已断开连接');
onDisconnected?.call();
};
_signaling.onError = (error) {
onStatusChanged?.call('状态: 连接错误 - $error');
onConnectionFailed?.call(error);
};
_signaling.onTokenExpired = () async {
try {
await authRepository.refresh();
token = authRepository.accessToken;
_reconnect();
} catch (e) {
onStatusChanged?.call('状态: 令牌刷新失败 - $e');
onTokenExpired?.call();
}
};
_signaling.onForceLogout = () {
onStatusChanged?.call('状态: 账号已在其他位置登录,已强制下线');
onForceLogout?.call();
};
_signaling.connect();
}
/// 确保 accessToken 有效:若已有则校验,失效则用 refreshToken 刷新。
Future<String> _ensureToken() async {
final existing = authRepository.accessToken;
if (existing != null) {
try {
await authRepository.verify();
return existing;
} on ApiException catch (e) {
if (e.httpCode != 401) return existing;
}
}
final data = await authRepository.refresh();
return data['accessToken'] as String;
}
void _reconnect() {
_signaling.disconnect();
_signaling.connect();
}
void _initWebRtc() {
final deviceId = _myDeviceId;
if (deviceId == null) {
onStatusChanged?.call('状态: 未获取到本机设备ID连接中止');
return;
}
_webRtc = WebRtcController(
signaling: _signaling,
deviceId: deviceId,
targetDeviceId: targetDeviceId,
authType: authType,
authValue: authValue,
);
_webRtc!.onConnectionEstablished = () {
onConnectionEstablished?.call();
_startStats();
};
_webRtc!.onConnectionFailed = (error) => onConnectionFailed?.call(error);
_webRtc!.onDisconnected = () {
onDisconnected?.call();
};
_webRtc!.onIceDisconnected = (message) => onIceDisconnected?.call(message);
_webRtc!.onRemoteStream = (renderer) => onRemoteStream?.call(renderer);
_webRtc!.onSelfCodecReady = (id) => onSelfCodecReady?.call(id);
_webRtc!.onSelfCodecLost = () => onSelfCodecLost?.call();
_webRtc!.onStreamModeReport = (mode) => onStreamModeReport?.call(mode);
_webRtc!.onResolutionReported = (w, h) => onResolutionReported?.call(w, h);
_webRtc!.onFpsReport =
(w, h, fps, list) => onFpsReport?.call(w, h, fps, list);
_webRtc!.onSelfCodecNotSupported = () => onSelfCodecNotSupported?.call();
onStatusChanged?.call('状态: 已注册 ($deviceId),正在发起连接...');
_webRtc!.initialize().catchError((e) {
onStatusChanged?.call('状态: 连接失败 - $e');
onConnectionFailed?.call(e.toString());
});
}
void _handleSignalMessage(SignalMessage message) {
final type = message.type?.toUpperCase();
if (type == 'REGISTER_SUCCESS') {
// 服务端下发本机 deviceId作为后续 OFFER 的 fromDeviceId。
_myDeviceId = message.fromDeviceId;
_initWebRtc();
// 拉取可连接的被控端绑定列表(仅已绑定设备)。
_loadBindings();
// 尝试用服务端 TURN 凭证覆盖默认 ICE 配置。
_loadTurnCredentials();
return;
}
switch (type) {
case 'ANSWER':
final payload = jsonDecode(message.payload!) as Map<String, dynamic>;
onStatusChanged?.call('状态: 被控端已接受连接,正在建立连接...');
_webRtc?.handleAnswer(payload['sdp'] as String);
break;
case 'ICE_CANDIDATE':
final payload = jsonDecode(message.payload!) as Map<String, dynamic>;
_webRtc?.handleIceCandidate(payload);
break;
case 'TARGET_OFFLINE':
final text = message.payload ?? '目标被控端不在线,请确认设备已开启并连接服务器';
onStatusChanged?.call('状态: $text');
onTargetOffline?.call(text);
break;
case 'CONNECTION_REJECTED':
case 'REQUEST_ERROR':
case 'REQUEST_TIMEOUT':
final text = _parseRejectReason(message.payload);
onStatusChanged?.call('状态: $text');
onConnectionRejected?.call(text);
break;
}
}
/// 解析拒绝原因:优先读取 JSON 中的 reason 字段,否则直接返回原文。
String _parseRejectReason(String? payload) {
if (payload == null || payload.isEmpty) return '连接请求失败';
try {
final map = jsonDecode(payload) as Map<String, dynamic>;
final r = map['reason'];
if (r is String && r.isNotEmpty) return r;
} catch (_) {}
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();
if (data != null && data['iceServers'] is List) {
final servers = (data['iceServers'] as List)
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
if (servers.isNotEmpty) {
WebRtcController.iceServersOverride = servers;
}
}
}
void _startStats() {
_statsTimer?.cancel();
_statsTimer = Timer.periodic(const Duration(seconds: 1), (_) async {
final text = await _webRtc?.getStatsText();
if (text != null && text.isNotEmpty) {
onStats?.call(text);
}
});
}
/// 发送控制指令protobuf 二进制)。
void sendControlCommand(ControlMessage command) {
_webRtc?.sendControlCommand(command);
}
/// 请求被控端切换屏幕采集分辨率。
void sendResolutionChange(int width, int height, int fps) {
_webRtc?.sendResolutionChange(width, height, fps);
}
/// 请求被控端切换屏幕串流模式0=WebRTC 全托管 / 1=自编码)。
Future<void> sendStreamMode(int mode) =>
_webRtc?.sendStreamMode(mode) ?? Future.value();
/// 断开连接并释放资源。
Future<void> disconnect() async {
_statsTimer?.cancel();
_statsTimer = null;
await _webRtc?.close();
_webRtc = null;
_signaling.disconnect();
}
}