import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; /// 自编码解码器(控制端)。 /// /// 接收来自 `video` DataChannel 的二进制分片(由被控端 [SelfCodecEncoder] 产生), /// 通过原生 Android `MediaCodec` 硬解码,并渲染到 Flutter 纹理(`Texture`)。 /// /// 为保证与 `WebRTCControlled` 的编码器在二进制协议上完全一致,协议的 /// 分片重组、H.264 参数集解析与硬解、纹理渲染全部在原生侧完成; /// 本类仅负责与原生侧通信。对应 Android 端 `SelfCodecDecoder`。 /// /// 注意:原生解码器目前仅在 Android 平台实现。其它平台(Windows / Linux / /// Web / iOS)调用 [create] 会失败并回调 [onError],调用方应回退到 /// WebRTC 默认媒体流。 class SelfCodecDecoder { static const MethodChannel _channel = MethodChannel('com.ttstd.fluttercontroller/self_codec'); /// 串流模式常量(与 ControlMessage.stream_mode 对齐)。 static const int streamModeWebRtc = 0; static const int streamModeSelfCodec = 1; int? _textureId; bool _disposed = false; bool _handlerSet = false; /// 解码输出尺寸变化(宽/高,单位像素)。 final void Function(int width, int height)? onSize; /// 错误回调(如当前平台不支持硬解)。 final void Function(String error)? onError; SelfCodecDecoder({this.onSize, this.onError}) { _channel.setMethodCallHandler(_handleNativeCall); _handlerSet = true; } Future _handleNativeCall(MethodCall call) async { switch (call.method) { case 'onSize': final args = call.arguments; final w = args is Map ? (args['width'] as int? ?? 0) : 0; final h = args is Map ? (args['height'] as int? ?? 0) : 0; if (w > 0 && h > 0) onSize?.call(w, h); break; case 'onError': final msg = call.arguments is String ? call.arguments as String : ''; if (msg.isNotEmpty) onError?.call(msg); break; } } /// 创建解码纹理。成功返回纹理 id(>=0),失败返回 null。 Future create() async { if (_disposed) return null; try { final id = await _channel.invokeMethod('create'); if (id == null || id < 0) return null; _textureId = id; return _textureId; } on PlatformException catch (e) { onError?.call('创建自编码解码纹理失败: ${e.message}'); return null; } catch (e) { onError?.call('创建自编码解码纹理失败: $e'); return null; } } /// 已创建的纹理 id;未创建或已释放时为 null。 int? get textureId => _textureId; /// 用于渲染解码画面的纹理控件。 Widget build(BuildContext context) { final id = _textureId; if (id == null) return const SizedBox.shrink(); return Texture(textureId: id); } /// 喂入来自 video DataChannel 的二进制分片。 Future feed(Uint8List chunk) async { if (_disposed || _textureId == null) return; try { await _channel.invokeMethod('feed', chunk); } catch (_) { // 解码未就绪或被释放时忽略。 } } /// 启用/停用解码(仅自编码模式为 true)。 Future setEnabled(bool enabled) async { if (_disposed || _textureId == null) return; try { await _channel.invokeMethod('setEnabled', enabled); } catch (_) { // 忽略。 } } /// 读取解码耗时统计。返回包含以下键的 Map(毫秒),失败返回 null: /// - lastMs:最近一帧解码耗时; /// - avgMs:最近窗口内的平均解码耗时; /// - maxMs:峰值解码耗时; /// - count:累计已解码帧数。 Future?> getStats() async { if (_disposed || _textureId == null) return null; try { final res = await _channel.invokeMethod('getStats'); if (res is Map) return res.cast(); } catch (_) { // 解码未就绪或被释放时忽略。 } return null; } /// 释放原生解码资源。重复调用安全。 Future dispose() async { if (_disposed) return; _disposed = true; if (_handlerSet) { _channel.setMethodCallHandler(null); _handlerSet = false; } try { await _channel.invokeMethod('dispose'); } catch (_) { // 忽略。 } _textureId = null; } }