diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart old mode 100755 new mode 100644 index 0956e9ed..2b47048d --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -5,7 +5,6 @@ import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/talk/call/callTalk.dart'; -import 'package:star_lock/talk/startChart/start_chart_manage.dart'; import 'package:star_lock/talk/udp/udp_talkClass.dart'; import '../../../../app_settings/app_settings.dart'; @@ -23,11 +22,8 @@ class LockMonitoringLogic extends BaseGetXController { state.voiceProcessor = VoiceProcessor.instance; } -/* -旧协议暂时不用 /// 收到UDP发送的状态 StreamSubscription? _getUDPStatusRefreshUIEvent; - void _getUDPStatusRefreshUIAction() { _getUDPStatusRefreshUIEvent = eventBus.on().listen((event) async { @@ -57,7 +53,6 @@ class LockMonitoringLogic extends BaseGetXController { } }); } - */ //发起接听命令,每隔一秒钟发一次,六秒无应答则失败 void initiateUdpAnswerAction() { @@ -79,9 +74,6 @@ class LockMonitoringLogic extends BaseGetXController { UDPTalkClass().callNoAnswer(1); } }); - - // 发送同意接听消息 - StartChartManage().sendTalkAcceptMessage(); } /// 接听 @@ -112,9 +104,6 @@ class LockMonitoringLogic extends BaseGetXController { UDPTalkClass().callNoAnswer(2); } }); - - // 发送拒绝接听消息 - StartChartManage().sendTalkRejectMessage(); } /// 挂断 @@ -416,7 +405,7 @@ class LockMonitoringLogic extends BaseGetXController { void onReady() { super.onReady(); - // _getUDPStatusRefreshUIAction(); + _getUDPStatusRefreshUIAction(); initRecorder(); } @@ -430,7 +419,7 @@ class LockMonitoringLogic extends BaseGetXController { void onClose() { CallTalk().finishAVData(); stopProcessing(); - // _getUDPStatusRefreshUIEvent!.cancel(); + _getUDPStatusRefreshUIEvent!.cancel(); state.getTVDataRefreshUIEvent!.cancel(); if (state.oneMinuteTimeTimer != null) { diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart old mode 100755 new mode 100644 index 03588c94..52998c78 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'dart:ui' as ui; @@ -11,18 +10,16 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:star_lock/app_settings/app_colors.dart'; -import 'package:star_lock/app_settings/app_settings.dart'; -import 'package:star_lock/main/lockDetail/monitoring/monitoring/star_chart_logic.dart'; +import 'package:star_lock/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart'; import 'package:star_lock/talk/call/callTalk.dart'; -import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; -import 'package:star_lock/talk/startChart/webView/h264_web_view.dart'; import 'package:star_lock/talk/udp/udp_manage.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; -import 'package:star_lock/tools/showTFView.dart'; +import '../../../../app_settings/app_colors.dart'; +import '../../../../app_settings/app_settings.dart'; +import '../../../../login/selectCountryRegion/common/index.dart'; +import '../../../../tools/showTFView.dart'; import 'lockMonitoring_logic.dart'; -import 'lockMonitoring_state.dart'; class LockMonitoringPage extends StatefulWidget { const LockMonitoringPage({Key? key}) : super(key: key); @@ -32,13 +29,12 @@ class LockMonitoringPage extends StatefulWidget { } class _LockMonitoringPageState extends State { - final StarChartLogic logic = Get.put(StarChartLogic()); - final LockMonitoringState state = Get.find().state; + final LockMonitoringLogic logic = Get.put(LockMonitoringLogic()); + final LockMonitoringState state = Get.find().state; @override void initState() { super.initState(); - initAsync(); _getTVDataRefreshUIAction(); } @@ -50,156 +46,242 @@ class _LockMonitoringPageState extends State { @override Widget build(BuildContext context) { return PopScope( - canPop: false, - child: RepaintBoundary( - key: state.globalKey, + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: Container( + width: 1.sw, + height: 1.sh, + color: Colors.transparent, + child: Stack( + children: [ + Image.memory( + state.listPhotoData.value, + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, //contain-原比例 none-原始图片 + filterQuality: FilterQuality.high, + errorBuilder: (BuildContext context, Object error, + StackTrace? stackTrace) { + return Container(color: Colors.transparent); + }, + ), + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + final String sec = (state.oneMinuteTime.value % 60) + .toString() + .padLeft(2, '0'); + final String min = (state.oneMinuteTime.value ~/ 60) + .toString() + .padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('$min:$sec', + style: TextStyle( + fontSize: 26.sp, color: Colors.white)), + ]); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + // height: 300.h, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h)), + child: Column( + children: [ + SizedBox(height: 20.h), + bottomTopBtnWidget(), + SizedBox(height: 20.h), + bottomBottomBtnWidget(), + SizedBox(height: 20.h), + ], + ), + )) + ], + ), + ), + )); + } + + Widget bottomTopBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + // 打开关闭声音 + GestureDetector( + onTap: () { + state.isOpenVoice.value = !state.isOpenVoice.value; + }, child: Container( - width: 1.sw, - height: 1.sh, - color: Colors.transparent, - child: _buildTalkView(isMpeg4: false), + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Obx(() => Image( + width: 40.w, + height: 40.w, + image: state.isOpenVoice.value + ? const AssetImage( + 'images/main/icon_lockDetail_monitoringCloseVoice.png') + : const AssetImage( + 'images/main/icon_lockDetail_monitoringOpenVoice.png'))), ), ), - ); + SizedBox(width: 60.w), + // 截图 + GestureDetector( + onTap: captureAndSavePng, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Image( + width: 40.w, + height: 40.w, + image: const AssetImage( + 'images/main/icon_lockDetail_monitoringScreenshot.png')), + ), + ), + SizedBox(width: 60.w), + // 录制 + GestureDetector( + onTap: () { + // Get.toNamed(Routers.monitoringRealTimeScreenPage); + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Image( + width: 40.w, + height: 40.w, + image: const AssetImage( + 'images/main/icon_lockDetail_monitoringScreenRecording.png')), + ), + ), + ]); } - Widget buildTopButtons() { + Widget bottomBottomBtnWidget() { return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buildIconButton( - icon: state.isOpenVoice.value - ? 'images/main/icon_lockDetail_monitoringCloseVoice.png' - : 'images/main/icon_lockDetail_monitoringOpenVoice.png', - onTap: () { - state.isOpenVoice.value = !state.isOpenVoice.value; - }, - ), - SizedBox(width: 60.w), - buildIconButton( - icon: 'images/main/icon_lockDetail_monitoringScreenshot.png', - onTap: captureAndSavePng, - ), - SizedBox(width: 60.w), - buildIconButton( - icon: 'images/main/icon_lockDetail_monitoringScreenRecording.png', - onTap: () { - // Get.toNamed(Routers.monitoringRealTimeScreenPage); - }, - ), - ], - ); - } - - Widget buildBottomButtons() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - buildAnswerButton(), - buildIconButton( - icon: 'images/main/icon_lockDetail_hangUp.png', - label: '挂断'.tr, - color: Colors.red, - onTap: () async { - // logic.stopProcessing(); - // CallTalk().finishAVData(); - - if (!state.isClickHangUp.value) { - // logic.initiateUdpHangUpAction(3); - logic.initiateHangUpCommand(); + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // 接听 + Obx(() => bottomBtnItemWidget( + state.isClickAnswer.value == true + ? 'images/main/icon_lockDetail_monitoringUnTalkback.png' + : getAnswerBtnImg(), + state.isClickAnswer.value == true + ? '长按说话'.tr + : getAnswerBtnName(), + Colors.white, () async { + if (state.isClickAnswer.value == false) { + logic.initiateUdpAnswerAction(); + state.isClickAnswer.value = true; + } + }, longPress: () { + // 开始长按 + AppLog.log('onLongPress'); + state.listAudioData.value = []; + if (state.udpStatus.value == 8) { + state.udpStatus.value = 9; + } + // logic.readG711Data(); + logic.startProcessing(); + }, longPressUp: () async { + // 长按结束 + AppLog.log('onLongPressUp'); + if (state.udpStatus.value == 9) { + state.udpStatus.value = 8; + } + logic.stopProcessing(); + })), + bottomBtnItemWidget( + 'images/main/icon_lockDetail_hangUp.png', '挂断'.tr, Colors.red, + () async { + logic.stopProcessing(); + CallTalk().finishAVData(); + // 挂断 + if (state.isClickHangUp.value == false) { + logic.initiateUdpHangUpAction(3); state.isClickHangUp.value = true; } - }, - ), - buildIconButton( - icon: 'images/main/icon_lockDetail_monitoringUnlock.png', - label: '开锁'.tr, - color: AppColors.mainColor, - onTap: () { + // else { + // AppLog.log('点了这里?'); + // state.isClickHangUp.value = true; + // UDPTalkClass().stopLocalAudio(); + // logic.initiateUdpHangUpAction(4); + // } + }), + bottomBtnItemWidget( + 'images/main/icon_lockDetail_monitoringUnlock.png', + '开锁'.tr, + AppColors.mainColor, () { if (UDPManage().remoteUnlock == 1) { showDeletPasswordAlertDialog(context); } else { logic.showToast('请在锁设置中开启远程开锁'.tr); } - }, - ), - ], - ); + }) + ]); } - Widget buildAnswerButton() { - return Obx(() { - final bool isDuringCall = - state.talkStatus.value == TalkStatus.duringCall.index; - return buildIconButton( - icon: isDuringCall - ? 'images/main/icon_lockDetail_monitoringUnTalkback.png' - : 'images/main/icon_lockDetail_monitoringAnswerCalls.png', - label: isDuringCall ? '长按说话'.tr : '接听'.tr, - onTap: () async { - if (!state.isClickAnswer.value) { - logic.initiateAnswerCommand(); - state.isClickAnswer.value = true; - } - }, - onLongPress: () { - state.listAudioData.value = []; - // if (state.udpStatus.value == 8) { - // state.udpStatus.value = 9; - // } - logic.startProcessing(); - }, - onLongPressUp: () { - // if (state.udpStatus.value == 9) { - // state.udpStatus.value = 8; - // } - logic.stopProcessing(); - }, - ); - }); + String getAnswerBtnImg() { + switch (state.udpStatus.value) { + case 8: + return 'images/main/icon_lockDetail_monitoringUnTalkback.png'; + case 9: + return 'images/main/icon_lockDetail_monitoringTalkback.png'; + default: + return 'images/main/icon_lockDetail_monitoringAnswerCalls.png'; + } } - Widget buildIconButton({ - required String icon, - String? label, - Color color = Colors.white, - required Function() onTap, - Function()? onLongPress, - Function()? onLongPressUp, - }) { + String getAnswerBtnName() { + switch (state.udpStatus.value) { + case 8: + return '长按说话'.tr; + case 9: + return '松开发送'.tr; + default: + return '接听'.tr; + } + } + + Widget bottomBtnItemWidget( + String iconUrl, String name, Color backgroundColor, Function() onClick, + {Function()? longPress, Function()? longPressUp}) { final double wh = 80.w; return GestureDetector( - onTap: onTap, - onLongPress: onLongPress, - onLongPressUp: onLongPressUp, + onTap: onClick, + onLongPress: longPress, + onLongPressUp: longPressUp, child: SizedBox( - height: 140.h, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: wh, - height: wh, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2), + height: 140.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: wh, + height: wh, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)), + padding: EdgeInsets.all(20.w), + child: Image.asset(iconUrl, fit: BoxFit.fitWidth), ), - padding: EdgeInsets.all(20.w), - child: Image.asset(icon, fit: BoxFit.fitWidth), - ), - if (label != null) ...[ SizedBox(height: 20.w), Expanded( - child: Text( - label, - style: TextStyle(fontSize: 20.sp, color: Colors.white), - textAlign: TextAlign.center, - ), - ), + child: Text(name, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + textAlign: TextAlign.center)) ], - ], - ), - ), + )), ); } @@ -217,18 +299,23 @@ class _LockMonitoringPageState extends State { FilteringTextInputFormatter.allow(RegExp('[0-9]')), ], sureClick: () async { + //发送删除锁请求 if (state.passwordTF.text.isEmpty) { logic.showToast('请输入开锁密码'.tr); return; } + + // List numbers = state.passwordTF.text.split('').map((char) => int.parse(char)).toList(); + // 开锁 + // lockID final List numbers = []; final List lockIDData = utf8.encode(state.passwordTF.text); numbers.addAll(lockIDData); + // topBytes = getFixedLengthList(lockIDData, 20 - lockIDData.length); for (int i = 0; i < 6 - lockIDData.length; i++) { numbers.add(0); } - //todo: 开门暂未实现 - // logic.udpOpenDoorAction(numbers); + logic.udpOpenDoorAction(numbers); }, cancelClick: () { Get.back(); @@ -238,11 +325,16 @@ class _LockMonitoringPageState extends State { ); } - Future requestMicrophonePermission() async { + //获取麦克风权限 + Future requestMicrophonePermission() async { await logic.getPermissionStatus().then((bool value) async { if (!value) { return; } + + // state.isSenderAudioData.value = false; + // AppLog.log("发送接听了"); + // 刚进来是接听状态,然后改为长按对讲 }); } @@ -263,12 +355,15 @@ class _LockMonitoringPageState extends State { } 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'); @@ -278,19 +373,31 @@ class _LockMonitoringPageState extends State { } } + /// 收到视频流数据 void _getTVDataRefreshUIAction() { + // 蓝牙协议通知传输跟蓝牙之外的数据传输类不一样 eventBus state.getTVDataRefreshUIEvent = eventBus .on() .listen((GetTVDataRefreshUI event) async { if (event.tvList.isNotEmpty && event.tvList.length > 100) { + // 比较新旧数据是否相同 final Uint8List imageData = Uint8List.fromList(event.tvList); + if (!listEquals(state.listPhotoData.value, imageData)) { + // 更新状态 state.listPhotoData.value = imageData; + // 设置标志为true,表示需要更新UI state.shouldUpdateUI.value = true; + // WidgetsBinding.instance.addPostFrameCallback((_) { + // 调用setState方法之前检查标志,只有当标志为true时才更新UI if (state.shouldUpdateUI.value) { - setState(() {}); + setState(() { + // 更新UI + }); + // 更新完UI后将标志重新设置为false state.shouldUpdateUI.value = false; } + // }); } } }); @@ -302,109 +409,4 @@ class _LockMonitoringPageState extends State { logic.stopProcessing(); state.getTVDataRefreshUIEvent!.cancel(); } - - Widget _buildTalkView({required bool isMpeg4}) { - return isMpeg4 ? _buildMpeg4TalkView() : _buildH264TalkView(); - } - - Widget _buildMpeg4TalkView() { - return Stack( - children: [ - Image.memory( - state.listPhotoData.value, - gaplessPlayback: true, - width: 1.sw, - height: 1.sh, - fit: BoxFit.cover, - filterQuality: FilterQuality.high, - errorBuilder: - (BuildContext context, Object error, StackTrace? stackTrace) { - return Container(color: Colors.transparent); - }, - ), - Positioned( - top: ScreenUtil().statusBarHeight + 30.h, - width: 1.sw, - child: Obx(() { - final String sec = - (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); - final String min = - (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('$min:$sec', - style: TextStyle(fontSize: 26.sp, color: Colors.white)), - ], - ); - }), - ), - Positioned( - bottom: 10.w, - child: Container( - width: 1.sw - 30.w * 2, - margin: EdgeInsets.all(30.w), - decoration: BoxDecoration( - color: const Color(0xC83C3F41), - borderRadius: BorderRadius.circular(20.h), - ), - child: Column( - children: [ - SizedBox(height: 20.h), - buildTopButtons(), - SizedBox(height: 20.h), - buildBottomButtons(), - SizedBox(height: 20.h), - ], - ), - ), - ), - ], - ); - } - - Widget _buildH264TalkView() { - return Stack( - children: [ - H264WebView(), - Positioned( - top: ScreenUtil().statusBarHeight + 30.h, - width: 1.sw, - child: Obx(() { - final String sec = - (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); - final String min = - (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('$min:$sec', - style: TextStyle(fontSize: 26.sp, color: Colors.white)), - ], - ); - }), - ), - Positioned( - bottom: 10.w, - child: Container( - width: 1.sw - 30.w * 2, - margin: EdgeInsets.all(30.w), - decoration: BoxDecoration( - color: const Color(0xC83C3F41), - borderRadius: BorderRadius.circular(20.h), - ), - child: Column( - children: [ - SizedBox(height: 20.h), - buildTopButtons(), - SizedBox(height: 20.h), - buildBottomButtons(), - SizedBox(height: 20.h), - ], - ), - ), - ), - ], - ); - } } diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart old mode 100755 new mode 100644 index d26067bb..c0f07e83 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:get/get.dart'; import 'package:network_info_plus/network_info_plus.dart'; -import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; import '../../../../tools/storage.dart'; @@ -19,8 +18,8 @@ class LockMonitoringState { Future userMobileIP = NetworkInfo().getWifiIP(); Future userUid = Storage.getUid(); - // RxInt udpStatus = - // 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话 + RxInt udpStatus = + 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话 TextEditingController passwordTF = TextEditingController(); Rx listPhotoData = Uint8List(0).obs; //得到的视频流字节数据 @@ -32,6 +31,10 @@ class LockMonitoringState { RxBool isButtonDisabled = false.obs; //是否禁用按钮 final int frameLength = 320; //音视频帧长度为320 final int sampleRate = 8000; //音频采样率为8000 + // final int volumeHistoryCapacity = 5; //音量历史记录的容量 + // final double dbOffset = 50.0; //用于音量计算的偏移量 + // var volumeHistory = [].obs; //用于存储音量历史记录的列表 + // var smoothedVolumeValue = 0.0.obs; //存储平滑后的音量值 RxString errorMessage = ''.obs; List> allFrames = >[]; @@ -52,6 +55,4 @@ class LockMonitoringState { late Timer openDoorTimer = Timer(const Duration(seconds: 1), () {}); //开门命令定时器 RxInt openDoorSeconds = 0.obs; - - RxInt talkStatus = 0.obs; //星图对讲状态 } diff --git a/lib/main/lockDetail/monitoring/monitoring/star_chart_logic.dart b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_logic.dart similarity index 90% rename from lib/main/lockDetail/monitoring/monitoring/star_chart_logic.dart rename to lib/main/lockDetail/monitoring/star_chart_h264/star_chart_logic.dart index b90c1d14..dc69806b 100644 --- a/lib/main/lockDetail/monitoring/monitoring/star_chart_logic.dart +++ b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_logic.dart @@ -4,15 +4,15 @@ import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/main/lockDetail/monitoring/star_chart_h264/star_chart_state.dart'; import 'package:star_lock/talk/startChart/events/talk_status_change_event.dart'; import 'package:star_lock/talk/startChart/start_chart_manage.dart'; import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; -import 'lockMonitoring_state.dart'; class StarChartLogic extends BaseGetXController { - final LockMonitoringState state = LockMonitoringState(); + final StarChartState state = StarChartState(); /// 收到Talk发送的状态 StreamSubscription? _getTalkStatusRefreshUIEvent; @@ -58,26 +58,10 @@ class StarChartLogic extends BaseGetXController { void _cancelTimers() { state.oneMinuteTimeTimer.cancel(); - state.answerTimer.cancel(); } // 发起接听命令 void initiateAnswerCommand() { - // state.answerTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - // StartChartManage().sendTalkAcceptMessage(); - // state.answerSeconds++; - - // if (state.talkStatus.value == TalkStatus.duringCall.index) { - // state.answerTimer.cancel(); - // return; - // } - - // if (state.answerSeconds >= 6) { - // state.answerTimer.cancel(); - // showToast('接听失败'.tr); - // initiateHangUpCommand(); - // } - // }); StartChartManage().sendTalkAcceptMessage(); } diff --git a/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_page.dart b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_page.dart new file mode 100644 index 00000000..927c4e51 --- /dev/null +++ b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_page.dart @@ -0,0 +1,397 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:star_lock/app_settings/app_colors.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/main/lockDetail/monitoring/star_chart_h264/star_chart_logic.dart'; +import 'package:star_lock/main/lockDetail/monitoring/star_chart_h264/star_chart_state.dart'; +import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; +import 'package:star_lock/talk/startChart/webView/h264_web_view.dart'; +import 'package:star_lock/talk/udp/udp_manage.dart'; +import 'package:star_lock/tools/eventBusEventManage.dart'; +import 'package:star_lock/tools/showTFView.dart'; + +class StarChartPage extends StatefulWidget { + const StarChartPage({Key? key}) : super(key: key); + + @override + State createState() => _StarChartPageState(); +} + +class _StarChartPageState extends State { + final StarChartLogic logic = Get.put(StarChartLogic()); + final StarChartState state = Get.find().state; + + @override + void initState() { + super.initState(); + + initAsync(); + _getTVDataRefreshUIAction(); + } + + Future initAsync() async { + await requestMicrophonePermission(); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: Container( + width: 1.sw, + height: 1.sh, + color: Colors.transparent, + child: _buildTalkView(isMpeg4: false), + ), + ), + ); + } + + Widget buildTopButtons() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildIconButton( + icon: state.isOpenVoice.value + ? 'images/main/icon_lockDetail_monitoringCloseVoice.png' + : 'images/main/icon_lockDetail_monitoringOpenVoice.png', + onTap: () { + state.isOpenVoice.value = !state.isOpenVoice.value; + }, + ), + SizedBox(width: 60.w), + buildIconButton( + icon: 'images/main/icon_lockDetail_monitoringScreenshot.png', + onTap: captureAndSavePng, + ), + SizedBox(width: 60.w), + buildIconButton( + icon: 'images/main/icon_lockDetail_monitoringScreenRecording.png', + onTap: () { + // Get.toNamed(Routers.monitoringRealTimeScreenPage); + }, + ), + ], + ); + } + + Widget buildBottomButtons() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + buildAnswerButton(), + buildIconButton( + icon: 'images/main/icon_lockDetail_hangUp.png', + label: '挂断'.tr, + color: Colors.red, + onTap: () async { + if (!state.isClickHangUp.value) { + logic.initiateHangUpCommand(); + state.isClickHangUp.value = true; + } + }, + ), + buildIconButton( + icon: 'images/main/icon_lockDetail_monitoringUnlock.png', + label: '开锁'.tr, + color: AppColors.mainColor, + onTap: () { + if (UDPManage().remoteUnlock == 1) { + showDeletPasswordAlertDialog(context); + } else { + logic.showToast('请在锁设置中开启远程开锁'.tr); + } + }, + ), + ], + ); + } + + Widget buildAnswerButton() { + return Obx(() { + final bool isDuringCall = + state.talkStatus.value == TalkStatus.duringCall.index; + return buildIconButton( + icon: isDuringCall + ? 'images/main/icon_lockDetail_monitoringUnTalkback.png' + : 'images/main/icon_lockDetail_monitoringAnswerCalls.png', + label: isDuringCall ? '长按说话'.tr : '接听'.tr, + onTap: () async { + if (!state.isClickAnswer.value) { + logic.initiateAnswerCommand(); + state.isClickAnswer.value = true; + } + }, + onLongPress: () { + state.listAudioData.value = []; + logic.startProcessing(); + }, + onLongPressUp: () { + logic.stopProcessing(); + }, + ); + }); + } + + Widget buildIconButton({ + required String icon, + String? label, + Color color = Colors.white, + required Function() onTap, + Function()? onLongPress, + Function()? onLongPressUp, + }) { + final double wh = 80.w; + return GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + onLongPressUp: onLongPressUp, + child: SizedBox( + height: 140.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: wh, + height: wh, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2), + ), + padding: EdgeInsets.all(20.w), + child: Image.asset(icon, fit: BoxFit.fitWidth), + ), + if (label != null) ...[ + SizedBox(height: 20.w), + Expanded( + child: Text( + label, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + textAlign: TextAlign.center, + ), + ), + ], + ], + ), + ), + ); + } + + void showDeletPasswordAlertDialog(BuildContext context) { + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return ShowTFView( + title: '请输入6位数字开锁密码'.tr, + tipTitle: '', + controller: state.passwordTF, + inputFormatters: [ + LengthLimitingTextInputFormatter(6), //限制长度 + FilteringTextInputFormatter.allow(RegExp('[0-9]')), + ], + sureClick: () async { + if (state.passwordTF.text.isEmpty) { + logic.showToast('请输入开锁密码'.tr); + return; + } + final List numbers = []; + final List lockIDData = utf8.encode(state.passwordTF.text); + numbers.addAll(lockIDData); + for (int i = 0; i < 6 - lockIDData.length; i++) { + numbers.add(0); + } + //todo: 开门暂未实现 + // logic.udpOpenDoorAction(numbers); + }, + cancelClick: () { + Get.back(); + }, + ); + }, + ); + } + + Future requestMicrophonePermission() async { + await logic.getPermissionStatus().then((bool value) async { + if (!value) { + return; + } + }); + } + + 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'); + logic.showToast('截图已保存到相册'.tr); + } catch (e) { + AppLog.log('截图失败: $e'); + } + } + + void _getTVDataRefreshUIAction() { + state.getTVDataRefreshUIEvent = eventBus + .on() + .listen((GetTVDataRefreshUI event) async { + if (event.tvList.isNotEmpty && event.tvList.length > 100) { + final Uint8List imageData = Uint8List.fromList(event.tvList); + if (!listEquals(state.listPhotoData.value, imageData)) { + state.listPhotoData.value = imageData; + state.shouldUpdateUI.value = true; + if (state.shouldUpdateUI.value) { + setState(() {}); + state.shouldUpdateUI.value = false; + } + } + } + }); + } + + @override + void dispose() { + super.dispose(); + logic.stopProcessing(); + state.getTVDataRefreshUIEvent!.cancel(); + } + + Widget _buildTalkView({required bool isMpeg4}) { + return isMpeg4 ? _buildMpeg4TalkView() : _buildH264TalkView(); + } + + Widget _buildMpeg4TalkView() { + return Stack( + children: [ + Image.memory( + state.listPhotoData.value, + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + filterQuality: FilterQuality.high, + errorBuilder: + (BuildContext context, Object error, StackTrace? stackTrace) { + return Container(color: Colors.transparent); + }, + ), + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + final String sec = + (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); + final String min = + (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('$min:$sec', + style: TextStyle(fontSize: 26.sp, color: Colors.white)), + ], + ); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h), + ), + child: Column( + children: [ + SizedBox(height: 20.h), + buildTopButtons(), + SizedBox(height: 20.h), + buildBottomButtons(), + SizedBox(height: 20.h), + ], + ), + ), + ), + ], + ); + } + + Widget _buildH264TalkView() { + return Stack( + children: [ + H264WebView(), + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + final String sec = + (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); + final String min = + (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('$min:$sec', + style: TextStyle(fontSize: 26.sp, color: Colors.white)), + ], + ); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h), + ), + child: Column( + children: [ + SizedBox(height: 20.h), + buildTopButtons(), + SizedBox(height: 20.h), + buildBottomButtons(), + SizedBox(height: 20.h), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_state.dart b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_state.dart new file mode 100644 index 00000000..4a8ddfc4 --- /dev/null +++ b/lib/main/lockDetail/monitoring/star_chart_h264/star_chart_state.dart @@ -0,0 +1,57 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:get/get.dart'; +import 'package:network_info_plus/network_info_plus.dart'; +import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; + +import '../../../../tools/storage.dart'; + +class StarChartState { + RxBool isOpenVoice = false.obs; + int udpSendDataFrameNumber = 0; // 帧序号 + // var isSenderAudioData = false.obs;// 是否要发送音频数据 + StreamSubscription? getTVDataRefreshUIEvent; //收到视频流数据 + RxBool shouldUpdateUI = false.obs; //是否需要更新UI + + 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 listPhotoData = Uint8List(0).obs; //得到的视频流字节数据 + RxList listAudioData = [].obs; //得到的音频流字节数据 + +//录音相关 + late VoiceProcessor? voiceProcessor; + RxBool isProcessing = false.obs; //是否正在处理音频数据 + RxBool isButtonDisabled = false.obs; //是否禁用按钮 + final int frameLength = 320; //音视频帧长度为320 + final int sampleRate = 8000; //音频采样率为8000 + RxString errorMessage = ''.obs; + List> allFrames = >[]; + + GlobalKey globalKey = GlobalKey(); + + late Timer oneMinuteTimeTimer = + Timer(const Duration(seconds: 1), () {}); // 定时器超过60秒关闭当前界面 + RxInt oneMinuteTime = 0.obs; // 定时器秒数 + + // 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次 + late Timer answerTimer = Timer(const Duration(seconds: 1), () {}); //接听命令定时器 + RxInt answerSeconds = 0.obs; + RxBool isClickAnswer = false.obs; //是否点击了接听按钮 + + late Timer hangUpTimer = Timer(const Duration(seconds: 1), () {}); //挂断命令定时器 + RxInt hangUpSeconds = 0.obs; + RxBool isClickHangUp = false.obs; //是否点击了挂断按钮 + + late Timer openDoorTimer = Timer(const Duration(seconds: 1), () {}); //开门命令定时器 + RxInt openDoorSeconds = 0.obs; + + RxInt talkStatus = 0.obs; //星图对讲状态 +}