From 90f94e1a9ac2eb5d878749f3da21bd534923cfb1 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:46:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E5=AE=8C=E6=88=90=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=B8=85=E6=99=B0=E5=BA=A6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native/talk_view_native_decode_logic.dart | 257 ++++++++++++++---- .../native/talk_view_native_decode_page.dart | 122 ++++++--- .../native/talk_view_native_decode_state.dart | 3 + 3 files changed, 291 insertions(+), 91 deletions(-) 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 af05d336..606ae73c 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 @@ -35,6 +35,8 @@ import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_st import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; import 'package:star_lock/tools/G711Tool.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; +import 'package:star_lock/tools/commonDataManage.dart'; +import 'package:star_lock/tools/storage.dart'; import 'package:video_decode_plugin/video_decode_plugin.dart'; import '../../../../tools/baseGetXController.dart'; @@ -75,6 +77,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 新增:记录上一个已接收的frameSeq int? _lastFrameSeq; + // 新增:frameSeq回绕检测标志 + bool _pendingStreamReset = false; + + // 新增:记录切换时的宽高参数 + int _pendingResetWidth = 864; + int _pendingResetHeight = 480; + + // 新增:等待新I帧状态 + bool _waitingForIFrame = false; + // 初始化视频解码器 Future _initVideoDecoder() async { try { @@ -89,12 +101,12 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 初始化解码器并获取textureId final textureId = await VideoDecodePlugin.initDecoder(config); if (textureId != null) { - state.textureId.value = textureId; + Future.microtask(() => state.textureId.value = textureId); AppLog.log('视频解码器初始化成功:textureId=$textureId'); - VideoDecodePlugin.setOnFrameRenderedListener((textureId) { - state.isLoading.value = false; AppLog.log('已经开始渲染======='); + // 只有真正渲染出首帧时才关闭loading + Future.microtask(() => state.isLoading.value = false); }); } else { AppLog.log('视频解码器初始化失败'); @@ -146,12 +158,53 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int frameSeq, int frameSeqI, ) { - // 只允许frameSeq严格递增,乱序或重复帧直接丢弃 - if (_lastFrameSeq != null && frameSeq <= _lastFrameSeq!) { - AppLog.log('丢弃乱序或重复帧: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); - return; + // 检测frameSeq回绕,且为I帧 + if (!_pendingStreamReset && + _lastFrameSeq != null && + frameType == TalkDataH264Frame_FrameTypeE.I && + frameSeq < _lastFrameSeq!) { + // 检测到新流I帧,进入loading并重置所有本地状态 + AppLog.log( + '检测到新流I帧,frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + Future.microtask(() => state.isLoading.value = true); + _pendingStreamReset = true; + // 先暂停帧处理定时器,防止竞态 + _stopFrameProcessTimer(); + // 先释放并重新初始化解码器 + _resetDecoderForNewStream(_pendingResetWidth, _pendingResetHeight); + // 重置所有本地状态 + _lastFrameSeq = null; + _decodedIFrames.clear(); + state.h264FrameBuffer.clear(); + // 再恢复帧处理定时器 + _startFrameProcessTimer(); + // 不return,直接用该I帧初始化解码器并解码 + // 继续往下执行 + } + // 如果处于pendingStreamReset,等待新I帧 + if (_pendingStreamReset) { + if (frameType == TalkDataH264Frame_FrameTypeE.I) { + // 收到新流I帧,关闭loading,恢复正常解码 + AppLog.log('收到新流I帧,关闭loading: frameSeq=$frameSeq'); + //Future.microtask(() => state.isLoading.value = false); + _pendingStreamReset = false; + _lastFrameSeq = frameSeq; + _decodedIFrames.clear(); + _decodedIFrames.add(frameSeq); + // 继续往下执行,直接用该I帧解码 + } else { + // 等待新流I帧期间,丢弃所有非I帧 + AppLog.log('等待新流I帧,丢弃非I帧: frameSeq=$frameSeq, frameType=$frameType'); + return; + } + } else { + // 正常流程 + if (_lastFrameSeq != null && frameSeq <= _lastFrameSeq!) { + AppLog.log('丢弃乱序或重复帧: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + return; + } + _lastFrameSeq = frameSeq; } - _lastFrameSeq = frameSeq; // 创建包含帧数据和类型的Map final Map frameMap = { 'frameData': frameData, @@ -163,8 +216,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 如果缓冲区超出最大大小,优先丢弃P/B帧 while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) { - int pbIndex = state.h264FrameBuffer.indexWhere((f) => - f['frameType'] == TalkDataH264Frame_FrameTypeE.P); + int pbIndex = state.h264FrameBuffer + .indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P); if (pbIndex != -1) { state.h264FrameBuffer.removeAt(pbIndex); } else { @@ -209,29 +262,31 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { try { // 取出最早的帧 - final Map frameMap = state.h264FrameBuffer.removeAt(0); - final List frameData = frameMap['frameData']; - final TalkDataH264Frame_FrameTypeE frameType = frameMap['frameType']; - final int frameSeq = frameMap['frameSeq']; - final int frameSeqI = frameMap['frameSeqI']; - int pts = frameMap['pts']; - // int pts = DateTime.now().millisecondsSinceEpoch; - - if (frameType == TalkDataH264Frame_FrameTypeE.P) { - // 以frameSeqI为I帧序号标识 - if (!(_decodedIFrames.contains(frameSeqI))) { - AppLog.log('丢弃P帧:未收到对应I帧,frameSeqI=${frameSeqI}'); - return; - } - } else if (frameType == TalkDataH264Frame_FrameTypeE.I) { - // 记录已解码I帧序号 - _decodedIFrames.add(frameSeq); + final Map? frameMap = state.h264FrameBuffer.isNotEmpty + ? state.h264FrameBuffer.removeAt(0) + : null; + if (frameMap == null) { + state.isProcessingFrame = false; + return; + } + final List? frameData = frameMap['frameData']; + final TalkDataH264Frame_FrameTypeE? frameType = frameMap['frameType']; + final int? frameSeq = frameMap['frameSeq']; + final int? frameSeqI = frameMap['frameSeqI']; + final int? pts = frameMap['pts']; + if (frameData == null || + frameType == null || + frameSeq == null || + frameSeqI == null || + pts == null) { + state.isProcessingFrame = false; + return; + } + // 解码器未初始化或textureId为null时跳过 + if (state.textureId.value == null) { + state.isProcessingFrame = false; + return; } - // 实时写入h264文件 - // _appendH264FrameToFile(frameData, frameType); - - // final timestamp = DateTime.now().millisecondsSinceEpoch; - // final timestamp64 = timestamp is int ? timestamp : timestamp.toInt(); await VideoDecodePlugin.sendFrame( frameData: frameData, frameType: frameType == TalkDataH264Frame_FrameTypeE.I ? 0 : 1, @@ -462,6 +517,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 初始化视频解码器 _initVideoDecoder(); + _initHdOptions(); // 初始化H264帧缓冲区 state.h264FrameBuffer.clear(); state.isProcessingFrame = false; @@ -490,7 +546,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 释放视频解码器资源 if (state.textureId.value != null) { VideoDecodePlugin.releaseDecoder(); - state.textureId.value = null; + Future.microtask(() => state.textureId.value = null); } // 取消数据流监听 @@ -577,36 +633,42 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } } - // 远程开锁 +// 远程开锁 Future remoteOpenLock() async { + final LockListInfoItemEntity currentKeyInfo = + CommonDataManage().currentKeyInfo; + + var lockId = currentKeyInfo.lockId ?? 0; + var remoteUnlock = currentKeyInfo.lockSetting?.remoteUnlock ?? 0; + final lockPeerId = StartChartManage().lockPeerId; - final lockListPeerId = StartChartManage().lockListPeerId; - int lockId = lockDetailState.keyInfos.value.lockId ?? 0; - - // 如果锁列表获取到peerId,代表有多个锁,使用锁列表的peerId - // 从列表中遍历出对应的peerId - lockListPeerId.forEach((element) { - if (element.network?.peerId == lockPeerId) { - lockId = element.lockId ?? 0; - } - }); - - final LockSetInfoEntity lockSetInfoEntity = - await ApiRepository.to.getLockSettingInfoData( - lockId: lockId.toString(), - ); - if (lockSetInfoEntity.errorCode!.codeIsSuccessful) { - if (lockSetInfoEntity.data?.lockFeature?.remoteUnlock == 1 && - lockSetInfoEntity.data?.lockSettingInfo?.remoteUnlock == 1) { - final LoginEntity entity = await ApiRepository.to - .remoteOpenLock(lockId: lockId.toString(), timeOut: 60); - if (entity.errorCode!.codeIsSuccessful) { - showToast('已开锁'.tr); - StartChartManage().lockListPeerId = []; + final LockListInfoGroupEntity? lockListInfoGroupEntity = + await Storage.getLockMainListData(); + if (lockListInfoGroupEntity != null) { + lockListInfoGroupEntity!.groupList?.forEach((element) { + final lockList = element.lockList; + if (lockList != null && lockList.length != 0) { + for (var lockInfo in lockList) { + final peerId = lockInfo.network?.peerId; + if (peerId != null && peerId != '') { + if (peerId == lockPeerId) { + lockId = lockInfo.lockId ?? 0; + remoteUnlock = lockInfo.lockSetting?.remoteUnlock ?? 0; + } + } + } } - } else { - showToast('该锁的远程开锁功能未启用'.tr); + }); + } + if (remoteUnlock == 1) { + final LoginEntity entity = await ApiRepository.to + .remoteOpenLock(lockId: lockId.toString(), timeOut: 60); + if (entity.errorCode!.codeIsSuccessful) { + showToast('已开锁'.tr); + StartChartManage().lockListPeerId = []; } + } else { + showToast('该锁的远程开锁功能未启用'.tr); } } @@ -1172,4 +1234,81 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } } } + + // 切换清晰度的方法,后续补充具体实现 + void onQualityChanged(String quality) async { + state.currentQuality.value = quality; + TalkExpectReq talkExpectReq = StartChartManage().getDefaultTalkExpect(); + final audioType = talkExpectReq.audioType; + int width = 864; + int height = 480; + switch (quality) { + case '高清': + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.H264_720P], + audioType: audioType, + ); + width = 1280; + height = 720; + break; + case '标清': + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.H264], + audioType: audioType, + ); + width = 864; + height = 480; + break; + } + + /// 修改发送预期数据 + StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( + talkExpect: talkExpectReq); + + // 不立即loading,继续解码旧流帧,等待frameSeq回绕检测 + // 仅重置frameSeq回绕检测标志 + _pendingStreamReset = false; + _pendingResetWidth = width; + _pendingResetHeight = height; + } + + void _initHdOptions() { + TalkExpectReq talkExpectReq = StartChartManage().getDefaultTalkExpect(); + final videoType = talkExpectReq.videoType; + if (videoType.contains(VideoTypeE.H264)) { + state.currentQuality.value = '标清'; + } else if (videoType.contains(VideoTypeE.H264_720P)) { + state.currentQuality.value = '高清'; + } + } + + // 新增:重置解码器方法 + Future _resetDecoderForNewStream(int width, int height) async { + try { + if (state.textureId.value != null) { + await VideoDecodePlugin.releaseDecoder(); + Future.microtask(() => state.textureId.value = null); + } + final config = VideoDecoderConfig( + width: width, + height: height, + codecType: 'h264', + ); + final textureId = await VideoDecodePlugin.initDecoder(config); + if (textureId != null) { + Future.microtask(() => state.textureId.value = textureId); + AppLog.log('frameSeq回绕后解码器初始化成功:textureId=$textureId'); + VideoDecodePlugin.setOnFrameRenderedListener((textureId) { + AppLog.log('已经开始渲染======='); + // 只有真正渲染出首帧时才关闭loading + Future.microtask(() => state.isLoading.value = false); + }); + } else { + AppLog.log('frameSeq回绕后解码器初始化失败'); + } + _startFrameProcessTimer(); + } catch (e) { + AppLog.log('frameSeq回绕时解码器初始化错误: $e'); + } + } } diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_page.dart b/lib/talk/starChart/views/native/talk_view_native_decode_page.dart index b7c1fb12..f4a7eb13 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_page.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_page.dart @@ -97,40 +97,42 @@ class _TalkViewNativeDecodePageState extends State final double scaleWidth = physicalWidth / rotatedImageWidth; final double scaleHeight = physicalHeight / rotatedImageHeight; max(scaleWidth, scaleHeight); // 选择较大的缩放比例 - return state.isLoading.isTrue - ? Image.asset( - 'images/main/monitorBg.png', - width: screenWidth, - height: screenHeight, - fit: BoxFit.cover, - ) - : Positioned.fill( - child: PopScope( - canPop: false, - child: RepaintBoundary( - key: state.globalKey, - child: SizedBox.expand( - child: RotatedBox( - // 解码器不支持硬件旋转,使用RotatedBox - quarterTurns: - startChartManage.rotateAngle ~/ 90, - child: Platform.isIOS - ? Transform.scale( - scale: 1.008, // 轻微放大,消除iOS白边 - child: Texture( - textureId: state.textureId.value!, - filterQuality: FilterQuality.medium, - ), - ) - : Texture( - textureId: state.textureId.value!, - filterQuality: FilterQuality.medium, - ), - ), - ), + // 防御性处理:只要loading中或textureId为null,优先渲染loading/占位 + if (state.isLoading.isTrue || state.textureId.value == null) { + return Image.asset( + 'images/main/monitorBg.png', + width: screenWidth, + height: screenHeight, + fit: BoxFit.cover, + ); + } else { + return Positioned.fill( + child: PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: SizedBox.expand( + child: RotatedBox( + // 解码器不支持硬件旋转,使用RotatedBox + quarterTurns: startChartManage.rotateAngle ~/ 90, + child: Platform.isIOS + ? Transform.scale( + scale: 1.008, // 轻微放大,消除iOS白边 + child: Texture( + textureId: state.textureId.value!, + filterQuality: FilterQuality.medium, + ), + ) + : Texture( + textureId: state.textureId.value!, + filterQuality: FilterQuality.medium, + ), ), ), - ); + ), + ), + ); + } }, ), @@ -295,6 +297,62 @@ class _TalkViewNativeDecodePageState extends State ), ), ), + SizedBox(width: 50.w), + // 清晰度切换按钮 + GestureDetector( + onTap: () async { + // 弹出底部弹出层,选择清晰度 + showModalBottomSheet( + context: context, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20.w)), + ), + builder: (BuildContext context) { + final List qualities = ['高清', '标清']; + return SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: qualities.map((q) { + return Obx(() => InkWell( + onTap: () { + Navigator.of(context).pop(); + logic.onQualityChanged(q); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 18.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + q, + style: TextStyle( + color: state.currentQuality.value == q + ? AppColors.mainColor + : Colors.black, + fontWeight: state.currentQuality.value == q + ? FontWeight.bold + : FontWeight.normal, + fontSize: 28.sp, + ), + ), + ], + ), + ), + )); + }).toList(), + ), + ), + ); + }, + ); + }, + child: Container( + child: Icon(Icons.high_quality_outlined, color: Colors.white, size: 38.w), + ), + ), ]); } diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_state.dart b/lib/talk/starChart/views/native/talk_view_native_decode_state.dart index b63ffc29..8d176500 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_state.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_state.dart @@ -117,4 +117,7 @@ class TalkViewNativeDecodeState { // H264文件保存相关 String? h264FilePath; File? h264File; + + // 当前清晰度选项,初始为'高清' + RxString currentQuality = '高清'.obs; // 可选:高清、标清、流畅 }