Files
VibeCoding/webrtc_controller_flutter/lib/controller/remote_controller.dart
TongTongStudio 778c37b8db fix: 修复自编码模式黑屏及并发问题
- 修复 MediaCodec 回调需在 configure 前设置导致的异常
- 修复解码器线程安全问题及包乱序/重复处理
- 增加关键帧前重发 SPS/PPS 以防首帧丢失导致黑屏
- 支持分辨率切换时重置解码器
- 修复 Flutter 控制端连接类型选项 UI 显示拥挤问题
2026-07-24 09:31:58 +08:00

196 lines
6.6 KiB
Dart
Raw Permalink 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;
String? authType;
String? authValue;
late final SignalingClient _signaling;
WebRtcController? _webRtc;
Timer? _statsTimer;
/// 信令状态变化(如“正在连接…”、“已连接…”)。
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()? onSelfCodecNotSupported;
RemoteController({
required this.serverUrl,
required this.deviceId,
required this.targetDeviceId,
this.authType,
this.authValue,
});
/// 发起连接:先连接信令服务器,成功后建立 WebRTC 并创建 Offer。
void connect({String? authType, String? authValue}) {
this.authType = authType;
this.authValue = authValue;
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,
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!.onSelfCodecNotSupported = () => onSelfCodecNotSupported?.call();
_webRtc!.initialize().catchError((e) {
onStatusChanged?.call('状态: 连接失败 - $e');
onConnectionFailed?.call(e.toString());
});
}
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 = _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;
}
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();
}
}