From b6552b4802d68251b886a22be224f6c61d822071 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 8 May 2025 14:01:03 +0800 Subject: [PATCH 01/31] =?UTF-8?q?fix:=E6=B8=85=E9=99=A4=E8=A7=92=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/mine/message/messageList/messageList_logic.dart | 4 ++++ pubspec.yaml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/mine/message/messageList/messageList_logic.dart b/lib/mine/message/messageList/messageList_logic.dart index 6c119cee..38ca4975 100755 --- a/lib/mine/message/messageList/messageList_logic.dart +++ b/lib/mine/message/messageList/messageList_logic.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter_app_badger/flutter_app_badger.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import '../../../network/api_repository.dart'; import '../../../tools/eventBusEventManage.dart'; @@ -46,6 +48,8 @@ class MessageListLogic extends BaseGetXController { if (entity.errorCode!.codeIsSuccessful) { pageNo = 1; messageListDataRequest(); + // 清除角标 + FlutterAppBadger.removeBadge(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 454bd944..7819d6e8 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -275,6 +275,8 @@ dependencies: provider: ^6.1.2 dio: ^4.0.6 # 网络请求库 video_thumbnail: ^0.5.3 + # 角标管理 + flutter_app_badger: ^1.3.0 From 10d4fa78fa5e44728e9b55c391140e23345db478 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 8 May 2025 15:11:08 +0800 Subject: [PATCH 02/31] =?UTF-8?q?fix:=E6=90=9C=E7=B4=A2=E8=93=9D=E7=89=99?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=8C=BA=E5=88=86,sky=E6=94=B9=E4=B8=BA76?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/blue/blue_manage.dart | 38 ++++++++++++++++++++++++-------------- lib/blue/io_type.dart | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/blue/blue_manage.dart b/lib/blue/blue_manage.dart index 359758b9..6f258b2c 100755 --- a/lib/blue/blue_manage.dart +++ b/lib/blue/blue_manage.dart @@ -262,7 +262,7 @@ class BlueManage { for (final ScanResult scanResult in results) { if (scanResult.advertisementData.serviceUuids.isNotEmpty) { // AppLog.log( - // '扫描到的设备:${scanResult.advertisementData.serviceUuids[0].toString()}'); + // '扫描到的设备:${scanResult.advertisementData.serviceUuids[0].toString()}====${scanResult.advertisementData.advName}'); } else { continue; } @@ -315,20 +315,30 @@ class BlueManage { } /// 判断是否包含指定的uuid - bool _isMatch(List serviceUuids, - {DeviceType deviceType = DeviceType.blue}) { - // 获取设备类型数组 - List deviceTypeList = getDeviceType(deviceType); - - // 检查 serviceUuids 是否包含 deviceTypeList 中的任意一个值 - if (serviceUuids != null && serviceUuids.isNotEmpty) { - return serviceUuids.any((uuid) { - return deviceTypeList - .any((type) => uuid.toLowerCase().contains(type.toLowerCase())); - }); + bool _isMatch(List serviceUuids, {DeviceType deviceType = DeviceType.blue}) { + final List prefixes = getDeviceType(deviceType).map((e) => e.toLowerCase()).toList(); + for (String uuid in serviceUuids) { + final String cleanUuid = uuid.replaceAll('-', '').toLowerCase(); + if (cleanUuid.length == 8) { + // 8位,判断前两位 + for (final prefix in prefixes) { + if (cleanUuid.startsWith(prefix)) { + return true; + } + } + } else if (cleanUuid.length == 32) { + // 128位,判断前8位的第3、第4位 + final String first8 = cleanUuid.substring(0, 8); + if (first8.length >= 4) { + final String thirdAndFourth = first8.substring(2, 4); // 索引2和3 + for (final prefix in prefixes) { + if (thirdAndFourth == prefix) { + return true; + } + } + } + } } - - // 如果 serviceUuids 为空,则返回 false return false; } diff --git a/lib/blue/io_type.dart b/lib/blue/io_type.dart index ee785811..a5f523b4 100755 --- a/lib/blue/io_type.dart +++ b/lib/blue/io_type.dart @@ -9,7 +9,7 @@ List getDeviceType(DeviceType deviceType) { List t = ['758824']; switch (deviceType) { case DeviceType.blue: - t = ['758824', '75']; + t = ['758824', '75', '768824', '76']; break; case DeviceType.gateway: t = ['758825']; From cf2fd775ec33240eb99860739eb5b8d08138ba63 Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 12 May 2025 09:59:48 +0800 Subject: [PATCH 03/31] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=94=81=E4=B9=8B=E5=90=8E=E4=B8=8D=E5=87=BA=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuringWifi_logic.dart | 1 + .../lockMian/lockList/lockList_logic.dart | 13 ++++++++- .../lockMian/lockMain/lockMain_logic.dart | 29 ++++++++++--------- lib/tools/eventBusEventManage.dart | 9 ++++++ 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart index 76cc2bf4..7b266390 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart @@ -189,6 +189,7 @@ class ConfiguringWifiLogic extends BaseGetXController { Get.offAllNamed(Routers.starLockMain); } eventBus.fire(SuccessfulDistributionNetwork()); + eventBus.fire(RefreshLockListInfoDataEvent()); }); // 获取锁设置 diff --git a/lib/main/lockMian/lockList/lockList_logic.dart b/lib/main/lockMian/lockList/lockList_logic.dart index 4ecd4e62..76a9bb2b 100755 --- a/lib/main/lockMian/lockList/lockList_logic.dart +++ b/lib/main/lockMian/lockList/lockList_logic.dart @@ -61,7 +61,7 @@ class LockListLogic extends BaseGetXController { void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) { this.entity = entity; // if (entity.pageNo == 1) { - _groupDataList = []; + _groupDataList = []; // } _groupDataList.addAll(entity.groupList!); update(); @@ -69,6 +69,7 @@ class LockListLogic extends BaseGetXController { // 监听蓝牙协议返回结果 late StreamSubscription _replySubscription; + late StreamSubscription _setLockListInfoGroupEntity; void _initReplySubscription() { _replySubscription = @@ -336,6 +337,7 @@ class LockListLogic extends BaseGetXController { void onReady() { super.onReady(); _initReplySubscription(); + _initEventHandler(); } @override @@ -348,5 +350,14 @@ class LockListLogic extends BaseGetXController { @override void onClose() { _replySubscription.cancel(); + _setLockListInfoGroupEntity.cancel(); + } + + void _initEventHandler() { + _setLockListInfoGroupEntity = eventBus + .on() + .listen((SetLockListInfoGroupEntity event) async { + setLockListInfoGroupEntity(event.lockListInfoGroupEntity); + }); } } diff --git a/lib/main/lockMian/lockMain/lockMain_logic.dart b/lib/main/lockMian/lockMain/lockMain_logic.dart index 802e9bbe..3ac67134 100755 --- a/lib/main/lockMian/lockMain/lockMain_logic.dart +++ b/lib/main/lockMian/lockMain/lockMain_logic.dart @@ -132,20 +132,21 @@ class LockMainLogic extends BaseGetXController { state.lockListInfoGroupEntity.refresh(); // AppLog.log('entity:$entity state.lockListInfoGroupEntity.value.groupList!.length:${state.lockListInfoGroupEntity.value.groupList![0].lockList!.length}'); //检测控制器是否存在 - if (Get.isRegistered()) { - //设置控制器数据并刷新 - // AppLog.log('检测控制器是否存 调用了 setLockListInfoGroupEntity'); - Get.find().setLockListInfoGroupEntity(entity); - } else { - //延迟加载 - Future.delayed(200.milliseconds, () { - if (Get.isRegistered()) { - //设置控制器数据并刷新 - // AppLog.log('检测控制器是否存 延迟调用了 setLockListInfoGroupEntity'); - Get.find().setLockListInfoGroupEntity(entity); - } - }); - } + eventBus.fire(SetLockListInfoGroupEntity(lockListInfoGroupEntity: entity)); + // if (Get.isRegistered()) { + // //设置控制器数据并刷新 + // // AppLog.log('检测控制器是否存 调用了 setLockListInfoGroupEntity'); + // Get.find().setLockListInfoGroupEntity(entity); + // } else { + // //延迟加载 + // Future.delayed(500.milliseconds, () { + // if (Get.isRegistered()) { + // //设置控制器数据并刷新 + // // AppLog.log('检测控制器是否存 延迟调用了 setLockListInfoGroupEntity'); + // Get.find().setLockListInfoGroupEntity(entity); + // } + // }); + // } if (state.dataLength.value == 1) { if (Get.isRegistered()) { diff --git a/lib/tools/eventBusEventManage.dart b/lib/tools/eventBusEventManage.dart index 810ad0a6..4c4fbb3d 100755 --- a/lib/tools/eventBusEventManage.dart +++ b/lib/tools/eventBusEventManage.dart @@ -1,4 +1,5 @@ import 'package:event_bus/event_bus.dart'; +import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import '../main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; @@ -195,6 +196,7 @@ class RogerThatLockInfoDataEvent { class GetGatewayListRefreshUI { GetGatewayListRefreshUI(); } + /// 同意隐私协议 class AgreePrivacyAgreement { AgreePrivacyAgreement(); @@ -204,3 +206,10 @@ class AgreePrivacyAgreement { class SuccessfulDistributionNetwork { SuccessfulDistributionNetwork(); } + +/// 设置锁列表数据 +class SetLockListInfoGroupEntity { + SetLockListInfoGroupEntity({required this.lockListInfoGroupEntity}); + + LockListInfoGroupEntity lockListInfoGroupEntity; +} From 310062513bd0c67c6688932fe71c62eb55daf04c Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 12 May 2025 13:57:10 +0800 Subject: [PATCH 04/31] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=94=81=E4=B9=8B=E5=90=8E=E4=B8=8D=E5=87=BA=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuringWifi_logic.dart | 2 +- .../lockMian/lockList/lockList_logic.dart | 19 +++++----- lib/main/lockMian/lockList/lockList_page.dart | 37 +++++++++---------- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart index 7b266390..6f0a4570 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart @@ -189,7 +189,7 @@ class ConfiguringWifiLogic extends BaseGetXController { Get.offAllNamed(Routers.starLockMain); } eventBus.fire(SuccessfulDistributionNetwork()); - eventBus.fire(RefreshLockListInfoDataEvent()); + eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true,isUnShowLoading: true)); }); // 获取锁设置 diff --git a/lib/main/lockMian/lockList/lockList_logic.dart b/lib/main/lockMian/lockList/lockList_logic.dart index 76a9bb2b..948546d3 100755 --- a/lib/main/lockMian/lockList/lockList_logic.dart +++ b/lib/main/lockMian/lockList/lockList_logic.dart @@ -27,13 +27,13 @@ class LockListLogic extends BaseGetXController { LockListLogic(this.entity) {} LockListState state = LockListState(); - List _groupDataList = []; + final RxList groupDataList = [].obs; LockListInfoGroupEntity? entity; final ShowTipView showTipView = ShowTipView(); - List get groupDataList { + List get groupDataListFiltered { final List list = - _groupDataList.map((GroupList e) => e.copy()).toList(); + groupDataList.map((GroupList e) => e.copy()).toList(); if (state.searchStr.value != '' && state.showSearch.value) { list.forEach((GroupList element) { element.lockList?.removeWhere((LockListInfoItemEntity element) => @@ -60,11 +60,7 @@ class LockListLogic extends BaseGetXController { //设置数据 void setLockListInfoGroupEntity(LockListInfoGroupEntity entity) { this.entity = entity; - // if (entity.pageNo == 1) { - _groupDataList = []; - // } - _groupDataList.addAll(entity.groupList!); - update(); + groupDataList.value = entity.groupList!; } // 监听蓝牙协议返回结果 @@ -343,8 +339,11 @@ class LockListLogic extends BaseGetXController { @override void onInit() { super.onInit(); - // AppLog.log('onInit调用了 setLockListInfoGroupEntity'); - setLockListInfoGroupEntity(entity!); + AppLog.log('[onInit] entity: \\${entity?.toString()}'); + if (entity != null) { + setLockListInfoGroupEntity(entity!); + } + _initEventHandler(); } @override diff --git a/lib/main/lockMian/lockList/lockList_page.dart b/lib/main/lockMian/lockList/lockList_page.dart index 1e53cdb9..55cb149a 100755 --- a/lib/main/lockMian/lockList/lockList_page.dart +++ b/lib/main/lockMian/lockList/lockList_page.dart @@ -37,32 +37,31 @@ class _LockListPageState extends State with RouteAware { @override Widget build(BuildContext context) { - return GetBuilder(builder: (LockListLogic logic) { - return Scaffold( - body: ListView.separated( - itemCount: logic.groupDataList.length, - itemBuilder: (BuildContext context, int index) { - final GroupList itemData = logic.groupDataList[index]; - return _buildLockExpandedList(context, index, itemData); - }, - shrinkWrap: true, - physics: const AlwaysScrollableScrollPhysics(), - separatorBuilder: (BuildContext context, int index) { - return const Divider( - height: 1, - color: AppColors.greyLineColor, - ); - }), - ); - }); + return Obx(() => Scaffold( + body: ListView.separated( + itemCount: logic.groupDataListFiltered.length, + itemBuilder: (BuildContext context, int index) { + final GroupList itemData = logic.groupDataListFiltered[index]; + return _buildLockExpandedList(context, index, itemData, key: ValueKey(itemData.groupId)); + }, + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), + separatorBuilder: (BuildContext context, int index) { + return const Divider( + height: 1, + color: AppColors.greyLineColor, + ); + }), + )); } //设备多层级列表 Widget _buildLockExpandedList(BuildContext context, int index, - GroupList itemData) { + GroupList itemData, {Key? key}) { final List lockItemList = itemData.lockList ?? []; return LockListGroupView( + key: key, onTap: () { //是否选中组 if (itemData.isChecked) {} else {} From 4a725be23fa953e970023aa0d377bef79ee4b8a2 Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 12 May 2025 13:57:50 +0800 Subject: [PATCH 05/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E8=93=9D=E7=89=99?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E6=97=B6=E5=88=A4=E6=96=AD=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=88=B0=E7=9A=84=E8=AE=BE=E5=A4=87=E6=98=AF=E5=90=A6=E9=85=8D?= =?UTF-8?q?=E5=AF=B9=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/blue/blue_manage.dart | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/blue/blue_manage.dart b/lib/blue/blue_manage.dart index cdc9ae3e..0259a5c0 100755 --- a/lib/blue/blue_manage.dart +++ b/lib/blue/blue_manage.dart @@ -319,22 +319,34 @@ class BlueManage { bool _isMatch(List serviceUuids, {DeviceType deviceType = DeviceType.blue}) { final List prefixes = getDeviceType(deviceType).map((e) => e.toLowerCase()).toList(); for (String uuid in serviceUuids) { - final String cleanUuid = uuid.replaceAll('-', '').toLowerCase(); + final String cleanUuid = uuid.toLowerCase(); if (cleanUuid.length == 8) { - // 8位,判断前两位 + // 8位,判断第4、5位 + String pairStatus = cleanUuid.substring(4, 6); // 第4、5位(索引3和4) for (final prefix in prefixes) { if (cleanUuid.startsWith(prefix)) { - return true; + // 00=未配对,01=已配对 + if (pairStatus == '00') { + return true; // 未配对才返回true + } + // 已配对(01)不返回true,继续判断下一个uuid } } - } else if (cleanUuid.length == 32) { + } else { // 128位,判断前8位的第3、第4位 - final String first8 = cleanUuid.substring(0, 8); - if (first8.length >= 4) { - final String thirdAndFourth = first8.substring(2, 4); // 索引2和3 + if (cleanUuid.length >= 32) { + final String thirdAndFourth = cleanUuid.substring(2, 4); // 索引2和3 for (final prefix in prefixes) { if (thirdAndFourth == prefix) { - return true; + // 判断配对状态(带横杠UUID的第31、32位,从1开始计数) + if (cleanUuid.length >= 32) { + String pairStatus = cleanUuid.substring(30, 32); // 第31、32位(从1开始计数) + // 00=未配对,01=已配对 + if (pairStatus == '00') { + return true; // 未配对才返回true + } + // 已配对(01)不返回true,继续判断下一个uuid + } } } } From 905368ec8da405630e54690931ac13988c805e13 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 09:45:57 +0800 Subject: [PATCH 06/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E5=9B=BE=E4=BC=A0?= =?UTF-8?q?=E5=85=A8=E8=87=AA=E5=8A=A8=E9=94=81=E9=80=9A=E8=AF=9D=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/appRouters.dart | 11 +- .../image_transmission_logic.dart | 667 ++++++++++++++++++ .../image_transmission_page.dart | 238 +++++++ .../image_transmission_state.dart | 94 +++ 4 files changed, 1009 insertions(+), 1 deletion(-) create mode 100644 lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart create mode 100644 lib/talk/starChart/views/imageTransmission/image_transmission_page.dart create mode 100644 lib/talk/starChart/views/imageTransmission/image_transmission_state.dart diff --git a/lib/appRouters.dart b/lib/appRouters.dart index 9043ffe1..0a06445a 100755 --- a/lib/appRouters.dart +++ b/lib/appRouters.dart @@ -203,6 +203,7 @@ import 'mine/valueAddedServices/valueAddedServicesRealName/value_added_services_ import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesAddSMSTemplate/newSMSTemplate_page.dart'; import 'mine/valueAddedServices/valueAddedServicesSMSTemplate/valueAddedServicesListSMSTemplate/customSMSTemplateList_page.dart'; import 'starLockApplication/starLockApplication.dart'; +import 'talk/starChart/views/imageTransmission/image_transmission_page.dart'; import 'tools/seletKeyCyclicDate/seletKeyCyclicDate_page.dart'; abstract class Routers { @@ -515,6 +516,8 @@ abstract class Routers { static const String starChartPage = '/starChartPage'; //星图 static const String starChartTalkView = '/starChartTalkView'; //星图对讲页面 static const String h264WebView = '/h264WebView'; //星图对讲页面 + static const String imageTransmissionView = + '/imageTransmissionView'; //星图对讲页面(图传) } abstract class AppRouters { @@ -1185,7 +1188,13 @@ abstract class AppRouters { page: () => const DoubleLockLinkPage()), GetPage( name: Routers.starChartTalkView, page: () => const TalkViewPage()), - GetPage(name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), // 插件播放页面 + GetPage( + name: Routers.h264WebView, page: () => TalkViewNativeDecodePage()), + // 插件播放页面 + GetPage( + name: Routers.imageTransmissionView, + page: () => ImageTransmissionPage()), + // 插件播放页面 // GetPage(name: Routers.h264WebView, page: () => H264WebView()), // webview播放页面 ]; } diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart new file mode 100644 index 00000000..8cc6f854 --- /dev/null +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart @@ -0,0 +1,667 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; +import 'dart:math'; // Import the math package to use sqrt +import 'dart:ui' show decodeImageFromList; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; +import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:gallery_saver/gallery_saver.dart'; +import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/login/login/entity/LoginEntity.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart'; +import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; +import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; +import 'package:star_lock/network/api_repository.dart'; +import 'package:star_lock/talk/call/g711.dart'; +import 'package:star_lock/talk/starChart/constant/talk_status.dart'; +import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart'; +import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; +import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; +import 'package:star_lock/talk/starChart/star_chart_manage.dart'; +import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_state.dart'; +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 '../../../../tools/baseGetXController.dart'; + +class ImageTransmissionLogic extends BaseGetXController { + ImageTransmissionState state = ImageTransmissionState(); + + final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; + + int bufferSize = 8; // 增大缓冲区,满时才渲染 + + int audioBufferSize = 2; // 音频默认缓冲2帧 + bool _isFirstAudioFrame = true; // 是否是第一帧 + + int _startAudioTime = 0; // 开始播放时间戳 + + // 定义音频帧缓冲和发送函数 + final List _bufferedAudioFrames = []; + + // 添加监听状态和订阅引用 + bool _isListening = false; + StreamSubscription? _streamSubscription; + + Timer? videoRenderTimer; // 视频渲染定时器 + + int _renderedFrameCount = 0; + int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch; + + /// 初始化音频播放器 + void _initFlutterPcmSound() { + const int sampleRate = 8000; + FlutterPcmSound.setLogLevel(LogLevel.none); + FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1); + // 设置 feed 阈值 + if (Platform.isAndroid) { + FlutterPcmSound.setFeedThreshold(1024); // Android 平台的特殊处理 + } else { + FlutterPcmSound.setFeedThreshold(2000); // 非 Android 平台的处理 + } + } + + /// 挂断 + void udpHangUpAction() async { + if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { + // 如果是通话中就挂断 + StartChartManage().startTalkHangupMessageTimer(); + } else { + // 拒绝 + StartChartManage().startTalkRejectMessageTimer(); + } + Get.back(); + } + + // 发起接听命令 + void initiateAnswerCommand() { + StartChartManage().startTalkAcceptTimer(); + } + + // 监听音视频数据流 + void _startListenTalkData() { + // 防止重复监听 + if (_isListening) { + AppLog.log("已经存在数据流监听,避免重复监听"); + return; + } + + AppLog.log("==== 启动新的数据流监听 ===="); + _isListening = true; + _streamSubscription = state.talkDataRepository.talkDataStream + .listen((TalkDataModel talkDataModel) async { + final talkData = talkDataModel.talkData; + final contentType = talkData!.contentType; + final currentTime = DateTime.now().millisecondsSinceEpoch; + + // 判断数据类型,进行分发处理 + switch (contentType) { + case TalkData_ContentTypeE.G711: + // // 第一帧到达时记录开始时间 + if (_isFirstAudioFrame) { + _startAudioTime = currentTime; + _isFirstAudioFrame = false; + } + + // 计算音频延迟 + final expectedTime = _startAudioTime + talkData.durationMs; + final audioDelay = currentTime - expectedTime; + + // 如果延迟太大,清空缓冲区并直接播放 + if (audioDelay > 500) { + state.audioBuffer.clear(); + if (state.isOpenVoice.value) { + _playAudioFrames(); + } + return; + } + if (state.audioBuffer.length >= audioBufferSize) { + state.audioBuffer.removeAt(0); // 丢弃最旧的数据 + } + state.audioBuffer.add(talkData); // 添加新数据 + // 添加音频播放逻辑,与视频类似 + _playAudioFrames(); + break; + case TalkData_ContentTypeE.Image: + // 固定长度缓冲区,最多保留bufferSize帧 + state.videoBuffer.add(talkData); + if (state.videoBuffer.length > bufferSize) { + state.videoBuffer.removeAt(0); // 移除最旧帧 + } + break; + } + }); + } + + // 新增:音频帧播放逻辑 + void _playAudioFrames() { + // 如果缓冲区为空或未达到目标大小,不进行播放 + // 音频缓冲区要求更小,以减少延迟 + if (state.audioBuffer.isEmpty || + state.audioBuffer.length < audioBufferSize) { + return; + } + + // 找出时间戳最小的音频帧 + TalkData? oldestFrame; + int oldestIndex = -1; + for (int i = 0; i < state.audioBuffer.length; i++) { + if (oldestFrame == null || + state.audioBuffer[i].durationMs < oldestFrame.durationMs) { + oldestFrame = state.audioBuffer[i]; + oldestIndex = i; + } + } + + // 确保找到了有效帧 + if (oldestFrame != null && oldestIndex != -1) { + if (state.isOpenVoice.value) { + // 播放音频 + _playAudioData(oldestFrame); + } + state.audioBuffer.removeAt(oldestIndex); + } + } + + /// 监听对讲状态 + void _startListenTalkStatus() { + state.startChartTalkStatus.statusStream.listen((talkStatus) { + state.talkStatus.value = talkStatus; + switch (talkStatus) { + case TalkStatus.rejected: + case TalkStatus.hangingUpDuring: + case TalkStatus.notTalkData: + case TalkStatus.notTalkPing: + case TalkStatus.end: + _handleInvalidTalkStatus(); + break; + case TalkStatus.answeredSuccessfully: + state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 + state.oneMinuteTimeTimer ??= + Timer.periodic(const Duration(seconds: 1), (Timer t) { + if (state.listData.value.length > 0) { + state.oneMinuteTime.value++; + // if (state.oneMinuteTime.value >= 60) { + // t.cancel(); // 取消定时器 + // state.oneMinuteTime.value = 0; + // // 倒计时结束挂断 + // // udpHangUpAction(); + // } + } + }); + break; + default: + // 其他状态的处理 + break; + } + }); + } + + /// 播放音频数据 + void _playAudioData(TalkData talkData) async { + if (state.isOpenVoice.value) { + final list = + G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); + // // 将 PCM 数据转换为 PcmArrayInt16 + final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list); + FlutterPcmSound.feed(fromList); + if (!state.isPlaying.value) { + FlutterPcmSound.play(); + state.isPlaying.value = true; + } + } + } + + /// 停止播放音频 + void _stopPlayG711Data() async { + await FlutterPcmSound.pause(); + await FlutterPcmSound.stop(); + await FlutterPcmSound.clear(); + } + + /// 开门 + // udpOpenDoorAction() async { + // final List? privateKey = + // await Storage.getStringList(saveBluePrivateKey); + // final List getPrivateKeyList = changeStringListToIntList(privateKey!); + // + // final List? signKey = await Storage.getStringList(saveBlueSignKey); + // final List signKeyDataList = changeStringListToIntList(signKey!); + // + // final List? token = await Storage.getStringList(saveBlueToken); + // final List getTokenList = changeStringListToIntList(token!); + // + // await _getLockNetToken(); + // + // final OpenLockCommand openLockCommand = OpenLockCommand( + // lockID: BlueManage().connectDeviceName, + // userID: await Storage.getUid(), + // openMode: lockDetailState.openDoorModel, + // openTime: _getUTCNetTime(), + // onlineToken: lockDetailState.lockNetToken, + // token: getTokenList, + // needAuthor: 1, + // signKey: signKeyDataList, + // privateKey: getPrivateKeyList, + // ); + // final messageDetail = openLockCommand.packageData(); + // // 将 List 转换为十六进制字符串 + // String hexString = messageDetail + // .map((byte) => byte.toRadixString(16).padLeft(2, '0')) + // .join(' '); + // + // AppLog.log('open lock hexString: $hexString'); + // // 发送远程开门消息 + // StartChartManage().sendRemoteUnLockMessage( + // bluetoothDeviceName: BlueManage().connectDeviceName, + // openLockCommand: messageDetail, + // ); + // showToast('正在开锁中...'.tr); + // } + + int _getUTCNetTime() { + if (lockDetailState.isHaveNetwork) { + return DateTime.now().millisecondsSinceEpoch ~/ 1000 + + lockDetailState.differentialTime; + } else { + return 0; + } + } + + /// 获取权限状态 + Future getPermissionStatus() async { + final Permission permission = Permission.microphone; + //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 + final PermissionStatus status = await permission.status; + if (status.isGranted) { + return true; + } else if (status.isDenied) { + requestPermission(permission); + } else if (status.isPermanentlyDenied) { + openAppSettings(); + } else if (status.isRestricted) { + requestPermission(permission); + } else {} + return false; + } + + ///申请权限 + void requestPermission(Permission permission) async { + final PermissionStatus status = await permission.request(); + if (status.isPermanentlyDenied) { + openAppSettings(); + } + } + + Future requestPermissions() async { + // 申请存储权限 + var storageStatus = await Permission.storage.request(); + // 申请录音权限 + var microphoneStatus = await Permission.microphone.request(); + + if (storageStatus.isGranted && microphoneStatus.isGranted) { + print("Permissions granted"); + } else { + print("Permissions denied"); + // 如果权限被拒绝,可以提示用户或跳转到设置页面 + if (await Permission.storage.isPermanentlyDenied) { + openAppSettings(); // 跳转到应用设置页面 + } + } + } + + Future startRecording() async { + // requestPermissions(); + // if (state.isRecordingScreen.value) { + // showToast('录屏已开始,请勿重复点击'); + // } + // bool start = await FlutterScreenRecording.startRecordScreen( + // "Screen Recording", // 视频文件名 + // titleNotification: "Recording in progress", // 通知栏标题 + // messageNotification: "Tap to stop recording", // 通知栏内容 + // ); + // + // if (start) { + // state.isRecordingScreen.value = true; + // } + } + + Future stopRecording() async { + // String path = await FlutterScreenRecording.stopRecordScreen; + // print("Recording saved to: $path"); + // + // // 将视频保存到系统相册 + // bool? success = await GallerySaver.saveVideo(path); + // if (success == true) { + // print("Video saved to gallery"); + // } else { + // print("Failed to save video to gallery"); + // } + // + // showToast('录屏结束,已保存到系统相册'); + // state.isRecordingScreen.value = false; + } + + @override + void onReady() { + super.onReady(); + } + + @override + void onInit() { + super.onInit(); + + // 启动监听音视频数据流 + _startListenTalkData(); + // 启动监听对讲状态 + _startListenTalkStatus(); + // 在没有监听成功之前赋值一遍状态 + // *** 由于页面会在状态变化之后才会初始化,导致识别不到最新的状态,在这里手动赋值 *** + state.talkStatus.value = state.startChartTalkStatus.status; + + // 初始化音频播放器 + _initFlutterPcmSound(); + + // 启动播放定时器 + // _startPlayback(); + + // 初始化录音控制器 + _initAudioRecorder(); + + requestPermissions(); + + // 启动视频渲染定时器(10fps) + videoRenderTimer = Timer.periodic(const Duration(milliseconds: 100), (_) { + final int now = DateTime.now().millisecondsSinceEpoch; + if (state.videoBuffer.isNotEmpty) { + final TalkData oldestFrame = state.videoBuffer.removeAt(0); + if (oldestFrame.content.isNotEmpty) { + state.listData.value = + Uint8List.fromList(oldestFrame.content); // 备份原始数据 + final int decodeStart = DateTime.now().millisecondsSinceEpoch; + decodeImageFromList(Uint8List.fromList(oldestFrame.content)) + .then((ui.Image img) { + final int decodeEnd = DateTime.now().millisecondsSinceEpoch; + state.currentImage.value = img; + _renderedFrameCount++; + // 每秒统计一次fps + if (now - _lastFpsPrintTime >= 1000) { + // print('实际渲染fps: $_renderedFrameCount'); + _renderedFrameCount = 0; + _lastFpsPrintTime = now; + } + }).catchError((e) { + print('图片解码失败: $e'); + }); + } + } + // 如果缓冲区为空,不做任何操作,保持上一次内容 + }); + } + + @override + void onClose() { + _stopPlayG711Data(); // 停止播放音频 + state.listData.value = Uint8List(0); // 清空视频数据 + state.audioBuffer.clear(); // 清空音频缓冲区 + state.videoBuffer.clear(); // 清空视频缓冲区 + + state.oneMinuteTimeTimer?.cancel(); + state.oneMinuteTimeTimer = null; + + stopProcessingAudio(); + // 清理图片缓存 + // _imageCache.clear(); + state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 + state.oneMinuteTimeTimer = null; // 取消旧定时器 + state.oneMinuteTime.value = 0; + // 取消数据流监听 + _streamSubscription?.cancel(); + _isListening = false; + + // 释放视频渲染定时器 + videoRenderTimer?.cancel(); + videoRenderTimer = null; + + super.onClose(); + } + + @override + void dispose() { + stopProcessingAudio(); + // 重置期望数据 + StartChartManage().reSetDefaultTalkExpect(); + // 释放视频渲染定时器 + videoRenderTimer?.cancel(); + videoRenderTimer = null; + super.dispose(); + } + + /// 处理无效通话状态 + void _handleInvalidTalkStatus() { + state.listData.value = Uint8List(0); + // 停止播放音频 + _stopPlayG711Data(); + stopProcessingAudio(); + } + + /// 更新发送预期数据 + void updateTalkExpect() { + TalkExpectReq talkExpectReq = TalkExpectReq(); + state.isOpenVoice.value = !state.isOpenVoice.value; + if (!state.isOpenVoice.value) { + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.IMAGE], + audioType: [], + ); + showToast('已静音'.tr); + } else { + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.IMAGE], + audioType: [AudioTypeE.G711], + ); + } + + /// 修改发送预期数据 + StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( + talkExpect: talkExpectReq); + } + + /// 截图并保存到相册 + Future captureAndSavePng() async { + try { + if (state.globalKey.currentContext == null) { + AppLog.log('截图失败: 未找到当前上下文'); + return; + } + final RenderRepaintBoundary boundary = state.globalKey.currentContext! + .findRenderObject()! as RenderRepaintBoundary; + final ui.Image image = await boundary.toImage(); + final ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + + if (byteData == null) { + AppLog.log('截图失败: 图像数据为空'); + return; + } + final Uint8List pngBytes = byteData.buffer.asUint8List(); + + // 获取应用程序的文档目录 + final Directory directory = await getApplicationDocumentsDirectory(); + final String imagePath = '${directory.path}/screenshot.png'; + + // 将截图保存为文件 + final File imgFile = File(imagePath); + await imgFile.writeAsBytes(pngBytes); + + // 将截图保存到相册 + await ImageGallerySaver.saveFile(imagePath); + + AppLog.log('截图保存路径: $imagePath'); + showToast('截图已保存到相册'.tr); + } catch (e) { + AppLog.log('截图失败: $e'); + } + } + + // 远程开锁 + Future remoteOpenLock() async { + 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 = []; + } + } else { + showToast('该锁的远程开锁功能未启用'.tr); + } + } + } + + /// 初始化音频录制器 + void _initAudioRecorder() { + state.voiceProcessor = VoiceProcessor.instance; + } + + //开始录音 + Future startProcessingAudio() async { + try { + if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { + await state.voiceProcessor?.start(state.frameLength, state.sampleRate); + final bool? isRecording = await state.voiceProcessor?.isRecording(); + state.isRecordingAudio.value = isRecording!; + state.startRecordingAudioTime.value = DateTime.now(); + + // 增加录音帧监听器和错误监听器 + state.voiceProcessor + ?.addFrameListeners([_onFrame]); + state.voiceProcessor?.addErrorListener(_onError); + } else { + // state.errorMessage.value = 'Recording permission not granted'; + } + } on PlatformException catch (ex) { + // state.errorMessage.value = 'Failed to start recorder: $ex'; + } + state.isOpenVoice.value = false; + } + + /// 停止录音 + Future stopProcessingAudio() async { + try { + await state.voiceProcessor?.stop(); + state.voiceProcessor?.removeFrameListener(_onFrame); + state.udpSendDataFrameNumber = 0; + // 记录结束时间 + state.endRecordingAudioTime.value = DateTime.now(); + + // 计算录音的持续时间 + final Duration duration = state.endRecordingAudioTime.value + .difference(state.startRecordingAudioTime.value); + + state.recordingAudioTime.value = duration.inSeconds; + } on PlatformException catch (ex) { + // state.errorMessage.value = 'Failed to stop recorder: $ex'; + } finally { + final bool? isRecording = await state.voiceProcessor?.isRecording(); + state.isRecordingAudio.value = isRecording!; + state.isOpenVoice.value = true; + } + } + +// 音频帧处理 + Future _onFrame(List frame) async { + // 添加最大缓冲限制 + if (_bufferedAudioFrames.length > state.frameLength * 3) { + _bufferedAudioFrames.clear(); // 清空过多积累的数据 + return; + } + + // 首先应用固定增益提升基础音量 + List amplifiedFrame = _applyGain(frame, 1.6); + // 编码为G711数据 + List encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law + _bufferedAudioFrames.addAll(encodedData); + // 使用相对时间戳 + final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使用循环时间戳 + int getFrameLength = state.frameLength; + if (Platform.isIOS) { + getFrameLength = state.frameLength * 2; + } + + // 添加发送间隔控制 + if (_bufferedAudioFrames.length >= state.frameLength) { + try { + await StartChartManage().sendTalkDataMessage( + talkData: TalkData( + content: _bufferedAudioFrames, + contentType: TalkData_ContentTypeE.G711, + durationMs: ms, + ), + ); + } finally { + _bufferedAudioFrames.clear(); // 确保清理缓冲区 + } + } else { + _bufferedAudioFrames.addAll(encodedData); + } + } + +// 错误监听 + void _onError(VoiceProcessorException error) { + AppLog.log(error.message!); + } + +// 添加音频增益处理方法 + List _applyGain(List pcmData, double gainFactor) { + List result = List.filled(pcmData.length, 0); + + for (int i = 0; i < pcmData.length; i++) { + // PCM数据通常是有符号的16位整数 + int sample = pcmData[i]; + + // 应用增益 + double amplified = sample * gainFactor; + + // 限制在有效范围内,防止溢出 + if (amplified > 32767) { + amplified = 32767; + } else if (amplified < -32768) { + amplified = -32768; + } + + result[i] = amplified.toInt(); + } + + return result; + } +} diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart new file mode 100644 index 00000000..c7ce2678 --- /dev/null +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/talk/call/callTalk.dart'; +import 'package:star_lock/talk/starChart/constant/talk_status.dart'; +import 'package:star_lock/talk/starChart/star_chart_manage.dart'; +import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_logic.dart'; +import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_state.dart'; +import 'package:star_lock/tools/titleAppBar.dart'; +import 'package:slide_to_act/slide_to_act.dart'; + +// 可选:引入第三方滑动解锁库 +// import 'package:flutter_slider_button/flutter_slider_button.dart'; + +class ImageTransmissionPage extends StatefulWidget { + const ImageTransmissionPage(); + + @override + State createState() => _ImageTransmissionPageState(); +} + +class _ImageTransmissionPageState extends State + with TickerProviderStateMixin { + final ImageTransmissionLogic logic = Get.put(ImageTransmissionLogic()); + final ImageTransmissionState state = Get.find().state; + final startChartManage = StartChartManage(); + + @override + void initState() { + super.initState(); + state.animationController = AnimationController( + vsync: this, // 确保使用的TickerProvider是当前Widget + duration: const Duration(seconds: 1), + ); + state.animationController.repeat(); + state.animationController.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + state.animationController.reset(); + state.animationController.forward(); + } else if (status == AnimationStatus.dismissed) { + state.animationController.reset(); + state.animationController.forward(); + } + }); + } + + @override + void dispose() { + state.animationController.dispose(); + CallTalk().finishAVData(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.mainBackgroundColor, + resizeToAvoidBottomInset: false, + appBar: TitleAppBar( + barTitle: '图传全自动'.tr, + haveBack: true, + backgroundColor: AppColors.mainColor, + backAction: (){ + logic.udpHangUpAction(); + }, + ), + body: Obx(() => Column( + children: [ + SizedBox(height: 24.h), + SizedBox( + height: 0.6.sh, + child: state.listData.value.isEmpty + ? _buildWaitingView() + : _buildVideoView(), + ), + SizedBox(height: 30.h), + _buildBottomToolBar(), + SizedBox(height: 30.h), + ], + )), + ); + } + + Widget _buildWaitingView() { + double barWidth = MediaQuery.of(context).size.width - 60.w; + return Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(30.h), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + width: barWidth, + height: double.infinity, + child: Image.asset( + 'images/main/monitorBg.png', + fit: BoxFit.cover, + ), + ), + RotationTransition( + turns: state.animationController, + child: Image.asset( + 'images/main/realTime_connecting.png', + width: 300.w, + height: 300.w, + fit: BoxFit.contain, + ), + ), + ], + ), + ), + ); + } + + Widget _buildVideoView() { + double barWidth = MediaQuery.of(context).size.width - 60.w; + return PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(30.h), + child: Container( + width: barWidth, + height: double.infinity, + child: RotatedBox( + quarterTurns: startChartManage.rotateAngle ~/ 90, + child: RawImage( + image: state.currentImage.value, + fit: BoxFit.cover, + filterQuality: FilterQuality.high, + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildBottomToolBar() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 30.w), + padding: EdgeInsets.symmetric(vertical: 28.h, horizontal: 20.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30.h), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 12, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _circleButton( + icon: Icons.call, + color: Colors.green, + onTap: () { + if (state.talkStatus.value == + TalkStatus.passiveCallWaitingAnswer) { + // 接听 + logic.initiateAnswerCommand(); + } + }, + ), + _circleButton( + icon: Icons.call_end, + color: Colors.red, + onTap: () { + logic.udpHangUpAction(); + }, + ), + _circleButton( + icon: Icons.camera_alt, + color: Colors.blue, + onTap: () async { + if (state.talkStatus.value == + TalkStatus.answeredSuccessfully) { + await logic.captureAndSavePng(); + } + }, + ), + ], + ), + SizedBox(height: 36.h), + SlideAction( + height: 64.h, + borderRadius: 24.h, + elevation: 0, + innerColor: Colors.amber, + outerColor: Colors.amber.withOpacity(0.15), + sliderButtonIcon: Icon(Icons.lock, color: Colors.white, size: 40.w), + text: '滑动解锁', + textStyle: TextStyle(fontSize: 26.sp, color: Colors.black54, fontWeight: FontWeight.bold), + onSubmit: () { + // TODO: 实现滑动解锁逻辑 + logic.remoteOpenLock(); + }, + ), + ], + ), + ); + } + + Widget _circleButton( + {required IconData icon, + required Color color, + required VoidCallback onTap}) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 90.w, + height: 90.w, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.3), + blurRadius: 10, + offset: Offset(0, 4), + ), + ], + ), + child: Icon(icon, color: Colors.white, size: 48.w), + ), + ); + } +} diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_state.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_state.dart new file mode 100644 index 00000000..a7cd0efc --- /dev/null +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_state.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:get/get.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:get/state_manager.dart'; +import 'package:network_info_plus/network_info_plus.dart'; +import 'package:star_lock/talk/starChart/constant/talk_status.dart'; +import 'package:star_lock/talk/starChart/handle/other/talk_data_repository.dart'; +import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; +import 'package:star_lock/talk/starChart/status/star_chart_talk_status.dart'; + +import '../../../../tools/storage.dart'; + +enum NetworkStatus { + normal, // 0 + lagging, // 1 + delayed, // 2 + packetLoss // 3 +} + +class ImageTransmissionState{ + int udpSendDataFrameNumber = 0; // 帧序号 + // var isSenderAudioData = false.obs;// 是否要发送音频数据 + + Future userMobileIP = NetworkInfo().getWifiIP(); + Future userUid = Storage.getUid(); + + RxInt udpStatus = + 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话 + TextEditingController passwordTF = TextEditingController(); + + Rx listData = Uint8List(0).obs; //得到的视频流字节数据 + RxList listAudioData = [].obs; //得到的音频流字节数据 + GlobalKey globalKey = GlobalKey(); + + Timer? oneMinuteTimeTimer; // 定时器超过60秒关闭当前界面 + RxInt oneMinuteTime = 0.obs; // 定时器秒数 + + // 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次 + late Timer answerTimer; + late Timer hangUpTimer; + late Timer openDoorTimer; + Timer? fpsTimer; + late AnimationController animationController; + + late Timer autoBackTimer = + Timer(const Duration(seconds: 1), () {}); //发送30秒监视后自动返回 + late Timer realTimePicTimer = + Timer(const Duration(seconds: 1), () {}); //监视命令定时器 + RxInt elapsedSeconds = 0.obs; + + // 星图对讲相关状态 + List audioBuffer = [].obs; + List activeAudioBuffer = [].obs; + List activeVideoBuffer = [].obs; + + List videoBuffer = [].obs; + List videoBuffer2 = [].obs; + RxBool isPlaying = false.obs; // 是否开始播放 + Rx talkStatus = TalkStatus.none.obs; //星图对讲状态 + // 获取 startChartTalkStatus 的唯一实例 + final StartChartTalkStatus startChartTalkStatus = + StartChartTalkStatus.instance; + + // 通话数据流的单例流数据处理类 + final TalkDataRepository talkDataRepository = TalkDataRepository.instance; + RxInt lastFrameTimestamp = 0.obs; // 上一帧的时间戳,用来判断网络环境 + Rx networkStatus = + NetworkStatus.normal.obs; // 网络状态:0-正常 1-网络卡顿 2-网络延迟 3-网络丢包 + RxInt alertCount = 0.obs; // 网络状态提示计数器 + RxInt maxAlertNumber = 3.obs; // 网络状态提示最大提示次数 + RxBool isOpenVoice = true.obs; // 是否打开声音 + RxBool isRecordingScreen = false.obs; // 是否录屏中 + RxBool isRecordingAudio = false.obs; // 是否录音中 + Rx startRecordingAudioTime = DateTime.now().obs; // 开始录音时间 + Rx endRecordingAudioTime = DateTime.now().obs; // 结束录音时间 + RxInt recordingAudioTime = 0.obs; // 录音时间持续时间 + RxInt fps = 0.obs; // 添加 FPS 计数 + late VoiceProcessor? voiceProcessor; // 音频处理器、录音 + final int frameLength = 320; //录音视频帧长度为640 + final int sampleRate = 8000; //录音频采样率为8000 + List recordingAudioAllFrames = []; // 录制音频的所有帧 + List lockRecordingAudioAllFrames = []; // 录制音频的所有帧 + RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位) + RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位) + RxBool hasAudioData = false.obs; // 是否有音频数据 + RxInt lastAudioTimestamp = 0.obs; // 最后接收到的音频数据的时间戳 + Rx currentImage = Rx(null); +} \ No newline at end of file From 6dc16276221b63ef3413dae330a1fde194aaacb6 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 09:47:35 +0800 Subject: [PATCH 07/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E8=93=9D=E7=89=99?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E6=97=B6=E7=9A=84=E6=90=9C=E7=B4=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/blue/blue_manage.dart | 48 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/blue/blue_manage.dart b/lib/blue/blue_manage.dart index 0259a5c0..a955171b 100755 --- a/lib/blue/blue_manage.dart +++ b/lib/blue/blue_manage.dart @@ -187,10 +187,12 @@ class BlueManage { continue; } - final isMatch = _isMatch(scanResult - .advertisementData.serviceUuids - .map((e) => e.uuid) - .toList()); + final isMatch = _isMatch( + scanResult.advertisementData.serviceUuids + .map((e) => e.uuid) + .toList(), + isSingle: true, + ); if (isMatch && (scanResult.rssi >= -100)) { // 查询id相同的元素 @@ -273,6 +275,7 @@ class BlueManage { .map((e) => e.uuid) .toList(), deviceType: deviceType, + isSingle: false, ); // 判断名字为空的直接剔除 if (isMatch && (scanResult.rssi >= -100)) { @@ -316,8 +319,10 @@ class BlueManage { } /// 判断是否包含指定的uuid - bool _isMatch(List serviceUuids, {DeviceType deviceType = DeviceType.blue}) { - final List prefixes = getDeviceType(deviceType).map((e) => e.toLowerCase()).toList(); + bool _isMatch(List serviceUuids, + {DeviceType deviceType = DeviceType.blue, required bool isSingle}) { + final List prefixes = + getDeviceType(deviceType).map((e) => e.toLowerCase()).toList(); for (String uuid in serviceUuids) { final String cleanUuid = uuid.toLowerCase(); if (cleanUuid.length == 8) { @@ -325,11 +330,15 @@ class BlueManage { String pairStatus = cleanUuid.substring(4, 6); // 第4、5位(索引3和4) for (final prefix in prefixes) { if (cleanUuid.startsWith(prefix)) { - // 00=未配对,01=已配对 - if (pairStatus == '00') { - return true; // 未配对才返回true + if (isSingle) { + return true; // isSingle为true,前缀匹配即返回true + } else { + // 00=未配对,01=已配对 + if (pairStatus == '00') { + return true; // 未配对才返回true + } + // 已配对(01)不返回true,继续判断下一个uuid } - // 已配对(01)不返回true,继续判断下一个uuid } } } else { @@ -338,14 +347,19 @@ class BlueManage { final String thirdAndFourth = cleanUuid.substring(2, 4); // 索引2和3 for (final prefix in prefixes) { if (thirdAndFourth == prefix) { - // 判断配对状态(带横杠UUID的第31、32位,从1开始计数) - if (cleanUuid.length >= 32) { - String pairStatus = cleanUuid.substring(30, 32); // 第31、32位(从1开始计数) - // 00=未配对,01=已配对 - if (pairStatus == '00') { - return true; // 未配对才返回true + if (isSingle) { + return true; // isSingle为true,前缀匹配即返回true + } else { + // 判断配对状态(带横杠UUID的第31、32位,从1开始计数) + if (cleanUuid.length >= 32) { + String pairStatus = + cleanUuid.substring(30, 32); // 第31、32位(从1开始计数) + // 00=未配对,01=已配对 + if (pairStatus == '00') { + return true; // 未配对才返回true + } + // 已配对(01)不返回true,继续判断下一个uuid } - // 已配对(01)不返回true,继续判断下一个uuid } } } From 57a171b993cdf1e5bff347521d0dacf22f4bc128 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 09:47:55 +0800 Subject: [PATCH 08/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E5=AF=86=E7=A0=81?= =?UTF-8?q?=E6=8C=89=E7=85=A7=E9=94=81=E6=94=AF=E6=8C=81=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockDetail/lockDetail/lockDetail_page.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/lib/main/lockDetail/lockDetail/lockDetail_page.dart index ed3c9b68..f7605c48 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -1103,13 +1103,15 @@ class _LockDetailPageState extends State })); // 密码 - showWidgetArr.add(bottomItem('images/main/icon_main_password.png', '密码'.tr, - state.bottomBtnisEable.value, () { - Get.toNamed(Routers.passwordKeyListPage, - arguments: { - 'keyInfo': state.keyInfos.value - }); - })); + if (state.keyInfos.value.lockFeature!.password == 1) { + showWidgetArr.add(bottomItem('images/main/icon_main_password.png', + '密码'.tr, state.bottomBtnisEable.value, () { + Get.toNamed(Routers.passwordKeyListPage, + arguments: { + 'keyInfo': state.keyInfos.value + }); + })); + } // ic卡 if (state.keyInfos.value.lockFeature!.icCard == 1) { From f887bd37c436452e283470062e7dd77f1f588e88 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 09:48:08 +0800 Subject: [PATCH 09/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=A1=B9=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main/lockMian/entity/lockListInfo_entity.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/main/lockMian/entity/lockListInfo_entity.dart b/lib/main/lockMian/entity/lockListInfo_entity.dart index b98bb328..28c48457 100755 --- a/lib/main/lockMian/entity/lockListInfo_entity.dart +++ b/lib/main/lockMian/entity/lockListInfo_entity.dart @@ -361,6 +361,7 @@ class Bluetooth { class LockFeature { LockFeature({ this.password, + this.passwordIssue, this.icCard, this.fingerprint, this.fingerVein, @@ -381,6 +382,7 @@ class LockFeature { LockFeature.fromJson(Map json) { password = json['password']; + passwordIssue = json['passwordIssue']; icCard = json['icCard']; fingerprint = json['fingerprint']; fingerVein = json['fingerVein']; @@ -400,6 +402,7 @@ class LockFeature { } int? password; + int? passwordIssue; int? icCard; int? fingerprint; int? fingerVein; @@ -420,6 +423,7 @@ class LockFeature { Map toJson() { final Map data = {}; data['password'] = password; + data['passwordIssue'] = passwordIssue; data['icCard'] = icCard; data['fingerprint'] = fingerprint; data['fingerVein'] = fingerVein; From 06fc544f1a120b6810a6dabff00f321ad2c801ab Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 10:05:05 +0800 Subject: [PATCH 10/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=88=86=E8=BE=A8=E7=8E=87=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_expect_handler.dart | 4 +- lib/talk/starChart/star_chart_manage.dart | 2 + .../views/talkView/talk_view_page.dart | 165 ++++++++++++------ 3 files changed, 120 insertions(+), 51 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart index e9f3e598..792e7f2c 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart @@ -43,7 +43,9 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle startChartManage.stopCallRequestMessageTimer(); // talkViewState.rotateAngle.value = talkExpectResp.rotate ?? 0; startChartManage.rotateAngle = talkExpectResp.rotate; - AppLog.log('视频画面需要旋转:${talkExpectResp.rotate}'); + startChartManage.videoWidth = talkExpectResp.width; + startChartManage.videoHeight = talkExpectResp.height; + AppLog.log('视频画面需要旋转:${talkExpectResp.rotate},画面宽高:${talkExpectResp.width}-${talkExpectResp.height}'); // 收到预期数据的应答后,代表建立了连接,启动通话保持的监听 // 启动通话保持监听定时器(用来判断如果x秒内没有收到通话保持则执行的操作); talkePingOverTimeTimerManager.start(); diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index e5eddb59..d64cb808 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -114,6 +114,8 @@ class StartChartManage { final int _maxPayloadSize = 8 * 1024; // 分包大小 int rotateAngle = 0; // 视频旋转角度 + int videoWidth = 0; // 视频宽度 + int videoHeight = 0; // 视频高度 // 默认通话的期望数据格式 TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect; diff --git a/lib/talk/starChart/views/talkView/talk_view_page.dart b/lib/talk/starChart/views/talkView/talk_view_page.dart index 264e0252..7abb28b1 100644 --- a/lib/talk/starChart/views/talkView/talk_view_page.dart +++ b/lib/talk/starChart/views/talkView/talk_view_page.dart @@ -98,56 +98,55 @@ class _TalkViewPageState extends State child: Stack( alignment: Alignment.center, children: [ - Obx( - () { - final double screenWidth = MediaQuery.of(context).size.width; - final double screenHeight = MediaQuery.of(context).size.height; - - final double logicalWidth = MediaQuery.of(context).size.width; - final double logicalHeight = MediaQuery.of(context).size.height; - final double devicePixelRatio = - MediaQuery.of(context).devicePixelRatio; - - // 计算物理像素值 - final double physicalWidth = logicalWidth * devicePixelRatio; - final double physicalHeight = logicalHeight * devicePixelRatio; - - // 旋转后的图片尺寸 - const int rotatedImageWidth = 480; // 原始高度 - const int rotatedImageHeight = 864; // 原始宽度 - - // 计算缩放比例 - final double scaleWidth = physicalWidth / rotatedImageWidth; - final double scaleHeight = physicalHeight / rotatedImageHeight; - max(scaleWidth, scaleHeight); // 选择较大的缩放比例 - - return state.listData.value.isEmpty - ? Image.asset( - 'images/main/monitorBg.png', - width: screenWidth, - height: screenHeight, - fit: BoxFit.cover, - ) - : PopScope( - canPop: false, - child: RepaintBoundary( - key: state.globalKey, - child: SizedBox.expand( - child: RotatedBox( - quarterTurns: startChartManage.rotateAngle ~/ 90, - child: RawImage( - image: state.currentImage.value, - width: ScreenUtil().scaleWidth, - height: ScreenUtil().scaleHeight, - fit: BoxFit.cover, - filterQuality: FilterQuality.high, - ), - ), - ), - ), - ); - }, - ), + // 全屏背景图片或渐变色背景 + Obx(() { + if (state.listData.value.isEmpty) { + return SizedBox.expand( + child: Image.asset( + 'images/main/monitorBg.png', + fit: BoxFit.cover, + ), + ); + } + final int videoW = startChartManage.videoWidth; + final int videoH = startChartManage.videoHeight; + if (videoW == 320 && videoH == 240) { + return SizedBox.expand( + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF232526), + Color(0xFF414345), + ], + ), + ), + ), + ); + } + return const SizedBox.shrink(); + }), + // 视频窗口,分辨率判断 + Obx(() { + if (state.listData.value.isEmpty) { + return const SizedBox.shrink(); + } + final int videoW = startChartManage.videoWidth; + final int videoH = startChartManage.videoHeight; + if (videoW == 320 && videoH == 240) { + return Positioned( + top: 150.h, + left: 0, + right: 0, + child: _buildVideoWidget(), + ); + } else { + // 直接全屏显示 + return _buildVideoWidget(); + } + }), Obx(() => state.listData.value.isEmpty ? Positioned( bottom: 310.h, @@ -183,6 +182,8 @@ class _TalkViewPageState extends State ), ) : Container()), + + /// 工具栏 Positioned( bottom: 10.w, child: Container( @@ -614,4 +615,68 @@ class _TalkViewPageState extends State // UdpTalkDataHandler().resetDataRates(); super.dispose(); } + + Widget _buildVideoWidget() { + // 工具栏宽度 + double barWidth = 1.sw - 30.w * 2; + int videoW = startChartManage.videoWidth; + int videoH = startChartManage.videoHeight; + int quarterTurns = startChartManage.rotateAngle ~/ 90; + bool isRotated = quarterTurns % 2 == 1; + // 旋转后宽高互换 + double videoAspect = isRotated ? videoW / videoH : videoH / videoW; + double containerHeight = + barWidth * (isRotated ? videoW / videoH : videoH / videoW); + + if (videoW == 320 && videoH == 240) { + return Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(20.h), + child: Container( + width: barWidth, + height: containerHeight, + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF232526), + Color(0xFF414345), + ], + ), + ), + child: RotatedBox( + quarterTurns: quarterTurns, + child: RawImage( + image: state.currentImage.value, + fit: BoxFit.contain, + filterQuality: FilterQuality.high, + width: barWidth, + height: containerHeight, + ), + ), + ), + ), + ); + } else { + return PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: SizedBox.expand( + child: RotatedBox( + quarterTurns: startChartManage.rotateAngle ~/ 90, + child: RawImage( + image: state.currentImage.value, + width: ScreenUtil().scaleWidth, + height: ScreenUtil().scaleHeight, + fit: BoxFit.cover, + filterQuality: FilterQuality.high, + ), + ), + ), + ), + ); + } + } } From a7a70f41b1d43329f0953896f1005ef7106e543d Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 10:19:43 +0800 Subject: [PATCH 11/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=88=86=E8=BE=A8=E7=8E=87=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_accept_handler.dart | 31 +++++++-- .../handle/impl/udp_talk_request_handler.dart | 66 +++++++++++++++++-- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_accept_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_accept_handler.dart index baaae2e8..35389a70 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_accept_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_accept_handler.dart @@ -15,6 +15,7 @@ import 'package:star_lock/talk/starChart/proto/generic.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_accept.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/tools/commonDataManage.dart'; +import 'package:star_lock/tools/storage.dart'; import '../../star_chart_manage.dart'; @@ -34,7 +35,7 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle // 停止同意接听的重发 startChartManage.stopTalkAcceptTimer(); // 接听之后增加期望音频的接收 - _handleSendExpect(); + _handleSendExpect(lockPeerID: scpMessage.FromPeerId!); // 停止播放铃声 stopRingtone(); // 设置状态为接听成功 @@ -79,11 +80,33 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle } /// 收到同意接听回复之后增加音频的期望数据 - void _handleSendExpect() { + void _handleSendExpect({ + required String lockPeerID, + }) async { final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo; - final isH264 = currentKeyInfo.lockFeature?.isH264 == 1; - final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + + var isH264 = currentKeyInfo.lockFeature?.isH264 == 1; + var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + + 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) { + isH264 = lockInfo.lockFeature?.isH264 == 1; + isMJpeg = lockInfo.lockFeature?.isMJpeg == 1; + } + } + } + } + }); + } // 优先使用H264,其次是MJPEG if (isH264) { diff --git a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart index 55a2ac27..7ce19cf6 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart @@ -37,7 +37,10 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle startChartManage.ToPeerId = scpMessage.FromPeerId!; startChartManage.lockPeerId = scpMessage.FromPeerId!; // 处理收到接听请求后的事件 - _talkRequestEvent(talkObjectName: talkReq.callerName); + _talkRequestEvent( + talkObjectName: talkReq.callerName, + lockPeerID: scpMessage.FromPeerId!, + ); // 回复成功 replySuccessMessage(scpMessage); @@ -78,9 +81,12 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle void handleRealTimeData(ScpMessage scpMessage) {} // 来电事件的处理 - void _talkRequestEvent({required String talkObjectName}) { + void _talkRequestEvent({ + required String talkObjectName, + required String lockPeerID, + }) async { // 发送预期数据、通知锁板需要获取视频数据 - _handleRequestSendExpect(); + _handleRequestSendExpect(lockPeerID: lockPeerID); // 播放铃声 //test:使用自定义铃声 playRingtone(); @@ -88,6 +94,33 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle // _showTalkRequestNotification(talkObjectName: talkObjectName); // 设置为等待接听状态 talkStatus.setPassiveCallWaitingAnswer(); + + // 获取锁支持项 + final LockListInfoItemEntity currentKeyInfo = + CommonDataManage().currentKeyInfo; + var isWifiLockType = currentKeyInfo.lockFeature?.wifiLockType == 1; + + 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) { + isWifiLockType = lockInfo.lockFeature?.wifiLockType == 1; + } + } + } + } + }); + } + if (isWifiLockType) { + Get.toNamed(Routers.imageTransmissionView); + return; + } if (startChartManage .getDefaultTalkExpect() .videoType @@ -170,12 +203,33 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle } /// app收到的对讲请求后,发送的预期数据 - void _handleRequestSendExpect() { + void _handleRequestSendExpect({ + required String lockPeerID, + }) async { final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo; - final isH264 = currentKeyInfo.lockFeature?.isH264 == 1; - final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + var isH264 = currentKeyInfo.lockFeature?.isH264 == 1; + var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + + 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) { + isH264 = lockInfo.lockFeature?.isH264 == 1; + isMJpeg = lockInfo.lockFeature?.isMJpeg == 1; + } + } + } + } + }); + } // 优先使用H264,其次是MJPEG if (isH264) { // 锁支持H264,发送H264视频和G711音频期望 From 7d27de087d8f47ccc778f35cb647ab7bcb5987c3 Mon Sep 17 00:00:00 2001 From: liyi Date: Tue, 13 May 2025 14:30:10 +0800 Subject: [PATCH 12/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index 09ba91d5..68d2d366 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -280,6 +280,8 @@ dependencies: video_thumbnail: ^0.5.3 # 角标管理 flutter_app_badger: ^1.3.0 + # 滑块支持 + slide_to_act: ^2.0.2 From 160c4d33acaeeaf9ef316c36029714b3d2f3454e Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 14 May 2025 09:08:41 +0800 Subject: [PATCH 13/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E7=BC=93=E5=86=B2?= =?UTF-8?q?=E5=8C=BA=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockDetail/lockDetail_logic.dart | 2 +- lib/talk/starChart/star_chart_manage.dart | 6 ++- .../native/talk_view_native_decode_logic.dart | 37 +++++++++++-------- .../native/talk_view_native_decode_state.dart | 2 +- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 7c086ba5..cf9eda27 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -773,7 +773,7 @@ class LockDetailLogic extends BaseGetXController { return; } // 重置丢包率监控 - PacketLossStatistics().reset(); + // PacketLossStatistics().reset(); // 发送监控id StartChartManage() .startCallRequestMessageTimer(ToPeerId: network!.peerId ?? ''); diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index d64cb808..2d8b4529 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -606,7 +606,7 @@ class StartChartManage { void startTalkRejectMessageTimer() async { try { int count = 0; - final int maxCount = 10; // 最大执行次数为10秒 + final int maxCount = 3; // 最大执行次数为10秒 talkRejectTimer ??= Timer.periodic( Duration(seconds: _defaultIntervalTime), @@ -632,6 +632,8 @@ class StartChartManage { stopCallRequestMessageTimer(); stopSendingRbcuInfoMessages(); stopSendingRbcuProBeMessages(); + stopTalkAcceptTimer(); + stopCallRequestMessageTimer(); // 取消定时器 talkePingOverTimeTimerManager.cancel(); @@ -730,6 +732,8 @@ class StartChartManage { stopCallRequestMessageTimer(); stopSendingRbcuInfoMessages(); stopSendingRbcuProBeMessages(); + stopTalkAcceptTimer(); + stopCallRequestMessageTimer(); // 取消定时器 talkePingOverTimeTimerManager.cancel(); talkDataOverTimeTimerManager.cancel(); 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 033c3f57..af05d336 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 @@ -148,7 +148,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { ) { // 只允许frameSeq严格递增,乱序或重复帧直接丢弃 if (_lastFrameSeq != null && frameSeq <= _lastFrameSeq!) { - // 可选:打印日志 AppLog.log('丢弃乱序或重复帧: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); return; } @@ -162,13 +161,19 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 'pts': pts, }; + // 如果缓冲区超出最大大小,优先丢弃P/B帧 + while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) { + int pbIndex = state.h264FrameBuffer.indexWhere((f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.P); + if (pbIndex != -1) { + state.h264FrameBuffer.removeAt(pbIndex); + } else { + state.h264FrameBuffer.removeAt(0); + } + } + // 将帧添加到缓冲区 state.h264FrameBuffer.add(frameMap); - - // 如果缓冲区超出最大大小,移除最早的帧 - while (state.h264FrameBuffer.length > state.maxFrameBufferSize) { - state.h264FrameBuffer.removeAt(0); - } } /// 启动帧处理定时器 @@ -212,16 +217,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 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); - // } + 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); + } // 实时写入h264文件 // _appendH264FrameToFile(frameData, frameType); 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 8703012d..b63ffc29 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 @@ -109,7 +109,7 @@ class TalkViewNativeDecodeState { // H264帧缓冲区相关 final List> h264FrameBuffer = >[]; // H264帧缓冲区,存储帧数据和类型 - final int maxFrameBufferSize = 7; // 最大缓冲区大小 + final int maxFrameBufferSize = 15; // 最大缓冲区大小 final int targetFps = 30; // 目标解码帧率,只是为了快速填充native的缓冲区 Timer? frameProcessTimer; // 帧处理定时器 bool isProcessingFrame = false; // 是否正在处理帧 From 4de271603d706c412e02ba59efad622a8ba7e462 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 14 May 2025 14:53:30 +0800 Subject: [PATCH 14/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=B8=B8=E5=BC=80?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E8=93=9D=E7=89=99=E5=91=BD=E4=BB=A4=E5=8F=91?= =?UTF-8?q?=E9=80=81=E9=A1=BA=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockSet/normallyOpenMode/normallyOpenMode_page.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_page.dart b/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_page.dart index ed696552..e203a907 100755 --- a/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_page.dart +++ b/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_page.dart @@ -118,6 +118,15 @@ class _NormallyOpenModePageState extends State with RouteA : SubmitBtn( btnName: '保存'.tr, onClick: () { + if (state.weekDays.value.isEmpty) { + logic.showToast('请选择常开日期'.tr); + return; + } + + if (state.endTimeMinute.value < state.beginTimeMinute.value) { + logic.showToast('结束时间不能小于开始时间哦'.tr); + return; + } logic.sendAutoLock(); }), )), From e754d008c5007ea7b161f3c77a39550e011b0c2d Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:45:56 +0800 Subject: [PATCH 15/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=9B=BE=E4=BC=A0?= =?UTF-8?q?=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/imageTransmission/image_transmission_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart index c7ce2678..3340151c 100644 --- a/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_page.dart @@ -58,7 +58,7 @@ class _ImageTransmissionPageState extends State backgroundColor: AppColors.mainBackgroundColor, resizeToAvoidBottomInset: false, appBar: TitleAppBar( - barTitle: '图传全自动'.tr, + barTitle: '图传'.tr, haveBack: true, backgroundColor: AppColors.mainColor, backAction: (){ From 069ef1b5924e88b8ef34381ae922cbb1b213b41f Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:46:14 +0800 Subject: [PATCH 16/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E8=BF=9C=E7=A8=8B?= =?UTF-8?q?=E5=BC=80=E9=94=81=E8=8E=B7=E5=8F=96lockId=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image_transmission_logic.dart | 58 +++++++++++-------- .../views/talkView/talk_view_logic.dart | 58 +++++++++++-------- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart index 8cc6f854..c9c42baf 100644 --- a/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart @@ -32,6 +32,8 @@ import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmiss 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 '../../../../tools/baseGetXController.dart'; @@ -517,34 +519,40 @@ class ImageTransmissionLogic 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); } } diff --git a/lib/talk/starChart/views/talkView/talk_view_logic.dart b/lib/talk/starChart/views/talkView/talk_view_logic.dart index 02ed3f25..cfe0d6f2 100644 --- a/lib/talk/starChart/views/talkView/talk_view_logic.dart +++ b/lib/talk/starChart/views/talkView/talk_view_logic.dart @@ -31,6 +31,8 @@ import 'package:star_lock/talk/starChart/star_chart_manage.dart'; 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 '../../../../tools/baseGetXController.dart'; @@ -514,34 +516,40 @@ class TalkViewLogic 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); } } From 90f94e1a9ac2eb5d878749f3da21bd534923cfb1 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:46:50 +0800 Subject: [PATCH 17/31] =?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; // 可选:高清、标清、流畅 } From e2f8400ddcc8bdf70caaeb763daad2cee7875771 Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:47:08 +0800 Subject: [PATCH 18/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=A1=B9=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_request_handler.dart | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart index 7ce19cf6..5b72e229 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart @@ -59,8 +59,11 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle // 收到对讲请求的应答 startChartManage.FromPeerId = scpMessage.ToPeerId!; startChartManage.ToPeerId = scpMessage.FromPeerId!; + startChartManage.lockPeerId = scpMessage.FromPeerId!; // 处理预期数据格式 - _handleResponseSendExpect(); + _handleResponseSendExpect( + lockPeerID: scpMessage.FromPeerId!, + ); // 发送预期数据 startChartManage.startTalkExpectTimer(); // 停止发送对讲请求 @@ -247,12 +250,33 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle } /// app主动发请求,收到回复后发送的预期数据 - void _handleResponseSendExpect() { + void _handleResponseSendExpect({ + required String lockPeerID, + }) async { final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo; - final isH264 = currentKeyInfo.lockFeature?.isH264 == 1; - final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + var isH264 = currentKeyInfo.lockFeature?.isH264 == 1; + var isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; + + 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) { + isH264 = lockInfo.lockFeature?.isH264 == 1; + isMJpeg = lockInfo.lockFeature?.isMJpeg == 1; + } + } + } + } + }); + } // 优先使用H264,其次是MJPEG if (isH264) { // 锁支持H264,发送H264视频和G711音频期望 From 17e9c0e5ed26dca4ce05f638cba8f5b3824279be Mon Sep 17 00:00:00 2001 From: liyi Date: Thu, 15 May 2025 16:48:50 +0800 Subject: [PATCH 19/31] =?UTF-8?q?fix:=E6=9B=B4=E6=96=B0=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 68d2d366..36b08c64 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -130,7 +130,7 @@ dependencies: video_decode_plugin: git: url: git@code.star-lock.cn:liyi/video_decode_plugin.git - ref: 38df1883f5108ec1ce590ba52318815333fded38 + ref: 68bb4b7fb637ef5a78856908e1bc464f50fe967a flutter_localizations: sdk: flutter From 755ec4965c02c9f9836a489650c23020572cd2d5 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 11:42:03 +0800 Subject: [PATCH 20/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/Gemfile | 1 + ios/Gemfile | 1 + 2 files changed, 2 insertions(+) diff --git a/android/Gemfile b/android/Gemfile index cdd3a6b3..5f5b8322 100644 --- a/android/Gemfile +++ b/android/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" gem "fastlane" +gem 'nkf', '0.2.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/ios/Gemfile b/ios/Gemfile index 21cb5dfc..cb7538c4 100644 --- a/ios/Gemfile +++ b/ios/Gemfile @@ -5,3 +5,4 @@ gem 'cocoapods', '1.14.3' gem 'public_suffix', '~> 4.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) +gem 'nkf', '0.2.0' From 7a73356ed208aae12174bc7c498dcf4a597b2bb0 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 12:02:34 +0800 Subject: [PATCH 21/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17a01616..254f67e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,8 +66,9 @@ variables: before_script: - ls -li - export NEXT_VERSION="$(cat app_new.version)" -# - flutter pub get + - bundle config set --local path 'vendor/bundle' - bundle install --gemfile android/Gemfile --quiet + - gem pristine --all || true cache: paths: - app_new.version @@ -77,8 +78,9 @@ variables: before_script: - ls -li - export NEXT_VERSION="$(cat app_new.version)" -# - flutter pub get + - bundle config set --local path 'vendor/bundle' - bundle install --gemfile ios/Gemfile --quiet + - gem pristine --all || true cache: paths: - app_new.version From 47d9c4d2eb3887da355e5b658c9cc887f4a7c263 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 15:17:58 +0800 Subject: [PATCH 22/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 254f67e1..a5f0e421 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,11 +64,27 @@ variables: .setup_fastlane_android: extends: .build_rule before_script: + - export PATH="$HOME/.rbenv/bin:$PATH" + - eval "$(rbenv init -)" + - rbenv global 2.7.8 + - export PATH="$HOME/.rbenv/shims:$PATH" + - which ruby + - ruby -v + - gem install bundler - ls -li - export NEXT_VERSION="$(cat app_new.version)" - bundle config set --local path 'vendor/bundle' - bundle install --gemfile android/Gemfile --quiet - gem pristine --all || true + script: + - echo "=== DEBUG INFO (android) ===" + - which ruby + - ruby -v + - which gem + - gem -v + - echo $PATH + - env + - bash android/build.sh cache: paths: - app_new.version @@ -76,11 +92,27 @@ variables: .setup_fastlane_ios: extends: .build_rule before_script: + - export PATH="$HOME/.rbenv/bin:$PATH" + - eval "$(rbenv init -)" + - rbenv global 2.7.8 + - export PATH="$HOME/.rbenv/shims:$PATH" + - which ruby + - ruby -v + - gem install bundler - ls -li - export NEXT_VERSION="$(cat app_new.version)" - bundle config set --local path 'vendor/bundle' - bundle install --gemfile ios/Gemfile --quiet - gem pristine --all || true + script: + - echo "=== DEBUG INFO (ios) ===" + - which ruby + - ruby -v + - which gem + - gem -v + - echo $PATH + - env + - bash ios/build.sh cache: paths: - app_new.version From ac0d8073eeb965afb0cd00bf01e40e9b20636953 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 15:25:03 +0800 Subject: [PATCH 23/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a5f0e421..3134ae08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,19 +64,24 @@ variables: .setup_fastlane_android: extends: .build_rule before_script: + # 初始化rbenv环境,确保使用用户级Ruby - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 - export PATH="$HOME/.rbenv/shims:$PATH" - - which ruby - - ruby -v - - gem install bundler + - which ruby # 输出当前使用的ruby路径,便于调试 + - ruby -v # 输出当前ruby版本,便于调试 + # 切换到国内RubyGems镜像源,加速gem下载(如在中国大陆) + - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 + # 优先使用已存在的bundler,若无则用国内源安装,减少重复安装耗时 + - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" - - bundle config set --local path 'vendor/bundle' + - bundle config set --local path 'vendor/bundle' # 避免全局权限问题,依赖装到本地 - bundle install --gemfile android/Gemfile --quiet - - gem pristine --all || true + - gem pristine --all || true # 修复所有未编译的gem扩展 script: + # 输出调试信息,便于后续排查环境问题 - echo "=== DEBUG INFO (android) ===" - which ruby - ruby -v @@ -92,19 +97,24 @@ variables: .setup_fastlane_ios: extends: .build_rule before_script: + # 初始化rbenv环境,确保使用用户级Ruby - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 - export PATH="$HOME/.rbenv/shims:$PATH" - - which ruby - - ruby -v - - gem install bundler + - which ruby # 输出当前使用的ruby路径,便于调试 + - ruby -v # 输出当前ruby版本,便于调试 + # 切换到国内RubyGems镜像源,加速gem下载(如在中国大陆) + - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 + # 优先使用已存在的bundler,若无则用国内源安装,减少重复安装耗时 + - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" - - bundle config set --local path 'vendor/bundle' + - bundle config set --local path 'vendor/bundle' # 避免全局权限问题,依赖装到本地 - bundle install --gemfile ios/Gemfile --quiet - - gem pristine --all || true + - gem pristine --all || true # 修复所有未编译的gem扩展 script: + # 输出调试信息,便于后续排查环境问题 - echo "=== DEBUG INFO (ios) ===" - which ruby - ruby -v From 3ab1e5e4519649e8b7a0d0bc02c35acea9ebe743 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 15:43:00 +0800 Subject: [PATCH 24/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/build.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ios/build.sh b/ios/build.sh index a8a977bf..b492d623 100755 --- a/ios/build.sh +++ b/ios/build.sh @@ -10,6 +10,18 @@ cd ${CI_PROJECT_DIR}/ios #bundle exec pod install echo "ENV_BUILD_TAG:${ENV_BUILD_TAG},ENV_BUILD_BRANCH:${ENV_BUILD_BRANCH}" regex='^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z]+\.[0-9]+)?$' + +# ==== 调试输出,确认环境和依赖 ==== +echo "=== FASTLANE/GEM/ENV DEBUG ===" +which fastlane +fastlane -v +which bundle +bundle -v +echo $PATH +gem list | grep fastlane +gem list | grep digest-crc +# ==== END DEBUG ==== + if [[ "${ENV_BUILD_BRANCH}" == "canary_release" ]]; then echo "===build canary_release: ${NEXT_VERSION}" export ENV_BUILD_TAG=${NEXT_VERSION} From 3201f839eb6a81a4dc5bb538bdac001e4605cd59 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 16:00:39 +0800 Subject: [PATCH 25/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3134ae08..d29c133a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,7 +64,7 @@ variables: .setup_fastlane_android: extends: .build_rule before_script: - # 初始化rbenv环境,确保使用用户级Ruby + - rm -rf ~/.gem ~/.bundle vendor/bundle # 清理依赖缓存,防止环境污染 - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 @@ -97,7 +97,7 @@ variables: .setup_fastlane_ios: extends: .build_rule before_script: - # 初始化rbenv环境,确保使用用户级Ruby + - rm -rf ~/.gem ~/.bundle vendor/bundle # 清理依赖缓存,防止环境污染 - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 @@ -161,7 +161,15 @@ generate_next_version: build_android: stage: build-artifacts extends: .setup_fastlane_android - script: bash android/build.sh + script: + - echo "=== DEBUG INFO (android) ===" + - which ruby + - ruby -v + - which gem + - gem -v + - echo $PATH + - env + - bash android/build.sh artifacts: paths: - build/app/outputs/flutter-apk/ @@ -169,7 +177,15 @@ build_android: build_ios: stage: build-artifacts extends: .setup_fastlane_ios + needs: [build_android] # 确保build_ios在build_android完成后再执行 script: + - echo "=== DEBUG INFO (ios) ===" + - which ruby + - ruby -v + - which gem + - gem -v + - echo $PATH + - env - bash ios/build.sh artifacts: paths: From 448268ecc7d404d1963a8b89839157358741e436 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 16:21:41 +0800 Subject: [PATCH 26/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d29c133a..2b1ce4bb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -64,21 +64,19 @@ variables: .setup_fastlane_android: extends: .build_rule before_script: - - rm -rf ~/.gem ~/.bundle vendor/bundle # 清理依赖缓存,防止环境污染 + - rm -rf ~/.gem ~/.bundle vendor/bundle_android # 强烈建议每次清理,防止并发/缓存污染 - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 - export PATH="$HOME/.rbenv/shims:$PATH" - which ruby # 输出当前使用的ruby路径,便于调试 - ruby -v # 输出当前ruby版本,便于调试 - # 切换到国内RubyGems镜像源,加速gem下载(如在中国大陆) - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 - # 优先使用已存在的bundler,若无则用国内源安装,减少重复安装耗时 - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" - - bundle config set --local path 'vendor/bundle' # 避免全局权限问题,依赖装到本地 - - bundle install --gemfile android/Gemfile --quiet + - bundle config set --local path 'vendor/bundle_android' # Android独立依赖目录 + - bundle install --gemfile android/Gemfile # 去掉--quiet,便于观察进度 - gem pristine --all || true # 修复所有未编译的gem扩展 script: # 输出调试信息,便于后续排查环境问题 @@ -97,21 +95,19 @@ variables: .setup_fastlane_ios: extends: .build_rule before_script: - - rm -rf ~/.gem ~/.bundle vendor/bundle # 清理依赖缓存,防止环境污染 + - rm -rf ~/.gem ~/.bundle vendor/bundle_ios # 强烈建议每次清理,防止并发/缓存污染 - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - rbenv global 2.7.8 - export PATH="$HOME/.rbenv/shims:$PATH" - which ruby # 输出当前使用的ruby路径,便于调试 - ruby -v # 输出当前ruby版本,便于调试 - # 切换到国内RubyGems镜像源,加速gem下载(如在中国大陆) - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 - # 优先使用已存在的bundler,若无则用国内源安装,减少重复安装耗时 - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" - - bundle config set --local path 'vendor/bundle' # 避免全局权限问题,依赖装到本地 - - bundle install --gemfile ios/Gemfile --quiet + - bundle config set --local path 'vendor/bundle_ios' # iOS独立依赖目录 + - bundle install --gemfile ios/Gemfile # 去掉--quiet,便于观察进度 - gem pristine --all || true # 修复所有未编译的gem扩展 script: # 输出调试信息,便于后续排查环境问题 From a053e238437ef9b82a7ea21282c89d01bdf5dea8 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 16:25:38 +0800 Subject: [PATCH 27/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b1ce4bb..835d30d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -173,7 +173,6 @@ build_android: build_ios: stage: build-artifacts extends: .setup_fastlane_ios - needs: [build_android] # 确保build_ios在build_android完成后再执行 script: - echo "=== DEBUG INFO (ios) ===" - which ruby From faca7eae3ec4cc6c6804aa825ad7014727faea4d Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 16:39:49 +0800 Subject: [PATCH 28/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 ++ android/Gemfile | 2 +- ios/Gemfile | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 835d30d9..bc8c4c69 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,6 +72,7 @@ variables: - which ruby # 输出当前使用的ruby路径,便于调试 - ruby -v # 输出当前ruby版本,便于调试 - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 + - bundle config mirror.https://rubygems.org https://gems.ruby-china.com - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" @@ -103,6 +104,7 @@ variables: - which ruby # 输出当前使用的ruby路径,便于调试 - ruby -v # 输出当前ruby版本,便于调试 - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 + - bundle config mirror.https://rubygems.org https://gems.ruby-china.com - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" diff --git a/android/Gemfile b/android/Gemfile index 5f5b8322..3de6080f 100644 --- a/android/Gemfile +++ b/android/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source "https://gems.ruby-china.com" gem "fastlane" gem 'nkf', '0.2.0' diff --git a/ios/Gemfile b/ios/Gemfile index cb7538c4..8a7082c8 100644 --- a/ios/Gemfile +++ b/ios/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source "https://gems.ruby-china.com" gem "fastlane" gem 'cocoapods', '1.14.3' From 84ca900ddfe0f9bd6628355c3e8d50fe3e066805 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 16:45:21 +0800 Subject: [PATCH 29/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- android/Gemfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bc8c4c69..69738d60 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,7 +72,7 @@ variables: - which ruby # 输出当前使用的ruby路径,便于调试 - ruby -v # 输出当前ruby版本,便于调试 - gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/ # 如在国外可移除此行 - - bundle config mirror.https://rubygems.org https://gems.ruby-china.com + - bundle config mirror.https://rubygems.org https://mirrors.aliyun.com/rubygems/ - bundle -v || gem install bundler --source https://gems.ruby-china.com/ - ls -li - export NEXT_VERSION="$(cat app_new.version)" diff --git a/android/Gemfile b/android/Gemfile index 3de6080f..79087196 100644 --- a/android/Gemfile +++ b/android/Gemfile @@ -1,4 +1,4 @@ -source "https://gems.ruby-china.com" +source "https://mirrors.aliyun.com/rubygems/" gem "fastlane" gem 'nkf', '0.2.0' From 7d4e2574040094cd1bfbe08a91e95831ab1fcb2f Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 17:06:49 +0800 Subject: [PATCH 30/31] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E9=9D=99=E9=9F=B3=E6=97=B6=E4=BD=BF=E7=94=A8=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E7=9A=84=E6=B8=85=E6=99=B0=E5=BA=A6=E4=BD=9C=E4=B8=BA=E6=9C=9F?= =?UTF-8?q?=E6=9C=9B=E6=95=B0=E6=8D=AE=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/native/talk_view_native_decode_logic.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 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 606ae73c..9ddf4a57 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 @@ -576,17 +576,25 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { /// 更新发送预期数据 void updateTalkExpect() { + // 清晰度与VideoTypeE的映射 + final Map qualityToVideoType = { + '标清': VideoTypeE.H264, + '高清': VideoTypeE.H264_720P, + // 可扩展更多清晰度 + }; TalkExpectReq talkExpectReq = TalkExpectReq(); state.isOpenVoice.value = !state.isOpenVoice.value; + // 根据当前清晰度动态设置videoType + VideoTypeE currentVideoType = qualityToVideoType[state.currentQuality.value] ?? VideoTypeE.H264; if (!state.isOpenVoice.value) { talkExpectReq = TalkExpectReq( - videoType: [VideoTypeE.H264], + videoType: [currentVideoType], audioType: [], ); showToast('已静音'.tr); } else { talkExpectReq = TalkExpectReq( - videoType: [VideoTypeE.H264], + videoType: [currentVideoType], audioType: [AudioTypeE.G711], ); } From 8e122e5a09b338ba48e5e98e8ae8de58d407c1e6 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 16 May 2025 17:07:05 +0800 Subject: [PATCH 31/31] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E6=B7=B7mjpeg=E6=9C=9F=E6=9C=9B=E6=95=B0=E6=8D=AE=E7=9A=84?= =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_request_handler.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart index 5b72e229..195c2ab6 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_request_handler.dart @@ -237,15 +237,18 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle if (isH264) { // 锁支持H264,发送H264视频和G711音频期望 startChartManage.sendOnlyH264VideoTalkExpectData(); - print('app收到的对讲请求后,发送的预期数据=========锁支持H264,发送H264视频格式期望数据'); + print( + 'app收到的对讲请求后,发送的预期数据=========锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}'); } else if (isMJpeg) { // 锁只支持MJPEG,发送图像视频和G711音频期望 startChartManage.sendOnlyImageVideoTalkExpectData(); - print('app收到的对讲请求后,发送的预期数据=========锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据'); + print( + 'app收到的对讲请求后,发送的预期数据=========锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}'); } else { // 默认使用图像视频 startChartManage.sendOnlyImageVideoTalkExpectData(); - print('app收到的对讲请求后,发送的预期数据=========锁不支持H264和MJPEG,默认发送图像视频格式期望数据'); + print( + 'app收到的对讲请求后,发送的预期数据=========锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}'); } } @@ -281,15 +284,18 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle if (isH264) { // 锁支持H264,发送H264视频和G711音频期望 startChartManage.sendH264VideoAndG711AudioTalkExpectData(); - print('app主动发请求,收到回复后发送的预期数据=======锁支持H264,发送H264视频格式期望数据'); + AppLog.log( + 'app主动发对讲请求,收到回复后发送的预期数据=======锁支持H264,发送H264视频格式期望数据,peerID=${lockPeerID}'); } else if (isMJpeg) { // 锁只支持MJPEG,发送图像视频和G711音频期望 startChartManage.sendImageVideoAndG711AudioTalkExpectData(); - print('app主动发请求,收到回复后发送的预期数据=======锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据'); + AppLog.log( + 'app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264,支持MJPEG,发送MJPEG视频格式期望数据,peerID=${lockPeerID}'); } else { // 默认使用图像视频 startChartManage.sendImageVideoAndG711AudioTalkExpectData(); - print('app主动发请求,收到回复后发送的预期数据=======锁不支持H264和MJPEG,默认发送图像视频格式期望数据'); + AppLog.log( + 'app主动发对讲请求,收到回复后发送的预期数据=======锁不支持H264和MJPEG,默认发送MJPEG视频格式期望数据,peerID=${lockPeerID}'); } } }