diff --git a/lib/main/lockDetail/lockSet/basicInformation/basicInformation/basicInformation_page.dart b/lib/main/lockDetail/lockSet/basicInformation/basicInformation/basicInformation_page.dart index ccf2a4cd..9b1417b5 100755 --- a/lib/main/lockDetail/lockSet/basicInformation/basicInformation/basicInformation_page.dart +++ b/lib/main/lockDetail/lockSet/basicInformation/basicInformation/basicInformation_page.dart @@ -153,7 +153,7 @@ class _BasicInformationPageState extends State { visible: state.lockSetInfoData.value.lockFeature?.wifi == 1, child: CommonItem( leftTitel: '设备时区'.tr, - rightTitle: state.lockSetInfoData.value.lockBasicInfo?.timezoneName, + rightTitle: _convertTimezoneToUTCFormat(state.lockSetInfoData.value.lockBasicInfo?.timezoneName), allHeight: 70.h, isHaveLine: true, ), @@ -244,4 +244,112 @@ class _BasicInformationPageState extends State { // 线性映射: percentage = (rssi + 100) * 100 / 70 return ((clampedRssi + 100) * 100 ~/ 70).clamp(0, 100); } + + // 添加时区名称转换方法 + String _convertTimezoneToUTCFormat(String? timezoneName) { + if (timezoneName == null || timezoneName.isEmpty) { + return '-'; + } + + // 如果已经是UTC格式,直接返回 + if (timezoneName.startsWith('UTC')) { + return timezoneName; + } + + // 处理常见的时区名称转换 + final Map timezoneMap = { + // 亚洲时区 (UTC+) + 'Asia/Shanghai': 'UTC+8', + 'Asia/Taipei': 'UTC+8', + 'Asia/Singapore': 'UTC+8', + 'Asia/Hong_Kong': 'UTC+8', + 'Asia/Macau': 'UTC+8', + 'Asia/Seoul': 'UTC+9', + 'Asia/Tokyo': 'UTC+9', + 'Asia/Dubai': 'UTC+4', + 'Asia/Kolkata': 'UTC+5:30', + 'Asia/Bangkok': 'UTC+7', + 'Asia/Jakarta': 'UTC+7', + 'Asia/Kuala_Lumpur': 'UTC+8', + 'Asia/Manila': 'UTC+8', + 'Asia/Karachi': 'UTC+5', + 'Asia/Tehran': 'UTC+3:30', + 'Asia/Baghdad': 'UTC+3', + 'Asia/Beirut': 'UTC+2', + 'Asia/Jerusalem': 'UTC+2', + 'Asia/Damascus': 'UTC+3', + 'Asia/Amman': 'UTC+3', + 'Asia/Baku': 'UTC+4', + 'Asia/Yerevan': 'UTC+4', + 'Asia/Tbilisi': 'UTC+4', + + // 欧洲时区 + 'Europe/London': 'UTC+0', + 'Europe/Paris': 'UTC+1', + 'Europe/Berlin': 'UTC+1', + 'Europe/Rome': 'UTC+1', + 'Europe/Madrid': 'UTC+1', + 'Europe/Amsterdam': 'UTC+1', + 'Europe/Brussels': 'UTC+1', + 'Europe/Vienna': 'UTC+1', + 'Europe/Stockholm': 'UTC+1', + 'Europe/Oslo': 'UTC+1', + 'Europe/Copenhagen': 'UTC+1', + 'Europe/Warsaw': 'UTC+1', + 'Europe/Prague': 'UTC+1', + 'Europe/Budapest': 'UTC+1', + 'Europe/Athens': 'UTC+2', + 'Europe/Helsinki': 'UTC+2', + 'Europe/Riga': 'UTC+2', + 'Europe/Tallinn': 'UTC+2', + 'Europe/Vilnius': 'UTC+2', + 'Europe/Sofia': 'UTC+2', + 'Europe/Bucharest': 'UTC+2', + 'Europe/Istanbul': 'UTC+3', + 'Europe/Minsk': 'UTC+3', + 'Europe/Moscow': 'UTC+3', + + // 北美时区 (UTC-) + 'America/New_York': 'UTC-5', + 'America/Los_Angeles': 'UTC-8', + 'America/Chicago': 'UTC-6', + 'America/Denver': 'UTC-7', + 'America/Phoenix': 'UTC-7', + 'America/Toronto': 'UTC-5', + 'America/Montreal': 'UTC-5', + 'America/Vancouver': 'UTC-8', + 'America/Edmonton': 'UTC-7', + 'America/Halifax': 'UTC-4', + 'America/St_Johns': 'UTC-3:30', + + // 南美时区 + 'America/Sao_Paulo': 'UTC-3', + 'America/Buenos_Aires': 'UTC-3', + 'America/Santiago': 'UTC-4', + 'America/Lima': 'UTC-5', + 'America/Bogota': 'UTC-5', + 'America/Caracas': 'UTC-4', + 'America/Mexico_City': 'UTC-6', + + // 大洋洲时区 + 'Australia/Sydney': 'UTC+10', + 'Australia/Melbourne': 'UTC+10', + 'Australia/Brisbane': 'UTC+10', + 'Australia/Perth': 'UTC+8', + 'Australia/Adelaide': 'UTC+9:30', + 'Pacific/Auckland': 'UTC+12', + 'Pacific/Fiji': 'UTC+12', + + // 非洲时区 + 'Africa/Cairo': 'UTC+2', + 'Africa/Johannesburg': 'UTC+2', + 'Africa/Lagos': 'UTC+1', + 'Africa/Nairobi': 'UTC+3', + 'Africa/Casablanca': 'UTC+1', + 'Africa/Tunis': 'UTC+1', + 'Africa/Algiers': 'UTC+1', + }; + + return timezoneMap[timezoneName] ?? timezoneName; + } } diff --git a/lib/mine/addLock/selectLockType/selectLockType_logic.dart b/lib/mine/addLock/selectLockType/selectLockType_logic.dart index 971feaaa..5d6d3565 100755 --- a/lib/mine/addLock/selectLockType/selectLockType_logic.dart +++ b/lib/mine/addLock/selectLockType/selectLockType_logic.dart @@ -26,8 +26,11 @@ class SelectLockTypeLogic extends BaseGetXController { final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); if (Platform.isAndroid) { final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - // 检查是否为华为设备且系统版本包含HarmonyOS标识,例如'HUAWEI'开头或特定系统属性等。 - return androidInfo.brand == 'HONOR' || androidInfo.version.sdkInt >= 30; // 注意:具体API可能需要更新以适配最新鸿蒙系统版本。 + // 更准确地检测鸿蒙系统 + return androidInfo.brand == 'HUAWEI' || + androidInfo.brand == 'HONOR' || + androidInfo.manufacturer == 'HUAWEI' || + androidInfo.version.release.contains('HarmonyOS'); } else { return false; } @@ -38,19 +41,33 @@ class SelectLockTypeLogic extends BaseGetXController { if (!Platform.isIOS) { final bool locationRequest = await PermissionDialog.request(Permission.location); final bool bluetoothRequest = await PermissionDialog.requestBluetooth(); + // 添加存储权限请求(相册权限) + final bool storageRequest = await PermissionDialog.request(Permission.storage); + bool isHarmonyOS = await checkIfHarmonyOS(); // 鸿蒙系统 if(isHarmonyOS){ - Get.snackbar('提示', '如您是鸿蒙系统,请下拉手动开启系统的“位置信息”,否则无法搜索到锁哦'); - } - if (!bluetoothRequest || !locationRequest) { - return; + print('鸿蒙手机提示----'); + if (!bluetoothRequest || !locationRequest || !storageRequest) { + return; + } + } else { + if (!bluetoothRequest || !locationRequest) { + return; + } } + } Get.toNamed(Routers.nearbyLockPage); } + Future requestHarmonyOSPermissions() async { + // 鸿蒙系统可能需要分别请求不同权限 + await Permission.storage.request(); // 存储权限(包含相册访问) + await Permission.photos.request(); // 照片权限(如果可用) + } + @override void onInit() { super.onInit(); 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 359fae53..9854efb5 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 @@ -44,6 +44,31 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int audioBufferSize = 20; // 音频默认缓冲2帧 + int _frameProcessCount = 0; + int _lastFrameProcessTime = 0; + double _actualFps = 0.0; + void _monitorFrameProcessingPerformance() { + _frameProcessCount++; + final now = DateTime.now().millisecondsSinceEpoch; + + if (now - _lastFrameProcessTime >= 1000) { // 每秒统计一次 + _actualFps = _frameProcessCount.toDouble(); + _frameProcessCount = 0; + _lastFrameProcessTime = now; + + // 根据实际处理能力调整目标帧率 + if (_actualFps < state.targetFps * 0.7) { + // 处理能力不足,降低目标帧率 + state.targetFps = (state.targetFps * 0.9).clamp(15.0, 60.0) as int; + _startFrameProcessTimer(); + } else if (_actualFps > state.targetFps * 1.2 && state.targetFps < 30.0) { + // 处理能力充足,可以提高帧率 + state.targetFps = (state.targetFps * 1.1).clamp(15.0, 30.0) as int; + _startFrameProcessTimer(); + } + } + } + // 回绕阈值,动态调整,frameSeq较小时阈值也小 int _getFrameSeqRolloverThreshold(int lastSeq) { if (lastSeq > 2000) { @@ -237,6 +262,23 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } } + // 根据缓冲区长度动态调整最大缓冲区大小 + if (state.h264FrameBuffer.length > state.maxFrameBufferSize * 0.8) { + // 缓冲区接近满载,更积极地丢弃帧 + while (state.h264FrameBuffer.length >= state.maxFrameBufferSize * 0.9) { + // 优先丢弃旧的P帧 + int pbIndex = state.h264FrameBuffer.indexWhere((f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.P && + f['frameSeq'] < frameSeq - 100); + + if (pbIndex != -1) { + state.h264FrameBuffer.removeAt(pbIndex); + } else { + // 如果没有找到合适的P帧,移除最旧的帧 + state.h264FrameBuffer.removeAt(0); + } + } + } // 将帧添加到缓冲区 state.h264FrameBuffer.add(frameMap); } @@ -259,8 +301,20 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { /// 从缓冲区处理下一帧 /// 从缓冲区处理下一帧 void _processNextFrameFromBuffer() async { + _monitorFrameProcessingPerformance(); final startTime = DateTime.now().microsecondsSinceEpoch; + // 动态调整处理策略基于缓冲区长度 + final bufferLength = state.h264FrameBuffer.length; + // 如果缓冲区过长,提高处理频率 + if (bufferLength > 30 && state.targetFps < 60) { + _adjustFrameProcessFrequency(state.targetFps * 1.5); + } + // 如果缓冲区较短,可以适当降低处理频率节省资源 + else if (bufferLength < 10 && state.targetFps > 25) { + _adjustFrameProcessFrequency(state.targetFps * 0.8); + } + // 避免重复处理 if (state.isProcessingFrame) { return; @@ -273,17 +327,22 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { try { // 优先查找I帧,按frameSeq最小的I帧消费 - final iFrames = state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I).toList(); - iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); + final iFrames = state.h264FrameBuffer.where((f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.I).toList(); + iFrames.sort((a, b) => + (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); if (iFrames.isNotEmpty) { final minIFrame = iFrames.first; final minIFrameSeq = minIFrame['frameSeq']; final targetIndex = state.h264FrameBuffer.indexWhere( - (f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I && f['frameSeq'] == minIFrameSeq, + (f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.I && + f['frameSeq'] == minIFrameSeq, ); state.isProcessingFrame = true; - final Map? frameMap = state.h264FrameBuffer.removeAt(targetIndex); + final Map? frameMap = state.h264FrameBuffer.removeAt( + targetIndex); if (frameMap == null) { state.isProcessingFrame = false; return; @@ -293,7 +352,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 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) { + if (frameData == null || frameType == null || frameSeq == null || + frameSeqI == null || pts == null) { state.isProcessingFrame = false; return; } @@ -315,19 +375,41 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { return; } + state.isProcessingFrame = true; + + // 使用更高效的查找方式,避免每次都排序整个列表 + Map? frameToProcess; + int frameIndex = -1; + // 没有I帧时,只消费refIFrameSeq等于lastDecodedIFrameSeq的P帧 if (lastDecodedIFrameSeq != null) { + // 先查找与上一个I帧关联的P帧 + for (int i = 0; i < state.h264FrameBuffer.length; i++) { + final frame = state.h264FrameBuffer[i]; + if (frame['frameType'] == TalkDataH264Frame_FrameTypeE.P && + frame['frameSeqI'] == lastDecodedIFrameSeq) { + frameToProcess = frame; + frameIndex = i; + break; + } + } final validPFrames = - state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeqI'] == lastDecodedIFrameSeq).toList(); + state.h264FrameBuffer.where((f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.P && + f['frameSeqI'] == lastDecodedIFrameSeq).toList(); if (validPFrames.isNotEmpty) { - validPFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); + validPFrames.sort((a, b) => + (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); final minPFrame = validPFrames.first; final targetIndex = state.h264FrameBuffer.indexWhere( - (f) => - f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeq'] == minPFrame['frameSeq'] && f['frameSeqI'] == lastDecodedIFrameSeq, + (f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.P && + f['frameSeq'] == minPFrame['frameSeq'] && + f['frameSeqI'] == lastDecodedIFrameSeq, ); state.isProcessingFrame = true; - final Map? frameMap = state.h264FrameBuffer.removeAt(targetIndex); + final Map? frameMap = state.h264FrameBuffer.removeAt( + targetIndex); if (frameMap == null) { state.isProcessingFrame = false; return; @@ -337,7 +419,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 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) { + if (frameData == null || frameType == null || frameSeq == null || + frameSeqI == null || pts == null) { state.isProcessingFrame = false; return; } @@ -358,8 +441,35 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { return; } } - // 其他情况不消费,等待I帧到来 + // 如果没有找到合适的P帧,查找最早的I帧 + if (frameToProcess == null) { + int earliestIframeIndex = -1; + int earliestIframeSeq = 999999; + + for (int i = 0; i < state.h264FrameBuffer.length; i++) { + final frame = state.h264FrameBuffer[i]; + if (frame['frameType'] == TalkDataH264Frame_FrameTypeE.I) { + final frameSeq = frame['frameSeq'] as int; + if (frameSeq < earliestIframeSeq) { + earliestIframeSeq = frameSeq; + frameToProcess = frame; + earliestIframeIndex = i; + } + } + } + + if (frameToProcess != null) { + frameIndex = earliestIframeIndex; + } + // 其他情况不消费,等待I帧到来 + } // 处理选中的帧 + if (frameToProcess != null && frameIndex >= 0) { + final Map frameMap = state.h264FrameBuffer.removeAt( + frameIndex); + // ... 帧处理逻辑 + } } finally { + state.isProcessingFrame = false; final endTime = DateTime.now().microsecondsSinceEpoch; final durationMs = (endTime - startTime) / 1000.0; // 可选:只在耗时较长时打印(例如 > 5ms) @@ -369,6 +479,18 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // AppLog.log('Frame processing took ${durationMs.toStringAsFixed(2)} ms'); } } + final endTime = DateTime.now().microsecondsSinceEpoch; + final durationMs = (endTime - startTime) / 1000.0; + + // 记录处理时间,用于性能分析 + if (durationMs > 16.67) { // 超过一帧的时间(60fps) + AppLog.log('帧处理耗时过长: ${durationMs.toStringAsFixed(2)} ms, 缓冲区长度: ${state.h264FrameBuffer.length}'); + } + } + + void _adjustFrameProcessFrequency(double newFps) { + state.targetFps = newFps.clamp(20.0, 60.0) as int; + _startFrameProcessTimer(); // 重新启动定时器 } /// 停止帧处理定时器 @@ -823,6 +945,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.currentQuality.value = quality; TalkExpectReq talkExpectReq = StartChartManage().getDefaultTalkExpect(); final audioType = talkExpectReq.audioType; + // 立即显示loading状态 + state.isLoading.value = true; int width = 864; int height = 480; switch (quality) { @@ -844,14 +968,19 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { break; } + // 立即预设解码器尺寸 + _pendingResetWidth = width; + _pendingResetHeight = height; + // 立即重置解码器而不是等待回绕检测 + await _resetDecoderForNewStream(width, height); /// 修改发送预期数据 StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq); // 不立即loading,继续解码旧流帧,等待frameSeq回绕检测 // 仅重置frameSeq回绕检测标志 - _pendingStreamReset = false; - _pendingResetWidth = width; - _pendingResetHeight = height; + // _pendingStreamReset = false; + // _pendingResetWidth = width; + // _pendingResetHeight = height; } void _initHdOptions() { @@ -867,6 +996,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 新增:重置解码器方法 Future _resetDecoderForNewStream(int width, int height) async { try { + // 显示加载状态 + state.isLoading.value = true; // 先停止帧处理定时器 _stopFrameProcessTimer(); @@ -877,7 +1008,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } // 等待一小段时间确保资源释放完成 - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(Duration(milliseconds: 50)); // 创建新的解码器配置 final config = VideoDecoderConfig( @@ -906,6 +1037,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _decodedIFrames.clear(); state.h264FrameBuffer.clear(); state.isProcessingFrame = false; + _lastFrameSeq = null; hasSps = false; hasPps = false; spsCache = null; 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 4f1a0cd5..ed63b493 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 @@ -110,7 +110,7 @@ class TalkViewNativeDecodeState { // H264帧缓冲区相关 final List> h264FrameBuffer = >[]; // H264帧缓冲区,存储帧数据和类型 final int maxFrameBufferSize = 25; // 最大缓冲区大小 - final int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区 + int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区 Timer? frameProcessTimer; // 帧处理定时器 bool isProcessingFrame = false; // 是否正在处理帧 int lastProcessedTimestamp = 0; // 上次处理帧的时间戳