import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; import 'dart:math'; // Import the math package to use sqrt import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart'; import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/talk/call/g711.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; import 'package:star_lock/tools/G711Tool.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; import '../../../../tools/baseGetXController.dart'; class TalkViewLogic extends BaseGetXController { final TalkViewState state = TalkViewState(); final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; final int minBufferSize = 2; // 最小缓冲2帧,约166ms final int maxBufferSize = 20; // 最大缓冲8帧,约666ms int bufferSize = 8; // 初始化为默认大小 // 修改音频相关的成员变量 final int minAudioBufferSize = 1; // 音频最小缓冲1帧 final int maxAudioBufferSize = 3; // 音频最大缓冲3帧 int audioBufferSize = 2; // 音频默认缓冲2帧 // 添加开始时间记录 int _startTime = 0; // 开始播放时间戳 int _startAudioTime = 0; // 开始播放时间戳 bool _isFirstFrame = true; // 是否是第一帧 bool _isFirstAudioFrame = true; // 是否是第一帧 // 定义音频帧缓冲和发送函数 final List _bufferedAudioFrames = []; final Map _imageCache = {}; // 添加一个变量用于记录上一帧的时间戳 int _lastFrameTimestamp = 0; // 初始值为 0 // 添加帧率计算相关变量 int _frameCount = 0; int _lastFpsUpdateTime = 0; Timer? _fpsTimer; /// 初始化音频播放器 void _initFlutterPcmSound() { const int sampleRate = 8000; FlutterPcmSound.setLogLevel(LogLevel.none); FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1); // 设置 feed 阈值 if (Platform.isAndroid) { FlutterPcmSound.setFeedThreshold(1024); // Android 平台的特殊处理 } else { FlutterPcmSound.setFeedThreshold(2000); // 非 Android 平台的处理 } } /// 挂断 void udpHangUpAction() async { if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { // 如果是通话中就挂断 StartChartManage().startTalkHangupMessageTimer(); } else { // 拒绝 StartChartManage().startTalkRejectMessageTimer(); } Get.back(); } // 发起接听命令 void initiateAnswerCommand() { StartChartManage().startTalkAcceptTimer(); } // 监听音视频数据流 void _startListenTalkData() { state.talkDataRepository.talkDataStream.listen((TalkData talkData) async { final contentType = talkData.contentType; final currentTime = DateTime.now().millisecondsSinceEpoch; // 判断数据类型,进行分发处理 switch (contentType) { case TalkData_ContentTypeE.G711: // // 第一帧到达时记录开始时间 // if (_isFirstAudioFrame) { // _startAudioTime = currentTime; // _isFirstAudioFrame = false; // } // 计算音频延迟 final expectedTime = _startAudioTime + talkData.durationMs; final audioDelay = currentTime - expectedTime; // 如果延迟太大,清空缓冲区并直接播放 if (audioDelay > 500) { state.audioBuffer.clear(); if (state.isOpenVoice.value) { _playAudioFrames(); } return; } if (state.audioBuffer.length >= audioBufferSize) { state.audioBuffer.removeAt(0); // 丢弃最旧的数据 } state.audioBuffer.add(talkData); // 添加新数据 // 添加音频播放逻辑,与视频类似 _playAudioFrames(); break; case TalkData_ContentTypeE.Image: // 第一帧到达时记录开始时间 if (_isFirstFrame) { _startTime = currentTime; _isFirstFrame = false; AppLog.log('第一帧帧的时间戳:${talkData.durationMs}'); } // AppLog.log('其他帧的时间戳:${talkData.durationMs}'); // 计算帧间间隔 if (_lastFrameTimestamp != 0) { final int frameInterval = talkData.durationMs - _lastFrameTimestamp; _adjustBufferSize(frameInterval); // 根据帧间间隔调整缓冲区 } _lastFrameTimestamp = talkData.durationMs; // 更新上一帧时间戳 // 然后添加到播放缓冲区 if (state.videoBuffer.length >= bufferSize) { state.videoBuffer.removeAt(0); } state.videoBuffer.add(talkData); // 先进行解码和缓存 await _decodeAndCacheFrame(talkData); // 最后尝试播放 _playVideoFrames(); break; } }); } // 修改:视频帧播放逻辑 void _playVideoFrames() { // 如果缓冲区为空或未达到目标大小,不进行播放 if (state.videoBuffer.isEmpty || state.videoBuffer.length < bufferSize) { // AppLog.log('📊 缓冲中 - 当前缓冲区大小: ${state.videoBuffer.length}/${bufferSize}'); return; } // 找出时间戳最小的帧(最旧的帧) TalkData? oldestFrame; int oldestIndex = -1; for (int i = 0; i < state.videoBuffer.length; i++) { if (oldestFrame == null || state.videoBuffer[i].durationMs < oldestFrame.durationMs) { oldestFrame = state.videoBuffer[i]; oldestIndex = i; } } // 确保找到了有效帧 if (oldestFrame != null && oldestIndex != -1) { final cacheKey = oldestFrame.content.hashCode.toString(); // 使用缓存的解码图片更新显示 if (_imageCache.containsKey(cacheKey)) { state.currentImage.value = _imageCache[cacheKey]; state.listData.value = Uint8List.fromList(oldestFrame.content); state.videoBuffer.removeAt(oldestIndex); // 移除已播放的帧 // // 更新帧率计算 // _frameCount++; // final currentTime = DateTime.now().millisecondsSinceEpoch; // final elapsed = currentTime - _lastFpsUpdateTime; // // if (elapsed >= 1000) { // // 每秒更新一次 // state.fps.value = (_frameCount * 1000 / elapsed).round(); // _frameCount = 0; // _lastFpsUpdateTime = currentTime; // } } else { // AppLog.log('⚠️ 帧未找到缓存 - Key: $cacheKey'); state.videoBuffer.removeAt(oldestIndex); // 移除无法播放的帧 } } } // 新增:音频帧播放逻辑 void _playAudioFrames() { // 如果缓冲区为空或未达到目标大小,不进行播放 // 音频缓冲区要求更小,以减少延迟 if (state.audioBuffer.isEmpty || state.audioBuffer.length < audioBufferSize) { return; } // 找出时间戳最小的音频帧 TalkData? oldestFrame; int oldestIndex = -1; for (int i = 0; i < state.audioBuffer.length; i++) { if (oldestFrame == null || state.audioBuffer[i].durationMs < oldestFrame.durationMs) { oldestFrame = state.audioBuffer[i]; oldestIndex = i; } } // 确保找到了有效帧 if (oldestFrame != null && oldestIndex != -1) { if (state.isOpenVoice.value) { // 播放音频 _playAudioData(oldestFrame); } state.audioBuffer.removeAt(oldestIndex); } } // 新增:解码和缓存帧的方法 Future _decodeAndCacheFrame(TalkData talkData) async { try { String cacheKey = talkData.content.hashCode.toString(); // 如果该帧还没有被缓存,则进行解码和缓存 if (!_imageCache.containsKey(cacheKey)) { final Uint8List uint8Data = Uint8List.fromList(talkData.content); final ui.Image image = await decodeImageFromList(uint8Data); // 管理缓存大小 if (_imageCache.length >= bufferSize) { _imageCache.remove(_imageCache.keys.first); } // 添加到缓存 _imageCache[cacheKey] = image; // AppLog.log('📥 缓存新帧 - 缓存数: ${_imageCache.length}, Key: $cacheKey'); } } catch (e) { AppLog.log('❌ 帧解码错误: $e'); } } // 新增:动态调整缓冲区大小的方法 void _adjustBufferSize(int frameInterval) { const int frameDuration = 83; // 假设每帧的时间间隔为 83ms(12fps) const int delayThresholdHigh = frameDuration * 2; // 高延迟阈值(2帧时间) const int delayThresholdLow = frameDuration; // 低延迟阈值(1帧时间) const int adjustInterval = 1; // 每次调整1帧 if (frameInterval > delayThresholdHigh && bufferSize < maxBufferSize) { // 帧间间隔较大,增加缓冲区 bufferSize = min(bufferSize + adjustInterval, maxBufferSize); AppLog.log('📈 增加缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms'); } else if (frameInterval < delayThresholdLow && bufferSize > minBufferSize) { // 帧间间隔较小,减少缓冲区 bufferSize = max(bufferSize - adjustInterval, minBufferSize); AppLog.log('📉 减少缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms'); } } /// 监听对讲状态 void _startListenTalkStatus() { state.startChartTalkStatus.statusStream.listen((talkStatus) { state.talkStatus.value = talkStatus; switch (talkStatus) { case TalkStatus.rejected: case TalkStatus.hangingUpDuring: case TalkStatus.notTalkData: case TalkStatus.notTalkPing: case TalkStatus.end: _handleInvalidTalkStatus(); break; case TalkStatus.answeredSuccessfully: state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 state.oneMinuteTimeTimer ??= Timer.periodic(const Duration(seconds: 1), (Timer t) { if (state.listData.value.length > 0) { state.oneMinuteTime.value++; // if (state.oneMinuteTime.value >= 60) { // t.cancel(); // 取消定时器 // state.oneMinuteTime.value = 0; // // 倒计时结束挂断 // // udpHangUpAction(); // } } }); break; default: // 其他状态的处理 break; } }); } /// 播放音频数据 void _playAudioData(TalkData talkData) async { if (state.isOpenVoice.value) { final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); // // 将 PCM 数据转换为 PcmArrayInt16 final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list); FlutterPcmSound.feed(fromList); if (!state.isPlaying.value) { FlutterPcmSound.play(); state.isPlaying.value = true; } } } /// 停止播放音频 void _stopPlayG711Data() async { await FlutterPcmSound.pause(); await FlutterPcmSound.stop(); await FlutterPcmSound.clear(); } /// 开门 // udpOpenDoorAction() async { // final List? privateKey = // await Storage.getStringList(saveBluePrivateKey); // final List getPrivateKeyList = changeStringListToIntList(privateKey!); // // final List? signKey = await Storage.getStringList(saveBlueSignKey); // final List signKeyDataList = changeStringListToIntList(signKey!); // // final List? token = await Storage.getStringList(saveBlueToken); // final List getTokenList = changeStringListToIntList(token!); // // await _getLockNetToken(); // // final OpenLockCommand openLockCommand = OpenLockCommand( // lockID: BlueManage().connectDeviceName, // userID: await Storage.getUid(), // openMode: lockDetailState.openDoorModel, // openTime: _getUTCNetTime(), // onlineToken: lockDetailState.lockNetToken, // token: getTokenList, // needAuthor: 1, // signKey: signKeyDataList, // privateKey: getPrivateKeyList, // ); // final messageDetail = openLockCommand.packageData(); // // 将 List 转换为十六进制字符串 // String hexString = messageDetail // .map((byte) => byte.toRadixString(16).padLeft(2, '0')) // .join(' '); // // AppLog.log('open lock hexString: $hexString'); // // 发送远程开门消息 // StartChartManage().sendRemoteUnLockMessage( // bluetoothDeviceName: BlueManage().connectDeviceName, // openLockCommand: messageDetail, // ); // showToast('正在开锁中...'.tr); // } int _getUTCNetTime() { if (lockDetailState.isHaveNetwork) { return DateTime.now().millisecondsSinceEpoch ~/ 1000 + lockDetailState.differentialTime; } else { return 0; } } // 获取手机联网token,根据锁设置里面获取的开锁时是否联网来判断是否调用这个接口 Future _getLockNetToken() async { final LockNetTokenEntity entity = await ApiRepository.to.getLockNetToken( lockId: lockDetailState.keyInfos.value.lockId.toString()); if (entity.errorCode!.codeIsSuccessful) { lockDetailState.lockNetToken = entity.data!.token!.toString(); AppLog.log('从服务器获取联网token:${lockDetailState.lockNetToken}'); } else { BuglyTool.uploadException( message: '点击了需要联网开锁', detail: '点击了需要联网开锁 获取连网token失败', upload: true); showToast('网络访问失败,请检查网络是否正常'.tr, something: () {}); } } /// 获取权限状态 Future getPermissionStatus() async { final Permission permission = Permission.microphone; //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 final 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 { final PermissionStatus status = await permission.request(); if (status.isPermanentlyDenied) { openAppSettings(); } } Future requestPermissions() async { // 申请存储权限 var storageStatus = await Permission.storage.request(); // 申请录音权限 var microphoneStatus = await Permission.microphone.request(); if (storageStatus.isGranted && microphoneStatus.isGranted) { print("Permissions granted"); } else { print("Permissions denied"); // 如果权限被拒绝,可以提示用户或跳转到设置页面 if (await Permission.storage.isPermanentlyDenied) { openAppSettings(); // 跳转到应用设置页面 } } } Future startRecording() async { // requestPermissions(); // if (state.isRecordingScreen.value) { // showToast('录屏已开始,请勿重复点击'); // } // bool start = await FlutterScreenRecording.startRecordScreen( // "Screen Recording", // 视频文件名 // titleNotification: "Recording in progress", // 通知栏标题 // messageNotification: "Tap to stop recording", // 通知栏内容 // ); // // if (start) { // state.isRecordingScreen.value = true; // } } Future stopRecording() async { // String path = await FlutterScreenRecording.stopRecordScreen; // print("Recording saved to: $path"); // // // 将视频保存到系统相册 // bool? success = await GallerySaver.saveVideo(path); // if (success == true) { // print("Video saved to gallery"); // } else { // print("Failed to save video to gallery"); // } // // showToast('录屏结束,已保存到系统相册'); // state.isRecordingScreen.value = false; } @override void onReady() { super.onReady(); } @override void onInit() { super.onInit(); // 启动监听音视频数据流 _startListenTalkData(); // 启动监听对讲状态 _startListenTalkStatus(); // 在没有监听成功之前赋值一遍状态 // *** 由于页面会在状态变化之后才会初始化,导致识别不到最新的状态,在这里手动赋值 *** state.talkStatus.value = state.startChartTalkStatus.status; // 初始化音频播放器 _initFlutterPcmSound(); // 启动播放定时器 // _startPlayback(); // 初始化录音控制器 _initAudioRecorder(); requestPermissions(); } @override void onClose() { _stopPlayG711Data(); // 停止播放音频 state.listData.value = Uint8List(0); // 清空视频数据 state.audioBuffer.clear(); // 清空音频缓冲区 state.videoBuffer.clear(); // 清空视频缓冲区 state.oneMinuteTimeTimer?.cancel(); state.oneMinuteTimeTimer = null; stopProcessingAudio(); // 清理图片缓存 _imageCache.clear(); state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 state.oneMinuteTimeTimer = null; // 取消旧定时器 state.oneMinuteTime.value = 0; super.onClose(); } @override void dispose() { stopProcessingAudio(); // 重置期望数据 StartChartManage().reSetDefaultTalkExpect(); super.dispose(); } /// 处理无效通话状态 void _handleInvalidTalkStatus() { state.listData.value = Uint8List(0); // 停止播放音频 _stopPlayG711Data(); stopProcessingAudio(); } /// 更新发送预期数据 void updateTalkExpect() { TalkExpectReq talkExpectReq = TalkExpectReq(); state.isOpenVoice.value = !state.isOpenVoice.value; if (!state.isOpenVoice.value) { talkExpectReq = TalkExpectReq( videoType: [VideoTypeE.IMAGE], audioType: [], ); showToast('已静音'.tr); } else { talkExpectReq = TalkExpectReq( videoType: [VideoTypeE.IMAGE], audioType: [AudioTypeE.G711], ); } /// 修改发送预期数据 StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: talkExpectReq); } /// 截图并保存到相册 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'); showToast('截图已保存到相册'.tr); } catch (e) { AppLog.log('截图失败: $e'); } } // 远程开锁 Future remoteOpenLock() async { final lockPeerId = StartChartManage().lockPeerId; final lockListPeerId = StartChartManage().lockListPeerId; int lockId = lockDetailState.keyInfos.value.lockId ?? 0; // 如果锁列表获取到peerId,代表有多个锁,使用锁列表的peerId // 从列表中遍历出对应的peerId lockListPeerId.forEach((element) { if (element.network?.peerId == lockPeerId) { lockId = element.lockId ?? 0; } }); final LockSetInfoEntity lockSetInfoEntity = await ApiRepository.to.getLockSettingInfoData( lockId: lockId.toString(), ); if (lockSetInfoEntity.errorCode!.codeIsSuccessful) { if (lockSetInfoEntity.data?.lockFeature?.remoteUnlock == 1 && lockSetInfoEntity.data?.lockSettingInfo?.remoteUnlock == 1) { final LoginEntity entity = await ApiRepository.to .remoteOpenLock(lockId: lockId.toString(), timeOut: 60); if (entity.errorCode!.codeIsSuccessful) { showToast('已开锁'.tr); StartChartManage().lockListPeerId = []; } } else { showToast('该锁的远程开锁功能未启用'.tr); } } } /// 初始化音频录制器 void _initAudioRecorder() { state.voiceProcessor = VoiceProcessor.instance; } //开始录音 Future startProcessingAudio() async { try { if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { await state.voiceProcessor?.start(state.frameLength, state.sampleRate); final bool? isRecording = await state.voiceProcessor?.isRecording(); state.isRecordingAudio.value = isRecording!; state.startRecordingAudioTime.value = DateTime.now(); // 增加录音帧监听器和错误监听器 state.voiceProcessor ?.addFrameListeners([_onFrame]); state.voiceProcessor?.addErrorListener(_onError); } else { // state.errorMessage.value = 'Recording permission not granted'; } } on PlatformException catch (ex) { // state.errorMessage.value = 'Failed to start recorder: $ex'; } state.isOpenVoice.value = false; } /// 停止录音 Future stopProcessingAudio() async { try { await state.voiceProcessor?.stop(); state.voiceProcessor?.removeFrameListener(_onFrame); state.udpSendDataFrameNumber = 0; // 记录结束时间 state.endRecordingAudioTime.value = DateTime.now(); // 计算录音的持续时间 final Duration duration = state.endRecordingAudioTime.value .difference(state.startRecordingAudioTime.value); state.recordingAudioTime.value = duration.inSeconds; } on PlatformException catch (ex) { // state.errorMessage.value = 'Failed to stop recorder: $ex'; } finally { final bool? isRecording = await state.voiceProcessor?.isRecording(); state.isRecordingAudio.value = isRecording!; state.isOpenVoice.value = true; } } // 音频帧处理 Future _onFrame(List frame) async { // 添加最大缓冲限制 if (_bufferedAudioFrames.length > state.frameLength * 3) { _bufferedAudioFrames.clear(); // 清空过多积累的数据 return; } // 首先应用固定增益提升基础音量 List amplifiedFrame = _applyGain(frame, 1.6); // 编码为G711数据 List encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law _bufferedAudioFrames.addAll(encodedData); // 使用相对时间戳 final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使用循环时间戳 int getFrameLength = state.frameLength; if (Platform.isIOS) { getFrameLength = state.frameLength * 2; } // 添加发送间隔控制 if (_bufferedAudioFrames.length >= state.frameLength) { try { await StartChartManage().sendTalkDataMessage( talkData: TalkData( content: _bufferedAudioFrames, contentType: TalkData_ContentTypeE.G711, durationMs: ms, ), ); } finally { _bufferedAudioFrames.clear(); // 确保清理缓冲区 } } else { _bufferedAudioFrames.addAll(encodedData); } } // 错误监听 void _onError(VoiceProcessorException error) { AppLog.log(error.message!); } // 添加音频增益处理方法 List _applyGain(List pcmData, double gainFactor) { List result = List.filled(pcmData.length, 0); for (int i = 0; i < pcmData.length; i++) { // PCM数据通常是有符号的16位整数 int sample = pcmData[i]; // 应用增益 double amplified = sample * gainFactor; // 限制在有效范围内,防止溢出 if (amplified > 32767) { amplified = 32767; } else if (amplified < -32768) { amplified = -32768; } result[i] = amplified.toInt(); } return result; } }