119 lines
3.8 KiB
Dart
119 lines
3.8 KiB
Dart
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<dynamic> _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<int?> create() async {
|
||
if (_disposed) return null;
|
||
try {
|
||
final id = await _channel.invokeMethod<int>('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<void> feed(Uint8List chunk) async {
|
||
if (_disposed || _textureId == null) return;
|
||
try {
|
||
await _channel.invokeMethod<void>('feed', chunk);
|
||
} catch (_) {
|
||
// 解码未就绪或被释放时忽略。
|
||
}
|
||
}
|
||
|
||
/// 启用/停用解码(仅自编码模式为 true)。
|
||
Future<void> setEnabled(bool enabled) async {
|
||
if (_disposed || _textureId == null) return;
|
||
try {
|
||
await _channel.invokeMethod<void>('setEnabled', enabled);
|
||
} catch (_) {
|
||
// 忽略。
|
||
}
|
||
}
|
||
|
||
/// 释放原生解码资源。重复调用安全。
|
||
Future<void> dispose() async {
|
||
if (_disposed) return;
|
||
_disposed = true;
|
||
if (_handlerSet) {
|
||
_channel.setMethodCallHandler(null);
|
||
_handlerSet = false;
|
||
}
|
||
try {
|
||
await _channel.invokeMethod<void>('dispose');
|
||
} catch (_) {
|
||
// 忽略。
|
||
}
|
||
_textureId = null;
|
||
}
|
||
}
|