From 705ba72e31a12307a4120f92b9a18a3abc17d864 Mon Sep 17 00:00:00 2001 From: sky_min Date: Tue, 11 Nov 2025 16:15:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=86=E9=A2=91=E5=AF=B9?= =?UTF-8?q?=E8=AE=B2=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockDetail/lockDetail_logic.dart | 26 +++ .../lockDetail/lockDetail_state.dart | 3 + .../native/talk_view_native_decode_logic.dart | 184 +++++++++++++----- 3 files changed, 159 insertions(+), 54 deletions(-) diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 91e64e2f..27089bfd 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -804,6 +804,8 @@ class LockDetailLogic extends BaseGetXController { } } + // 添加网络质量评估变量 + int _networkQualityScore = 5; // 1-5分,5为最佳 /// 发送监控消息 void sendMonitorMessage() async { final catEyeConfig = state.keyInfos.value.lockSetting?.catEyeConfig ?? []; @@ -817,6 +819,30 @@ class LockDetailLogic extends BaseGetXController { return; } } + // 根据网络质量设置提示信息 + switch (_networkQualityScore) { + case 1: + state.networkQualityMessage.value = '很差'; + break; + case 2: + state.networkQualityMessage.value = '较差'; + break; + case 3: + state.networkQualityMessage.value = '一般'; + break; + case 4: + state.networkQualityMessage.value = '良好'; + break; + case 5: + state.networkQualityMessage.value = '优秀'; + break; + default: + state.networkQualityMessage.value = ''; + } + // 根据网络质量设置提示信息 + if (_networkQualityScore < 4) { + showToast('网络质量'.tr + ':${state.networkQualityMessage.value}'); + } if (catEyeConfig.isNotEmpty && catEyeConfig.length > 0 && catEyeConfig[0].catEyeMode != 0) { if (network == null || network?.peerId == null || network?.peerId == '') { showToast('设备未配网'.tr); diff --git a/lib/main/lockDetail/lockDetail/lockDetail_state.dart b/lib/main/lockDetail/lockDetail/lockDetail_state.dart index eb4b6d30..e388dd96 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_state.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_state.dart @@ -17,6 +17,9 @@ class LockDetailState { StreamSubscription? DetailLockInfo; StreamSubscription? SuccessfulDistributionNetworkEvent; + // 添加网络质量状态变量 + final RxInt networkQualityScore = 1.obs; // 1-5分,5为最佳 + final RxString networkQualityMessage = ''.obs; // 网络质量提示信息 String lockNetToken = '0'; int differentialTime = 0; // 服务器时间与本地时间差值 bool isHaveNetwork = true; diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart index 5ecd4db9..f4376135 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart @@ -68,6 +68,15 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { final targetFps = state.targetFps.toDouble(); final actualRatio = _actualFps / targetFps; + // 增加中间档位的调整,避免帧率波动过大 + if (actualRatio < 0.3) { + state.targetFps = (targetFps * 0.5).round().clamp(15, 60); + _startFrameProcessTimer(); + } else if (actualRatio < 0.5) { + state.targetFps = (targetFps * 0.7).round().clamp(15, 60); + _startFrameProcessTimer(); + } + // 更加保守和稳定的调整策略 // iOS平台使用更保守的调整策略 if (Platform.isIOS) { @@ -85,7 +94,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _startFrameProcessTimer(); } } else { - // Android平台原有逻辑 + // Android平台 if (actualRatio < 0.4) { state.targetFps = (targetFps * 0.6).round().clamp(15, 60); _startFrameProcessTimer(); @@ -108,13 +117,13 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { : 0.0; // 根据丢包率评估网络质量 - if (dropRate > 0.3) { + if (dropRate > 0.25) { _networkQualityScore = 1; // 很差 - } else if (dropRate > 0.2) { + } else if (dropRate > 0.15) { _networkQualityScore = 2; // 较差 - } else if (dropRate > 0.1) { + } else if (dropRate > 0.08) { _networkQualityScore = 3; // 一般 - } else if (dropRate > 0.05) { + } else if (dropRate > 0.03) { _networkQualityScore = 4; // 良好 } else { _networkQualityScore = 5; // 优秀 @@ -269,9 +278,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { final startTime = DateTime.now().millisecondsSinceEpoch; // 使用超时控制避免长时间等待 - // iOS平台使用更短的超时时间 - final timeoutSeconds = Platform.isIOS ? 1 : 1; - final timeoutFuture = Future.delayed(Duration(seconds: timeoutSeconds), () => null); + // 设置超时时间为500ms,避免过长等待 + final timeoutFuture = Future.delayed(Duration(milliseconds: 500), () => null); final decoderFuture = VideoDecodePlugin.initDecoder(config); // 初始化解码器并获取textureId @@ -296,16 +304,14 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } // 启动定时器发送帧数据 // 延迟启动帧处理定时器,让用户感觉更快 - Future.delayed(Duration(milliseconds: 100), () { + Future.delayed(Duration(milliseconds: 50), () { _startFrameProcessTimer(); }); } catch (e) { AppLog.log('初始化视频解码器错误: $e'); state.isLoading.value = false; - // 如果初始化失败,延迟后重试 - // iOS平台使用更短的重试延迟 - final delaySeconds = Platform.isIOS ? 0.5 : 1; - await Future.delayed(Duration(milliseconds: (delaySeconds * 1000).toInt())); + // 如果初始化失败,延迟后重试 --缩短重试延迟 + await Future.delayed(Duration(milliseconds: 300)); if (!Get.isRegistered()) { return; } @@ -537,8 +543,25 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } } - // 查找最适合处理的帧 + // 查找最适合处理的帧 -- 优先处理I帧以加速首帧显示 int frameIndex = -1; + // 首先查找最早的I帧 + final iFrames = state.h264FrameBuffer + .where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I) + .toList() + ..sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); + + if (iFrames.isNotEmpty) { + final minIFrame = iFrames.first; + frameIndex = state.h264FrameBuffer.indexWhere( + (f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.I && + f['frameSeq'] == minIFrame['frameSeq'], + ); + } else { + // 如果没有I帧,处理最早的帧 + frameIndex = 0; + } // 优先处理与最近解码的I帧相关的P帧 if (lastDecodedIFrameSeq != null) { @@ -897,44 +920,103 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { @override void onClose() { + try { + // 先停止所有处理流程 + _stopAllProcessing(); + + // 释放视频相关资源 + _releaseVideoResources(); + + // 释放音频相关资源 + _releaseAudioResources(); + + // 释放网络和状态相关资源 + _releaseNetworkAndStateResources(); + + // 清理定时器和订阅 + _cancelAllTimersAndSubscriptions(); + + } catch (e, stackTrace) { + AppLog.log('资源释放过程中出现错误: $e\n$stackTrace'); + } finally { + super.onClose(); + } + } + + /// 停止所有处理流程 + void _stopAllProcessing() { // 停止帧处理定时器 _stopFrameProcessTimer(); - _stopPlayG711Data(); // 停止播放音频 - - state.audioBuffer.clear(); // 清空音频缓冲区 - - state.oneMinuteTimeTimer?.cancel(); - state.oneMinuteTimeTimer = null; - - // 停止播放音频 + // 停止音频处理 stopProcessingAudio(); - state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 - state.oneMinuteTimeTimer = null; // 取消旧定时器 - state.oneMinuteTime.value = 0; + // 停止播放音频 + _stopPlayG711Data(); + } - // 异步释放视频解码器资源 - _releaseVideoDecoderAsync(); + /// 释放视频相关资源 + void _releaseVideoResources() { + try { + // 异步释放视频解码器资源 + _releaseVideoDecoderAsync(); + } catch (e) { + AppLog.log('释放视频解码器资源失败: $e'); + } + } - // 取消数据流监听 - _streamSubscription?.cancel(); - _isListening = false; + /// 释放音频相关资源 + void _releaseAudioResources() { + try { + // 停止播放音频 + _stopPlayG711Data(); - // 重置期望数据 - StartChartManage().reSetDefaultTalkExpect(); - StartChartManage().stopTalkExpectMessageTimer(); + // 清空音频缓冲区 + state.audioBuffer.clear(); - // 取消批处理定时器 - _batchProcessTimer?.cancel(); - _batchProcessTimer = null; + // 清空音频帧缓冲 + _bufferedAudioFrames.clear(); + } catch (e) { + AppLog.log('释放音频资源失败: $e'); + } + } - // 清空已解码I帧集合 - _decodedIFrames.clear(); - _startProcessingAudioTimer?.cancel(); - _startProcessingAudioTimer = null; - _bufferedAudioFrames.clear(); - super.onClose(); + /// 释放网络和状态相关资源 + void _releaseNetworkAndStateResources() { + try { + // 重置期望数据 + StartChartManage().reSetDefaultTalkExpect(); + StartChartManage().stopTalkExpectMessageTimer(); + + // 清空已解码I帧集合 + _decodedIFrames.clear(); + + // 重置状态 + state.oneMinuteTimeTimer?.cancel(); + state.oneMinuteTimeTimer = null; + state.oneMinuteTime.value = 0; + } catch (e) { + AppLog.log('释放网络和状态资源失败: $e'); + } + } + + /// 取消所有定时器和订阅 + void _cancelAllTimersAndSubscriptions() { + try { + // 取消数据流监听 + _streamSubscription?.cancel(); + _isListening = false; + + // 取消批处理定时器 + _batchProcessTimer?.cancel(); + _batchProcessTimer = null; + + // 取消音频处理定时器 + _startProcessingAudioTimer?.cancel(); + _startProcessingAudioTimer = null; + } catch (e) { + AppLog.log('取消定时器和订阅失败: $e'); + } } /// 异步释放视频解码器 Future _releaseVideoDecoderAsync() async { @@ -1191,14 +1273,14 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _pendingResetWidth = width; _pendingResetHeight = height; - // 并行执行两个操作以提高效率 + // 并行执行两个操作以提高效率---使用更短的总超时时间 await Future.wait([ // 立即重置解码器 _resetDecoderForNewStream(width, height), // 修改发送预期数据 Future.microtask(() => StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq)) - ]).timeout(const Duration(seconds: 2)); // 设置总超时时间 + ]).timeout(const Duration(milliseconds: 1500)); // 设置总超时时间 } void _initHdOptions() { @@ -1222,13 +1304,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 快速清理缓冲区以加快响应 _clearFrameBufferQuickly(); - // 释放旧解码器 - 使用最短超时时间 + // 释放旧解码器 - 使用最短超时时间 - 减少不必要的延时 if (state.textureId.value != null) { try { // 极短超时时间,避免阻塞 - // iOS平台使用更短的超时时间 - final timeoutMs = Platform.isIOS ? 200 : 300; - await VideoDecodePlugin.releaseDecoder().timeout(Duration(milliseconds: timeoutMs)); + await VideoDecodePlugin.releaseDecoder().timeout(Duration(milliseconds: 100)); state.textureId.value = null; } catch (e) { AppLog.log('释放解码器超时或失败: $e'); @@ -1238,9 +1318,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } // 最小化等待时间 - // iOS平台使用更短的等待时间 - final delayMs = Platform.isIOS ? 0 : 1; - await Future.delayed(Duration(milliseconds: delayMs)); + await Future.delayed(Duration(milliseconds: 0)); // 创建新的解码器配置 final config = VideoDecoderConfig( @@ -1252,10 +1330,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 初始化新解码器 try { // 使用较短超时时间 - // iOS平台使用更短的超时时间 - final timeoutMs = Platform.isIOS ? 1000 : 1500; final textureId = await VideoDecodePlugin.initDecoder(config) - .timeout(Duration(milliseconds: timeoutMs)); + .timeout(Duration(milliseconds: 800)); if (textureId != null) { state.textureId.value = textureId;