fix: 修复自编码模式黑屏及并发问题
- 修复 MediaCodec 回调需在 configure 前设置导致的异常 - 修复解码器线程安全问题及包乱序/重复处理 - 增加关键帧前重发 SPS/PPS 以防首帧丢失导致黑屏 - 支持分辨率切换时重置解码器 - 修复 Flutter 控制端连接类型选项 UI 显示拥挤问题
This commit is contained in:
@@ -27,6 +27,9 @@ class RemoteController {
|
||||
/// WebRTC 连接建立(可开始远程控制)。
|
||||
void Function()? onConnectionEstablished;
|
||||
|
||||
/// 连接失败。
|
||||
void Function(String error)? onConnectionFailed;
|
||||
|
||||
/// 连接断开。
|
||||
void Function()? onDisconnected;
|
||||
|
||||
@@ -48,6 +51,9 @@ class RemoteController {
|
||||
/// 自编码解码纹理已就绪(textureId >= 0)。
|
||||
void Function(int textureId)? onSelfCodecReady;
|
||||
|
||||
/// 自编码解码器已释放(连接断开时)。
|
||||
void Function()? onSelfCodecLost;
|
||||
|
||||
/// 被控端上报当前生效的串流模式。
|
||||
void Function(int mode)? onStreamModeReport;
|
||||
|
||||
@@ -99,17 +105,20 @@ class RemoteController {
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,69 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
String selectedType = 'NONE';
|
||||
final valueController = TextEditingController();
|
||||
|
||||
// 纵向选项列表,避免分段控件在窄弹窗中把中文选项挤压成两行。
|
||||
Widget buildOption(
|
||||
String value,
|
||||
String title,
|
||||
String desc,
|
||||
void Function(void Function()) setDialogState,
|
||||
) {
|
||||
final selected = selectedType == value;
|
||||
return GestureDetector(
|
||||
onTap: () => setDialogState(() => selectedType = value),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? CupertinoColors.activeBlue
|
||||
: CupertinoColors.systemGrey4,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: selected
|
||||
? CupertinoColors.activeBlue.withValues(alpha: 0.06)
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
desc,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: CupertinoColors.systemGrey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
selected
|
||||
? CupertinoIcons.check_mark_circled_solid
|
||||
: CupertinoIcons.circle,
|
||||
color: selected
|
||||
? CupertinoColors.activeBlue
|
||||
: CupertinoColors.systemGrey,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await showCupertinoDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
@@ -134,37 +197,37 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
content: Column(
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
CupertinoSegmentedControl<String>(
|
||||
children: const {
|
||||
'NONE': Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('免密连接'),
|
||||
),
|
||||
'CODE': Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('动态验证码'),
|
||||
),
|
||||
'PASSWORD': Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text('固定密码'),
|
||||
),
|
||||
},
|
||||
groupValue: selectedType,
|
||||
onValueChanged: (v) => setDialogState(() => selectedType = v),
|
||||
buildOption(
|
||||
'NONE',
|
||||
'免密连接',
|
||||
'被控端弹出手动确认框,无需验证码或密码',
|
||||
setDialogState,
|
||||
),
|
||||
buildOption(
|
||||
'CODE',
|
||||
'动态验证码',
|
||||
'使用一次性动态验证码鉴权',
|
||||
setDialogState,
|
||||
),
|
||||
buildOption(
|
||||
'PASSWORD',
|
||||
'固定密码',
|
||||
'使用固定连接密码鉴权',
|
||||
setDialogState,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
CupertinoTextField(
|
||||
controller: valueController,
|
||||
enabled: selectedType != 'NONE',
|
||||
placeholder: selectedType == 'NONE'
|
||||
? '免密连接,无需填写'
|
||||
: selectedType == 'PASSWORD'
|
||||
? '请输入固定密码'
|
||||
: '请输入动态验证码',
|
||||
obscureText: true,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (selectedType == 'NONE')
|
||||
const Text(
|
||||
'免密连接:被控端将弹出手动确认对话框,无需输入验证码或密码。',
|
||||
style: TextStyle(fontSize: 13, color: CupertinoColors.systemGrey),
|
||||
)
|
||||
else
|
||||
CupertinoTextField(
|
||||
controller: valueController,
|
||||
placeholder: selectedType == 'PASSWORD' ? '请输入固定密码' : '请输入动态验证码',
|
||||
obscureText: true,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
@@ -245,6 +308,14 @@ class _ControllerHomeState extends State<ControllerHome> {
|
||||
});
|
||||
}
|
||||
};
|
||||
_controller!.onSelfCodecLost = () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selfCodecReady = false;
|
||||
_selfCodecTextureId = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
_controller!.onStreamModeReport = (mode) {
|
||||
if (mounted) setState(() => _streamMode = mode);
|
||||
};
|
||||
|
||||
@@ -217,7 +217,13 @@ class WebRtcController {
|
||||
switch (msg.action) {
|
||||
case Action.REPORT_STREAM_MODE:
|
||||
_currentStreamMode = msg.streamMode;
|
||||
_selfCodecDecoder?.setEnabled(msg.streamMode == streamModeSelfCodec);
|
||||
if (msg.streamMode == streamModeSelfCodec) {
|
||||
_ensureSelfCodecDecoder().then((_) {
|
||||
_selfCodecDecoder?.setEnabled(true);
|
||||
});
|
||||
} else {
|
||||
_selfCodecDecoder?.setEnabled(false);
|
||||
}
|
||||
onStreamModeReport?.call(msg.streamMode);
|
||||
break;
|
||||
case Action.REPORT_RESOLUTION:
|
||||
|
||||
Reference in New Issue
Block a user