Files
VibeCoding/webrtc_controller_flutter/lib/controller/remote_controller.dart

148 lines
4.8 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 '../models/signal_message.dart';
import '../proto/control_message.pb.dart';
import '../signaling/signaling_client.dart';
import '../webrtc/webrtc_controller.dart';
/// 控制端编排器:组合信令客户端与 WebRTC 控制器,
/// 对外暴露连接/断开/发送指令等高层接口(对应 Android 端 MainActivity 的流程)。
class RemoteController {
final String serverUrl;
final String deviceId;
final String targetDeviceId;
late final SignalingClient _signaling;
WebRtcController? _webRtc;
Timer? _statsTimer;
/// 信令状态变化(如“正在连接…”、“已连接…”)。
void Function(String status)? onStatusChanged;
/// WebRTC 连接建立(可开始远程控制)。
void Function()? onConnectionEstablished;
/// 连接断开。
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;
RemoteController({
required this.serverUrl,
required this.deviceId,
required this.targetDeviceId,
});
/// 发起连接:先连接信令服务器,成功后建立 WebRTC 并创建 Offer。
void connect() {
onStatusChanged?.call('状态: 正在连接信令服务器...');
_signaling = SignalingClient(serverUrl: serverUrl, deviceId: deviceId);
_signaling.onConnected = () {
onStatusChanged?.call('状态: 已连接信令服务器,正在发起连接...');
_initWebRtc();
};
_signaling.onMessage = _handleSignalMessage;
_signaling.onDisconnected = () {
onStatusChanged?.call('状态: 已断开连接');
onDisconnected?.call();
};
_signaling.onError = (error) {
onStatusChanged?.call('状态: 连接错误 - $error');
};
_signaling.connect();
}
void _initWebRtc() {
_webRtc = WebRtcController(
signaling: _signaling,
deviceId: deviceId,
targetDeviceId: targetDeviceId,
);
_webRtc!.onConnectionEstablished = () {
onConnectionEstablished?.call();
_startStats();
};
_webRtc!.onDisconnected = () {
onDisconnected?.call();
};
_webRtc!.onIceDisconnected = (message) => onIceDisconnected?.call(message);
_webRtc!.onRemoteStream = (renderer) => onRemoteStream?.call(renderer);
_webRtc!.initialize().catchError((e) {
onStatusChanged?.call('状态: 连接失败 - $e');
});
}
void _handleSignalMessage(SignalMessage message) {
switch (message.type?.toUpperCase()) {
case 'ANSWER':
final payload = jsonDecode(message.payload!) as Map<String, dynamic>;
// 被控端已接受连接请求,进入 WebRTC 协商阶段。
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 = message.payload ?? '连接请求失败';
onStatusChanged?.call('状态: $text');
onConnectionRejected?.call(text);
break;
}
}
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);
}
/// 断开连接并释放资源。
Future<void> disconnect() async {
_statsTimer?.cancel();
_statsTimer = null;
await _webRtc?.close();
_webRtc = null;
_signaling.disconnect();
}
}