From 266f68e5a60ce8142df991d0404cc0cc628681e5 Mon Sep 17 00:00:00 2001 From: Daisy <> Date: Tue, 2 Apr 2024 14:07:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A2=B3=E7=90=86=E5=AF=B9=E8=AE=B2=E5=BD=95?= =?UTF-8?q?=E9=9F=B3=E5=8A=9F=E8=83=BD=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=20=E8=A7=A3=E5=86=B3=E5=BD=95?= =?UTF-8?q?=E9=9F=B3=E5=8F=AA=E6=9C=89=E7=AC=AC=E4=B8=80=E6=AC=A1=E7=94=9F?= =?UTF-8?q?=E6=95=88=EF=BC=8C=E5=90=8E=E9=9D=A2=E4=B8=8D=E8=83=BD=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E5=8F=91=E9=80=81=E5=BD=95=E9=9F=B3=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20=E6=96=B0=E5=A2=9E=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=9A=84=E6=88=AA=E5=B1=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitoring/lockMonitoring_logic.dart | 89 +++++--- .../monitoring/lockMonitoring_page.dart | 205 +++++++++++------- .../monitoring/lockMonitoring_state.dart | 14 +- 3 files changed, 197 insertions(+), 111 deletions(-) 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 2155bd81..58d57554 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -163,48 +163,77 @@ class LockMonitoringLogic extends BaseGetXController { Get.back(); } +//开始录音 Future startProcessing() async { - state.sendEnd.value = false; - frameListener(List frame) async { - List pcmBytes = listLinearToULaw(frame); - await Future.delayed(const Duration(milliseconds: 10)); - sendRecordData({ - "bytes": pcmBytes, - // "udpSendDataFrameNumber": 0, - "lockID": UDPManage().lockId, - "lockIP": UDPManage().host, - "userMobile": await state.userUid, - "userMobileIP": await state.userMobileIP, - }); - } - - errorListener(VoiceProcessorException error) { - print("VoiceProcessorException: $error"); - } - - state.voiceProcessor?.addFrameListener(frameListener); - state.voiceProcessor?.addErrorListener(errorListener); + state.isButtonDisabled.value = true; + state.voiceProcessor?.addFrameListener(_onFrame); + state.voiceProcessor?.addErrorListener(_onError); try { if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { - await state.voiceProcessor?.start(320, 8000); + await state.voiceProcessor?.start(state.frameLength, state.sampleRate); bool? isRecording = await state.voiceProcessor?.isRecording(); - } else {} + state.isProcessing.value = isRecording!; + } else { + state.errorMessage.value = "Recording permission not granted"; + } } on PlatformException catch (ex) { - Get.log("PlatformException: $ex"); - } finally {} + state.errorMessage.value = "Failed to start recorder: $ex"; + } finally { + state.isButtonDisabled.value = false; + } + } + + 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 + 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.smoothedVolumeValue.value = + state.volumeHistory.value.reduce((a, b) => a + b) / + state.volumeHistory.value.length; + + 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 { 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 f92f460d..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,6 +349,41 @@ 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(); 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 b1f820e2..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,9 +23,19 @@ class LockMonitoringState { var listPhotoData = Uint8List(0).obs; //得到的视频流字节数据 var listAudioData = [].obs; //得到的音频流字节数据 +//录音相关 late final VoiceProcessor? voiceProcessor; - var sendEnd = false.obs; //发送完成 - late StreamSubscription> frameSubscription; + 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秒关闭当前界面