diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart index 3e767996..0956e9ed 100755 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -23,6 +23,8 @@ class LockMonitoringLogic extends BaseGetXController { state.voiceProcessor = VoiceProcessor.instance; } +/* +旧协议暂时不用 /// 收到UDP发送的状态 StreamSubscription? _getUDPStatusRefreshUIEvent; @@ -55,6 +57,7 @@ class LockMonitoringLogic extends BaseGetXController { } }); } + */ //发起接听命令,每隔一秒钟发一次,六秒无应答则失败 void initiateUdpAnswerAction() { @@ -413,7 +416,7 @@ class LockMonitoringLogic extends BaseGetXController { void onReady() { super.onReady(); - _getUDPStatusRefreshUIAction(); + // _getUDPStatusRefreshUIAction(); initRecorder(); } @@ -427,7 +430,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 index 4ddd8ba6..a371d31c 100755 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -1,5 +1,5 @@ - import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:ui' as ui; @@ -11,16 +11,17 @@ 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/main/lockDetail/monitoring/monitoring/lockMonitoring_state.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/talk/call/callTalk.dart'; +import 'package:star_lock/talk/startChart/start_chart_talk_status.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); @@ -30,12 +31,13 @@ class LockMonitoringPage extends StatefulWidget { } class _LockMonitoringPageState extends State { - final LockMonitoringLogic logic = Get.put(LockMonitoringLogic()); + late final StarChartLogic logic; final LockMonitoringState state = Get.find().state; @override void initState() { super.initState(); + logic = Get.put(StarChartLogic()); initAsync(); _getTVDataRefreshUIAction(); } @@ -47,234 +49,210 @@ class _LockMonitoringPageState extends State { @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: 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); - }, + 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, + 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), + ], + ), ), - 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: 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'))), + 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), - // 截图 - 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), + buildIconButton( + icon: 'images/main/icon_lockDetail_monitoringScreenshot.png', + onTap: captureAndSavePng, ), - ), - 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')), + SizedBox(width: 60.w), + buildIconButton( + icon: 'images/main/icon_lockDetail_monitoringScreenRecording.png', + onTap: () { + // Get.toNamed(Routers.monitoringRealTimeScreenPage); + }, ), - ), - ]); + ], + ); } - Widget bottomBottomBtnWidget() { - return Row(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; - } + 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(); - })), - 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; - } - // 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); - } - }) - ]); + CallTalk().finishAVData(); + if (!state.isClickHangUp.value) { + // logic.initiateUdpHangUpAction(3); + 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); + } + }, + ), + ], + ); } - 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 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 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}) { + 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: onClick, - onLongPress: longPress, - onLongPressUp: longPressUp, + 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: backgroundColor, - borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)), - padding: EdgeInsets.all(20.w), - child: Image.asset(iconUrl, fit: BoxFit.fitWidth), + 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(name, - style: TextStyle(fontSize: 20.sp, color: Colors.white), - textAlign: TextAlign.center)) + child: Text( + label, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + textAlign: TextAlign.center, + ), + ), ], - )), + ], + ), + ), ); } @@ -292,23 +270,18 @@ 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); } - logic.udpOpenDoorAction(numbers); + //todo: 开门暂未实现 + // logic.udpOpenDoorAction(numbers); }, cancelClick: () { Get.back(); @@ -318,16 +291,11 @@ 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("发送接听了"); - // 刚进来是接听状态,然后改为长按对讲 }); } @@ -348,15 +316,12 @@ 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'); @@ -366,30 +331,19 @@ class _LockMonitoringPageState extends State { } } - /// 收到视频流数据 void _getTVDataRefreshUIAction() { - // 蓝牙协议通知传输跟蓝牙之外的数据传输类不一样 eventBus - state.getTVDataRefreshUIEvent = - eventBus.on().listen((GetTVDataRefreshUI event) async { + 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(() { - // 更新UI - }); - // 更新完UI后将标志重新设置为false + setState(() {}); state.shouldUpdateUI.value = false; } - // }); } } }); diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart index c0f07e83..d26067bb 100755 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart @@ -5,6 +5,7 @@ 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'; @@ -18,8 +19,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; //得到的视频流字节数据 @@ -31,10 +32,6 @@ 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 = >[]; @@ -55,4 +52,6 @@ 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/monitoring/star_chart_logic.dart new file mode 100644 index 00000000..b77298ec --- /dev/null +++ b/lib/main/lockDetail/monitoring/monitoring/star_chart_logic.dart @@ -0,0 +1,206 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +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/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(); + + /// 收到Talk发送的状态 + StreamSubscription? _getTalkStatusRefreshUIEvent; + + @override + void onReady() { + super.onReady(); + _getTalkStatusRefreshUIAction(); + } + + void _getTalkStatusRefreshUIAction() { + _getTalkStatusRefreshUIEvent = eventBus + .on() + .listen((TalkStatusChangeEvent event) async { + state.talkStatus.value = event.newStatus.index; + state.oneMinuteTime.value = 0; + + if (state.talkStatus.value == TalkStatus.rejected.index) { + _cancelTimers(); + return; + } + + if (state.talkStatus.value == TalkStatus.duringCall.index) { + _startCallTimer(); + } + }); + } + + void _startCallTimer() { + state.oneMinuteTimeTimer.cancel(); + state.oneMinuteTimeTimer = + Timer.periodic(const Duration(seconds: 1), (Timer t) { + state.oneMinuteTime.value++; + if (state.oneMinuteTime.value >= 60) { + t.cancel(); + initiateHangUpCommand(); + AppLog.log('通话时间超过60秒,自动挂断'); + state.oneMinuteTime.value = 0; + } + }); + } + + 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(); + } + }); + } + + // 发起挂断命令 + void initiateHangUpCommand() { + _cancelTimers(); + StartChartManage().sendTalkRejectMessage(); + } + + Future _onFrame(List frame) async { + state.allFrames.add(frame); + final List concatenatedFrames = concatenateFrames(state.allFrames); + AppLog.log('pcm数据:$concatenatedFrames'); + + final List pcmBytes = listLinearToULaw(frame); + //发起发送录音命令 + // StartChartManage().sendTalkAudioMessage(pcmBytes); + } + + List listLinearToULaw(List pcmList) { + return pcmList.map(linearToULaw).toList(); + } + + List concatenateFrames(List> frames) { + return frames.expand((frame) => frame).toList(); + } + + Future startProcessing() async { + state.isButtonDisabled.value = true; + + state.voiceProcessor?.addFrameListener(_onFrame); + state.voiceProcessor?.addErrorListener(_onError); + try { + if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { + await state.voiceProcessor?.start(state.frameLength, state.sampleRate); + state.isProcessing.value = + await state.voiceProcessor?.isRecording() ?? false; + } else { + state.errorMessage.value = 'Recording permission not granted'; + } + } on PlatformException catch (ex) { + state.errorMessage.value = 'Failed to start recorder: $ex'; + } finally { + state.isButtonDisabled.value = false; + } + } + + void _onError(VoiceProcessorException error) { + state.errorMessage.value = error.message!; + } + + Future stopProcessing() async { + state.isButtonDisabled.value = true; + try { + await state.voiceProcessor?.stop(); + state.voiceProcessor?.removeFrameListener(_onFrame); + state.udpSendDataFrameNumber = 0; + } on PlatformException catch (ex) { + state.errorMessage.value = 'Failed to stop recorder: $ex'; + } finally { + state.isProcessing.value = + await state.voiceProcessor?.isRecording() ?? false; + state.isButtonDisabled.value = false; + } + } + + int linearToULaw(int pcmVal) { + int mask; + int seg; + int uval; + + if (pcmVal < 0) { + pcmVal = 0x84 - pcmVal; + mask = 0x7F; + } else { + pcmVal += 0x84; + mask = 0xFF; + } + + seg = search(pcmVal); + if (seg >= 8) { + return 0x7F ^ mask; + } else { + uval = seg << 4; + uval |= (pcmVal >> (seg + 3)) & 0xF; + return uval ^ mask; + } + } + + int search(int val) { + final List table = [ + 0xFF, + 0x1FF, + 0x3FF, + 0x7FF, + 0xFFF, + 0x1FFF, + 0x3FFF, + 0x7FFF + ]; + for (int i = 0; i < table.length; i++) { + if (val <= table[i]) { + return i; + } + } + return table.length; + } + + Future getPermissionStatus() async { + const Permission permission = Permission.microphone; + final PermissionStatus status = await permission.status; + if (status.isGranted) { + return true; + } else if (status.isDenied || status.isRestricted) { + await requestPermission(permission); + } else if (status.isPermanentlyDenied) { + openAppSettings(); + } + return false; + } + + Future requestPermission(Permission permission) async { + final PermissionStatus status = await permission.request(); + if (status.isPermanentlyDenied) { + openAppSettings(); + } + } +}