diff --git a/star_lock/images/main/realTime_connecting.png b/star_lock/images/main/realTime_connecting.png new file mode 100644 index 00000000..62c50bbc Binary files /dev/null and b/star_lock/images/main/realTime_connecting.png differ diff --git a/star_lock/lib/appRouters.dart b/star_lock/lib/appRouters.dart index ecf2e56c..f4b0beb6 100644 --- a/star_lock/lib/appRouters.dart +++ b/star_lock/lib/appRouters.dart @@ -15,6 +15,7 @@ import 'package:star_lock/main/lockDetail/lcokSet/msgNotification/msgNotificatio import 'package:star_lock/main/lockDetail/lcokSet/notificationMode/notificationMode_page.dart'; import 'package:star_lock/main/lockDetail/lcokSet/openDoorDirection/openDoorDirection_page.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_main_page.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart'; import 'package:star_lock/main/lockDetail/passwordKey/passwordKeyDetailChangeDate/passwordKeyDetailChangeDate_page.dart'; import 'package:star_lock/mine/about/webviewShow_page.dart'; import 'package:star_lock/mine/mine/safeVerify/safeVerify_page.dart'; @@ -406,6 +407,7 @@ abstract class Routers { static const addFaceTypeManagePage = '/AddFaceTypeManagePage'; // 添加人脸 static const passwordKeyDetailChangeDatePage = '/passwordKeyDetailChangeDatePage'; //密码更改时间 + static const realTimePicturePage = '/realTimePicturePage'; //实时监控画面 } abstract class AppRouters { @@ -953,12 +955,9 @@ abstract class AppRouters { GetPage( name: Routers.monitoringRealTimeScreenPage, page: () => const MonitoringRealTimeScreenPage()), + GetPage(name: Routers.videoLogPage, page: () => const VideoLogPage()), GetPage( - name: Routers.videoLogPage, - page: () => const VideoLogPage()), - GetPage( - name: Routers.editVideoLogPage, - page: () => const EditVideoLogPage()), + name: Routers.editVideoLogPage, page: () => const EditVideoLogPage()), GetPage( name: Routers.videoLogDetailPage, page: () => const VideoLogDetailPage()), @@ -971,15 +970,11 @@ abstract class AppRouters { GetPage( name: Routers.addRemoteControlManagePage, page: () => const AddRemoteControlManagePage()), - GetPage( - name: Routers.cardListPage, - page: () => const CardListPage()), + GetPage(name: Routers.cardListPage, page: () => const CardListPage()), GetPage( name: Routers.addCardTypeManagePage, page: () => const AddCardTypeManagePage()), - GetPage( - name: Routers.cardDetailPage, - page: () => const CardDetailPage()), + GetPage(name: Routers.cardDetailPage, page: () => const CardDetailPage()), GetPage( name: Routers.fingerprintListPage, page: () => const FingerprintListPage()), @@ -995,6 +990,9 @@ abstract class AppRouters { page: () => const AddFaceTypeManagePage()), GetPage( name: Routers.passwordKeyDetailChangeDatePage, - page: () => const PasswordKeyDetailChangeDatePage()) + page: () => const PasswordKeyDetailChangeDatePage()), + GetPage( + name: Routers.realTimePicturePage, + page: () => const RealTimePicturePage()) ]; } diff --git a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart index 988bceab..fa3de7f1 100644 --- a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -373,9 +373,11 @@ class _LockDetailPageState extends State with TickerProviderStat //可视对讲门锁新增->监控 if (state.keyInfos.value.lockFeature!.videoIntercom == 1) { showWidgetArr.add( - bottomItem('images/main/icon_catEyes.png', TranslationLoader.lanKeys!.monitoring!.tr, () { - Get.toNamed(Routers.lockMonitoringPage, arguments: { - "lockId": widget.lockListInfoItemEntity.lockId + bottomItem('images/main/icon_catEyes.png', + TranslationLoader.lanKeys!.monitoring!.tr, () { + Get.toNamed(Routers.realTimePicturePage, arguments: { + "lockId": widget.lockListInfoItemEntity.lockId, + "isMonitoring": true }); }), ); diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart new file mode 100644 index 00000000..013d18d3 --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart @@ -0,0 +1,368 @@ +import 'dart:async'; +import 'dart:math'; + +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/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart'; + +import '../../../../talk/call/g711.dart'; +import '../../../../talk/udp/udp_manage.dart'; +import '../../../../talk/udp/udp_senderManage.dart'; +import '../../../../tools/baseGetXController.dart'; +import '../../../../tools/eventBusEventManage.dart'; + +class RealTimePictureLogic extends BaseGetXController { + final RealTimePictureState state = RealTimePictureState(); + + /// 初始化发送声音 + initRecorder() { + state.voiceProcessor = VoiceProcessor.instance; + } + + /// 收到视频流数据 + StreamSubscription? _getTVDataRefreshUIEvent; + void _getTVDataRefreshUIAction() { + // 蓝牙协议通知传输跟蓝牙之外的数据传输类不一样 eventBus + _getTVDataRefreshUIEvent = + eventBus.on().listen((event) { + if (event.tvList.isNotEmpty) { + // 预加载图片数据 + Uint8List imageData = Uint8List.fromList(event.tvList); + // 更新状态 + state.listData.value = imageData; + } + }); + } + + /// 接听 + udpAnswerAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 6, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + } + + /// 挂断 + udpHangUpAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 30, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + } + + /// 开门 + udpOpenDoorAction() async { + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 10, + lockID: UDPManage().lockId, + lockIP: UDPManage().host, + userMobile: await state.userMobile, + userMobileIP: await state.userMobileIP, + endData: []); + 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}'); + + 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.userMobile, + "userMobileIP": await state.userMobileIP, + }); + print(sublist); + start += length; + } + print('G711数据发送完成'); + } else { + print('Failed to read audio data.'); + } + } + + Future startProcessing() async { + frameListener(List frame) async { + // Get.log('Get data.length:${frame.length} Received data:$frame'); + for (int i = 0; i < frame.length; i++) { + frame[i] = linearToULaw(frame[i]); + } + // Get.log('change Get data.length:${frame.length} change Received data:$frame'); + await Future.delayed(const Duration(milliseconds: 50)); + sendRecordData({ + "bytes": frame, + // "udpSendDataFrameNumber": 0, + "lockID": UDPManage().lockId, + "lockIP": UDPManage().host, + "userMobile": await state.userMobile, + "userMobileIP": await state.userMobileIP, + }); + } + + errorListener(VoiceProcessorException error) { + print("VoiceProcessorException: $error"); + } + + ; + state.voiceProcessor?.addFrameListener(frameListener); + state.voiceProcessor?.addErrorListener(errorListener); + + 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 {} + } + + Future stopProcessing() async { + try { + await state.voiceProcessor?.stop(); + } on PlatformException catch (ex) { + Get.log("PlatformException: $ex"); + } finally {} + } + + void onError(Object e) { + print(e); + } + + sendRecordData(Map args) async { + List bytes = args["bytes"]; + // int udpSendDataFrameNumber = args["udpSendDataFrameNumber"]; + String? lockID = args["lockID"]; + String? lockIP = args["lockIP"]; + String? userMobile = args["userMobile"]; + String? userMobileIP = args["userMobileIP"]; + + // int length = 320; // 每个子List的长度 + // List list = state.listAudioData.value.sublist(0, 320); + // for (int i = 0; i < bytes.length; i += length) { + // int end = (i + length < bytes.length) ? i + length : bytes.length; + // bytes.sublist(i, end); + // // _sendRecordData(bytes.sublist(i, end)); + // // // 刚进来是接听状态,然后改为长按对讲 + // } + + // while(list.isNotEmpty) { + state.udpSendDataFrameNumber++; + if (state.udpSendDataFrameNumber >= 65536) state.udpSendDataFrameNumber = 1; + // 57 + List topBytes = []; + + // var cID = "XXXCID"; + // List cIDData = utf8.encode(cID!); + // topBytes.addAll(cIDData); + // // topBytes = getFixedLengthList(cIDData, 20 - cIDData.length); + // for (int i = 0; i < 6 - cIDData.length; i++) { + // topBytes.add(0); + // } + // + // // 命令 + // topBytes.add(150); + // + // // 命令类型 + // topBytes.add(1); + // + // // 子命令 + // topBytes.add(8); + // + // // lockID + // List lockIDData = utf8.encode(lockID!); + // topBytes.addAll(lockIDData); + // // topBytes = getFixedLengthList(lockIDData, 20 - lockIDData.length); + // for (int i = 0; i < 20 - lockIDData.length; i++) { + // topBytes.add(0); + // } + // + // // lockIP + // var lockIPList = lockIP!.split("."); + // lockIPList.forEach((element) { + // topBytes.add(int.parse(element)); + // }); + // + // // userMobile + // List userMobileData = utf8.encode(userMobile!); + // topBytes.addAll(userMobileData); + // // topBytes = getFixedLengthList(topBytes, 20 - userMobileData.length); + // for (int i = 0; i < 20 - userMobileData.length; i++) { + // topBytes.add(0); + // } + // + // // userMobileIP + // var userMobileIPList = userMobileIP!.split("."); + // userMobileIPList.forEach((element) { + // topBytes.add(int.parse(element)); + // }); + + topBytes.addAll([ + 1, 1, 1, 1, // 时间戳 + 1, 0, // 音频 + 1, 0, // 帧序号 + 64, 0, 0, 0, // 帧长度 + 1, 0, // 总包数 + 1, 0, // 当前包号 + 64, 1, // 数据长度 + 176, 4, // 保留 + ]); + + topBytes[6] = (state.udpSendDataFrameNumber & 0x000000FF); + topBytes[7] = ((state.udpSendDataFrameNumber & 0x0000FF00) >> 8); + + print( + "udpSendDataFrameNumber:${state.udpSendDataFrameNumber} topBytes[63]:${topBytes[6]} topBytes[64]:${topBytes[7]}"); + topBytes.addAll(bytes); + Get.log("setVoiceBytes:$topBytes"); + + UDPSenderManage.sendMainProtocol( + command: 150, + commandTypeIsCalling: 1, + subCommand: 8, + lockID: lockID, + lockIP: lockIP, + userMobile: userMobile, + userMobileIP: userMobileIP, + endData: topBytes); + + // UDPManage().sendData(topBytes); + } + + // 拿到的音频转化成pcm + 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) { + List table = [ + 0xFF, + 0x1FF, + 0x3FF, + 0x7FF, + 0xFFF, + 0x1FFF, + 0x3FFF, + 0x7FFF + ]; + int size = 8; + for (int i = 0; i < size; i++) { + if (val <= table[i]) { + return i; + } + } + 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 拒绝且不在提示 + 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 { + PermissionStatus status = await permission.request(); + if (status.isPermanentlyDenied) { + openAppSettings(); + } + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + print("onReady()"); + + _getTVDataRefreshUIAction(); + + initRecorder(); + } + + @override + void onInit() { + // TODO: implement onInit + super.onInit(); + } + + @override + void onClose() { + // TODO: implement onClose + print("锁详情界面销毁了"); + _getTVDataRefreshUIEvent!.cancel(); + stopProcessing(); + } +} diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart new file mode 100644 index 00000000..fa067dab --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_page.dart @@ -0,0 +1,301 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:star_lock/main/lockDetail/lockDetail/realTimePicture/realTimePicture_logic.dart'; + +import '../../../../app_settings/app_colors.dart'; +import '../../../../tools/showTFView.dart'; +import '../../../../tools/toast.dart'; + +class RealTimePicturePage extends StatefulWidget { + const RealTimePicturePage({Key? key}) : super(key: key); + + @override + State createState() => _RealTimePicturePageState(); +} + +class _RealTimePicturePageState extends State + with TickerProviderStateMixin { + final logic = Get.put(RealTimePictureLogic()); + final state = Get.find().state; + + @override + void initState() { + super.initState(); + + // listeningAnimations(); + state.animationController = + AnimationController(duration: const Duration(seconds: 2), vsync: this); + state.animationController.repeat(); + //动画开始、结束、向前移动或向后移动时会调用StatusListener + state.animationController.addStatusListener((status) { + // print("AnimationStatus:$status"); + if (status == AnimationStatus.completed) { + state.animationController.reset(); + state.animationController.forward(); + } else if (status == AnimationStatus.dismissed) { + state.animationController.reset(); + state.animationController.forward(); + } + }); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 1.sw, + height: 1.sh, + child: Stack( + children: [ + Obx(() => state.listData.value.isEmpty + ? Container(color: Colors.black) + : Image.memory( + state.listData.value, + // key: ValueKey(state.listData.value.hashCode), + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + )), + 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), + ], + ), + )), + buildRotationTransition() + ], + ), + ); + } + + 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"))), + ), + ), + 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_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 bottomBottomBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // 接听 + Obx(() => bottomBtnItemWidget( + getAnswerBtnImg(), getAnswerBtnName(), Colors.white, () async { + //获取麦克风权限 + await logic.getPermissionStatus().then((value) async { + if (!value) { + return; + } + + // state.isSenderAudioData.value = false; + print("发送接听了"); + // 刚进来是接听状态,然后改为长按对讲 + logic.udpAnswerAction(); + }); + }, longPress: () { + // 开始长按 + print("onLongPress"); + state.listAudioData.value = []; + if (state.udpStatus.value == 8) { + state.udpStatus.value = 9; + } + // logic.readG711Data(); + logic.startProcessing(); + }, longPressUp: () async { + // 长按结束 + print("onLongPressUp"); + if (state.udpStatus.value == 9) { + state.udpStatus.value = 8; + } + })), + bottomBtnItemWidget( + "images/main/icon_lockDetail_hangUp.png", "挂断", Colors.red, () async { + logic.stopProcessing(); + + // 挂断 + logic.udpHangUpAction(); + }), + bottomBtnItemWidget("images/main/icon_lockDetail_monitoringUnlock.png", + "开锁", AppColors.mainColor, () { + showDeletPasswordAlertDialog(context); + }) + ]); + } + + 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"; + } + } + + String getAnswerBtnName() { + switch (state.udpStatus.value) { + case 8: + return "长按说话"; + case 9: + return "松开发送"; + default: + return "接听"; + } + } + + Widget bottomBtnItemWidget( + String iconUrl, String name, Color backgroundColor, Function() onClick, + {Function()? longPress, Function()? longPressUp}) { + var wh = 80.w; + return GestureDetector( + 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: backgroundColor, + borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)), + padding: EdgeInsets.all(20.w), + child: Image.asset(iconUrl, fit: BoxFit.fitWidth), + ), + SizedBox(height: 20.w), + Expanded( + child: Text(name, + 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: "请输入六位数字开锁密码", + tipTitle: "", + controller: state.passwordTF, + inputFormatters: [ + LengthLimitingTextInputFormatter(6), //限制长度 + FilteringTextInputFormatter.allow(RegExp("[0-9]")), + ], + sureClick: () async { + //发送删除锁请求 + if (state.passwordTF.text.isEmpty) { + Toast.show(msg: "请输入开锁密码"); + return; + } + + // 开锁 + logic.udpOpenDoorAction(); + }, + cancelClick: () { + Get.back(); + }, + ); + }, + ); + } + + //旋转动画 + Widget buildRotationTransition() { + return Positioned( + left: ScreenUtil().screenWidth / 2 - 220.w / 2, + top: ScreenUtil().screenHeight / 2 - 220.w / 2 - 150.h, + child: RotationTransition( + //设置动画的旋转中心 + alignment: Alignment.center, + //动画控制器 + turns: state.animationController, + //将要执行动画的子view + child: Image.asset( + 'images/main/realTime_connecting.png', + width: 220.w, + height: 220.w, + ), + ), + ); + } + + @override + void dispose() { + state.animationController.dispose(); + + super.dispose(); + } +} diff --git a/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart new file mode 100644 index 00000000..fe01bd43 --- /dev/null +++ b/star_lock/lib/main/lockDetail/lockDetail/realTimePicture/realTimePicture_state.dart @@ -0,0 +1,35 @@ +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 '../../../../tools/storage.dart'; + +class RealTimePictureState { + var isOpenVoice = false.obs; + var udpSendDataFrameNumber = 0; // 帧序号 + // var isSenderAudioData = false.obs;// 是否要发送音频数据 + + var userMobileIP = NetworkInfo().getWifiIP(); + var userMobile = Storage.getMobile(); + + var udpStatus = + 0.obs; //0:初始状态 1:等待监视 2: 3:监视中 4:呼叫成功 5:主角通话中 6:被叫通话 8:被叫通话中 9:长按说话 + var passwordTF = TextEditingController(); + + var listData = Uint8List(0).obs; //得到的视频流字节数据 + var listAudioData = [].obs; //得到的音频流字节数据 + + late final VoiceProcessor? voiceProcessor; + + var oneMinuteTime = 0.obs; // 定时器秒数 + + // 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次 + late Timer answerTimer; + late Timer hangUpTimer; + late Timer openDoorTimer; + late AnimationController animationController; +}