diff --git a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart index 166f34d8..ba8d3000 100644 --- a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -164,7 +164,7 @@ class _LockDetailPageState extends State Stack(children: [ Container( width: 1.sw, - height: 1.sh - ScreenUtil().statusBarHeight * 2, + height: 1.sh - ScreenUtil().statusBarHeight, color: Colors.white, child: Column( children: [ diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart index 3085ea5c..58d57554 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -8,7 +8,6 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/talk/call/callTalk.dart'; import 'package:star_lock/talk/udp/udp_talkClass.dart'; -import '../../../../talk/call/g711.dart'; import '../../../../talk/udp/udp_manage.dart'; import '../../../../talk/udp/udp_senderManage.dart'; import '../../../../tools/baseGetXController.dart'; @@ -113,19 +112,6 @@ class LockMonitoringLogic extends BaseGetXController { udpHangUpAction(); state.hangUpSeconds++; - // //如果已经挂断成功,则取消定时器 - // if (UDPTalkClass().isEndCall == true) { - // print('{$clickIndex}已经挂断成功'); - // state.hangUpTimer.cancel(); - // return; - // } - - // if (UDPTalkClass().status == 6) { - // print('{$clickIndex}被叫中,已经挂断成功'); - // state.hangUpTimer.cancel(); - // return; - // } - // 检查条件,如果达到6秒且未得到应答,则认为失败 if (state.hangUpSeconds >= 6) { state.hangUpTimer.cancel(); // 取消定时器 @@ -177,87 +163,77 @@ class LockMonitoringLogic extends BaseGetXController { Get.back(); } - Future _readG711Data() async { - String filePath = 'assets/s10-g711.bin'; - List audioData = await G711().readAssetFile(filePath); - // Get.log('发送读取711文件数据为:$audioData');// 数据为:$audioData - // return; - // print('发送读取711文件数据长度为:${audioData.length}');// 数据为:$audioData - if (audioData.isNotEmpty) { - // 在这里处理你的音频数据 - // pcmBytes = G711().convertList(audioData); - // print('发送转换pcmBytes数据长度为:${pcmBytes.length}'); +//开始录音 + Future startProcessing() async { + state.isButtonDisabled.value = true; - int start = 0; - int length = 320; - while (start < audioData.length) { - // await Future.delayed(const Duration(milliseconds: 50)); - - int end = (start + length > audioData.length) - ? audioData.length - : start + length; - List sublist = audioData.sublist(start, end); - sendRecordData({ - "bytes": sublist, - // "udpSendDataFrameNumber": 0, - "lockID": UDPManage().lockId, - "lockIP": UDPManage().host, - "userMobile": await state.userUid, - "userMobileIP": await state.userMobileIP, - }); - print(sublist); - start += length; + state.voiceProcessor?.addFrameListener(_onFrame); + state.voiceProcessor?.addErrorListener(_onError); + try { + if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { + await state.voiceProcessor?.start(state.frameLength, state.sampleRate); + bool? isRecording = await state.voiceProcessor?.isRecording(); + state.isProcessing.value = isRecording!; + } else { + state.errorMessage.value = "Recording permission not granted"; } - print('G711数据发送完成'); - } else { - print('Failed to read audio data.'); + } on PlatformException catch (ex) { + state.errorMessage.value = "Failed to start recorder: $ex"; + } finally { + state.isButtonDisabled.value = false; } } - Future startProcessing() async { - frameListener(List frame) async { - for (int i = 0; i < frame.length; i++) { - frame[i] = linearToULaw(frame[i]); - } - await Future.delayed(const Duration(milliseconds: 50)); - sendRecordData({ - "bytes": frame, - // "udpSendDataFrameNumber": 0, - "lockID": UDPManage().lockId, - "lockIP": UDPManage().host, - "userMobile": await state.userUid, - "userMobileIP": await state.userMobileIP, - }); + double _calculateVolumeLevel(List frame) { + double rms = 0.0; + for (int sample in frame) { + rms += pow(sample, 2); } + rms = sqrt(rms / frame.length); - errorListener(VoiceProcessorException error) { - print("VoiceProcessorException: $error"); + double dbfs = 20 * log(rms / 32767.0) / log(10); + double normalizedValue = (dbfs + state.dbOffset) / state.dbOffset; + return normalizedValue.clamp(0.0, 1.0); + } + + Future _onFrame(List frame) async { + double volumeLevel = _calculateVolumeLevel(frame); + if (state.volumeHistory.value.length == state.volumeHistoryCapacity) { + state.volumeHistory.value.removeAt(0); } + state.volumeHistory.value.add(volumeLevel); - ; - state.voiceProcessor?.addFrameListener(frameListener); - state.voiceProcessor?.addErrorListener(errorListener); + state.smoothedVolumeValue.value = + state.volumeHistory.value.reduce((a, b) => a + b) / + state.volumeHistory.value.length; - try { - if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { - await state.voiceProcessor?.start(320, 8000); - bool? isRecording = await state.voiceProcessor?.isRecording(); - } else {} - } on PlatformException catch (ex) { - Get.log("PlatformException: $ex"); - } finally {} + List pcmBytes = listLinearToULaw(frame); + await Future.delayed(const Duration(milliseconds: 100)); + sendRecordData({ + "bytes": pcmBytes, + // "udpSendDataFrameNumber": 0, + "lockID": UDPManage().lockId, + "lockIP": UDPManage().host, + "userMobile": await state.userUid, + "userMobileIP": await state.userMobileIP, + }); + } + + void _onError(VoiceProcessorException error) { + state.errorMessage.value = error.message!; } Future stopProcessing() async { + state.isButtonDisabled.value = true; try { await state.voiceProcessor?.stop(); } on PlatformException catch (ex) { - Get.log("PlatformException: $ex"); - } finally {} - } - - void onError(Object e) { - print(e); + state.errorMessage.value = "Failed to stop recorder: $ex"; + } finally { + bool? isRecording = await state.voiceProcessor?.isRecording(); + state.isProcessing.value = isRecording!; + state.isButtonDisabled.value = false; + } } sendRecordData(Map args) async { @@ -360,6 +336,15 @@ class LockMonitoringLogic extends BaseGetXController { // UDPManage().sendData(topBytes); } + List listLinearToULaw(List pcmList) { + List uLawList = []; + for (int pcmVal in pcmList) { + int uLawVal = linearToULaw(pcmVal); + uLawList.add(uLawVal); + } + return uLawList; + } + // 拿到的音频转化成pcm int linearToULaw(int pcmVal) { int mask; @@ -404,18 +389,6 @@ class LockMonitoringLogic extends BaseGetXController { return size; } - double _calculateVolumeLevel(List frame) { - double rms = 0.0; - for (int sample in frame) { - rms += pow(sample, 2); - } - rms = sqrt(rms / frame.length); - - double dbfs = 20 * log(rms / 32767.0) / log(10); - double normalizedValue = (dbfs + 50) / 50; - return normalizedValue.clamp(0.0, 1.0); - } - Future getPermissionStatus() async { Permission permission = Permission.microphone; //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart index c5315cbd..77b6edf8 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -1,9 +1,14 @@ import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; 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/talk/call/callTalk.dart'; import '../../../../app_settings/app_colors.dart'; @@ -36,85 +41,88 @@ class _LockMonitoringPageState extends State { Widget build(BuildContext context) { return PopScope( canPop: false, - child: Container( - width: 1.sw, - height: 1.sh, - color: Colors.white, - child: Stack( - children: [ - Obx(() { - if (state.listPhotoData.value.isEmpty || - state.listPhotoData.value.length < 10) { - return Container(color: Colors.transparent); - } else { - return Image.memory( - state.listPhotoData.value, - gaplessPlayback: true, - width: 1.sw, - height: 1.sh, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Container(color: Colors.transparent); - }, - ); - } - }), - Positioned( - top: ScreenUtil().statusBarHeight + 30.h, - width: 1.sw, - child: Obx(() { - var sec = (state.oneMinuteTime.value % 60) - .toString() - .padLeft(2, '0'); - var 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)), - // SizedBox(width: 30.w), - // GestureDetector( - // onTap: () { - // Get.back(); - // }, - // child: Container( - // decoration: BoxDecoration( - // color: Colors.white, - // borderRadius: BorderRadius.circular(25.h)), - // padding: EdgeInsets.all(10.w), - // child: Image( - // width: 40.w, - // height: 40.w, - // image: const AssetImage("images/icon_left_black.png"), - // ), - // ), - // ), - ]); + child: RepaintBoundary( + key: state.globalKey, + child: Container( + width: 1.sw, + height: 1.sh, + color: Colors.white, + child: Stack( + children: [ + Obx(() { + if (state.listPhotoData.value.isEmpty || + state.listPhotoData.value.length < 10) { + return Container(color: Colors.transparent); + } else { + return Image.memory( + state.listPhotoData.value, + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container(color: Colors.transparent); + }, + ); + } }), - ), - 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), - ], - ), - )) - ], + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + var sec = (state.oneMinuteTime.value % 60) + .toString() + .padLeft(2, '0'); + var 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)), + // SizedBox(width: 30.w), + // GestureDetector( + // onTap: () { + // Get.back(); + // }, + // child: Container( + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(25.h)), + // padding: EdgeInsets.all(10.w), + // child: Image( + // width: 40.w, + // height: 40.w, + // image: const AssetImage("images/icon_left_black.png"), + // ), + // ), + // ), + ]); + }), + ), + 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), + ], + ), + )) + ], + ), ), )); } @@ -144,6 +152,7 @@ class _LockMonitoringPageState extends State { // 截图 GestureDetector( onTap: () { + captureAndSavePng(); // Get.toNamed(Routers.monitoringRealTimeScreenPage); }, child: Container( @@ -198,13 +207,16 @@ class _LockMonitoringPageState extends State { state.udpStatus.value = 9; } // logic.readG711Data(); - logic.startProcessing(); + if (state.isProcessing.value == false) { + logic.startProcessing(); + } }, longPressUp: () async { // 长按结束 print("onLongPressUp"); if (state.udpStatus.value == 9) { state.udpStatus.value = 8; } + logic.stopProcessing(); })), bottomBtnItemWidget( "images/main/icon_lockDetail_hangUp.png", "挂断", Colors.red, () async { @@ -337,8 +349,44 @@ class _LockMonitoringPageState extends State { }); } + Future captureAndSavePng() async { + try { + if (state.globalKey.currentContext == null) { + print('截图失败: 未找到当前上下文'); + return; + } + RenderRepaintBoundary boundary = state.globalKey.currentContext! + .findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(); + ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + print('截图失败: 图像数据为空'); + return; + } + Uint8List pngBytes = byteData.buffer.asUint8List(); + + // 获取应用程序的文档目录 + final directory = await getApplicationDocumentsDirectory(); + final imagePath = '${directory.path}/screenshot.png'; + + // 将截图保存为文件 + File imgFile = File(imagePath); + await imgFile.writeAsBytes(pngBytes); + + // 将截图保存到相册 + await ImageGallerySaver.saveFile(imagePath); + + print('截图保存路径: $imagePath'); + logic.showToast('截图已保存到相册'); + } catch (e) { + print('截图失败: $e'); + } + } + @override void dispose() { super.dispose(); + logic.stopProcessing(); } } diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart index 115baf05..b1d00659 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart @@ -23,7 +23,19 @@ class LockMonitoringState { var listPhotoData = Uint8List(0).obs; //得到的视频流字节数据 var listAudioData = [].obs; //得到的音频流字节数据 +//录音相关 late final VoiceProcessor? voiceProcessor; + var isProcessing = false.obs; //是否正在处理音频数据 + var 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; //存储平滑后的音量值 + var errorMessage = ''.obs; + + GlobalKey globalKey = GlobalKey(); late Timer oneMinuteTimeTimer = Timer(const Duration(seconds: 1), () {}); // 定时器超过60秒关闭当前界面 diff --git a/star_lock/lib/talk/udp/udp_reciverData.dart b/star_lock/lib/talk/udp/udp_reciverData.dart index f2ea0956..2270faa8 100644 --- a/star_lock/lib/talk/udp/udp_reciverData.dart +++ b/star_lock/lib/talk/udp/udp_reciverData.dart @@ -99,6 +99,7 @@ class CommandUDPReciverManager { EasyLoading.showToast("开门成功", duration: 2000.milliseconds); } else { print("开门失败"); + EasyLoading.showToast("开门失败", duration: 2000.milliseconds); } } break;