app-starlock/lib/talk/starChart/views/talkView/talk_view_logic.dart

665 lines
22 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'dart:math'; // Import the math package to use sqrt
2025-05-08 11:33:38 +08:00
import 'dart:ui' show decodeImageFromList;
2025-02-21 14:30:21 +08:00
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pcm_sound/flutter_pcm_sound.dart';
2024-12-30 11:53:42 +08:00
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';
2025-01-23 14:30:31 +08:00
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart';
2025-01-23 14:30:31 +08:00
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';
2025-04-02 17:36:27 +08:00
import 'package:star_lock/tools/G711Tool.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/commonDataManage.dart';
import 'package:star_lock/tools/storage.dart';
import '../../../../tools/baseGetXController.dart';
class TalkViewLogic extends BaseGetXController {
final TalkViewState state = TalkViewState();
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
2025-05-08 11:33:38 +08:00
int bufferSize = 8; // 增大缓冲区,满时才渲染
int audioBufferSize = 2; // 音频默认缓冲2帧
bool _isFirstAudioFrame = true; // 是否是第一帧
2025-05-08 11:33:38 +08:00
2025-03-13 15:07:13 +08:00
int _startAudioTime = 0; // 开始播放时间戳
// 定义音频帧缓冲和发送函数
2025-02-24 19:01:38 +08:00
final List<int> _bufferedAudioFrames = <int>[];
2025-04-27 09:53:05 +08:00
// 添加监听状态和订阅引用
bool _isListening = false;
StreamSubscription? _streamSubscription;
2025-05-08 11:33:38 +08:00
Timer? videoRenderTimer; // 视频渲染定时器
int _renderedFrameCount = 0;
int _lastFpsPrintTime = DateTime.now().millisecondsSinceEpoch;
2024-12-30 11:53:42 +08:00
/// 初始化音频播放器
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() {
2025-04-27 09:53:05 +08:00
// 防止重复监听
if (_isListening) {
AppLog.log("已经存在数据流监听,避免重复监听");
return;
}
AppLog.log("==== 启动新的数据流监听 ====");
_isListening = true;
_streamSubscription = state.talkDataRepository.talkDataStream
.listen((TalkDataModel talkDataModel) async {
final talkData = talkDataModel.talkData;
final contentType = talkData!.contentType;
final currentTime = DateTime.now().millisecondsSinceEpoch;
// 判断数据类型,进行分发处理
switch (contentType) {
case TalkData_ContentTypeE.G711:
2025-08-28 13:41:53 +08:00
// 没有开启所有和录音时不缓存和播放音频
if (!state.isOpenVoice.value && state.isRecordingAudio.value) {
return;
}
2025-03-13 15:07:13 +08:00
if (state.audioBuffer.length >= audioBufferSize) {
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
}
state.audioBuffer.add(talkData); // 添加新数据
// 添加音频播放逻辑,与视频类似
_playAudioFrames();
break;
case TalkData_ContentTypeE.Image:
2025-05-08 11:33:38 +08:00
// 固定长度缓冲区最多保留bufferSize帧
state.videoBuffer.add(talkData);
2025-05-08 11:33:38 +08:00
if (state.videoBuffer.length > bufferSize) {
state.videoBuffer.removeAt(0); // 移除最旧帧
}
break;
}
});
}
// 新增:音频帧播放逻辑
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);
}
}
/// 监听对讲状态
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++;
2025-04-02 09:58:52 +08:00
// if (state.oneMinuteTime.value >= 60) {
// t.cancel(); // 取消定时器
// state.oneMinuteTime.value = 0;
// // 倒计时结束挂断
// // udpHangUpAction();
// }
}
});
break;
default:
// 其他状态的处理
break;
}
});
}
2024-12-30 11:53:42 +08:00
/// 播放音频数据
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<String>? privateKey =
// await Storage.getStringList(saveBluePrivateKey);
// final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
//
// final List<String>? signKey = await Storage.getStringList(saveBlueSignKey);
// final List<int> signKeyDataList = changeStringListToIntList(signKey!);
//
// final List<String>? token = await Storage.getStringList(saveBlueToken);
// final List<int> 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<int> 转换为十六进制字符串
// 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;
}
}
2024-12-30 11:53:42 +08:00
/// 获取权限状态
Future<bool> 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<void> 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<void> 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<void> 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();
2024-12-30 11:53:42 +08:00
// 启动监听音视频数据流
_startListenTalkData();
2024-12-30 11:53:42 +08:00
// 启动监听对讲状态
_startListenTalkStatus();
// 在没有监听成功之前赋值一遍状态
// *** 由于页面会在状态变化之后才会初始化,导致识别不到最新的状态,在这里手动赋值 ***
state.talkStatus.value = state.startChartTalkStatus.status;
2024-12-30 11:53:42 +08:00
// 初始化音频播放器
_initFlutterPcmSound();
2024-12-30 11:53:42 +08:00
// 启动播放定时器
// _startPlayback();
2024-12-30 11:53:42 +08:00
// 初始化录音控制器
_initAudioRecorder();
requestPermissions();
2025-05-08 11:33:38 +08:00
// 启动视频渲染定时器10fps
videoRenderTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
final int now = DateTime.now().millisecondsSinceEpoch;
if (state.videoBuffer.isNotEmpty) {
final TalkData oldestFrame = state.videoBuffer.removeAt(0);
if (oldestFrame.content.isNotEmpty) {
2025-08-28 13:41:53 +08:00
state.listData.value =
Uint8List.fromList(oldestFrame.content); // 备份原始数据
2025-05-08 11:33:38 +08:00
final int decodeStart = DateTime.now().millisecondsSinceEpoch;
2025-08-28 13:41:53 +08:00
decodeImageFromList(Uint8List.fromList(oldestFrame.content))
.then((ui.Image img) {
2025-05-08 11:33:38 +08:00
final int decodeEnd = DateTime.now().millisecondsSinceEpoch;
state.currentImage.value = img;
_renderedFrameCount++;
// 每秒统计一次fps
if (now - _lastFpsPrintTime >= 1000) {
// print('实际渲染fps: $_renderedFrameCount');
_renderedFrameCount = 0;
_lastFpsPrintTime = now;
}
}).catchError((e) {
print('图片解码失败: $e');
});
}
}
// 如果缓冲区为空,不做任何操作,保持上一次内容
});
}
@override
void onClose() {
_stopPlayG711Data(); // 停止播放音频
state.listData.value = Uint8List(0); // 清空视频数据
state.audioBuffer.clear(); // 清空音频缓冲区
state.videoBuffer.clear(); // 清空视频缓冲区
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
2025-01-14 17:57:33 +08:00
stopProcessingAudio();
// 清理图片缓存
2025-05-08 11:33:38 +08:00
// _imageCache.clear();
2025-04-02 09:58:52 +08:00
state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器
state.oneMinuteTimeTimer = null; // 取消旧定时器
state.oneMinuteTime.value = 0;
2025-04-27 09:53:05 +08:00
// 取消数据流监听
_streamSubscription?.cancel();
_isListening = false;
2025-05-08 11:33:38 +08:00
// 释放视频渲染定时器
videoRenderTimer?.cancel();
videoRenderTimer = null;
super.onClose();
}
2025-01-14 17:57:33 +08:00
@override
void dispose() {
stopProcessingAudio();
// 重置期望数据
StartChartManage().reSetDefaultTalkExpect();
2025-05-08 11:33:38 +08:00
// 释放视频渲染定时器
videoRenderTimer?.cancel();
videoRenderTimer = null;
2025-01-14 17:57:33 +08:00
super.dispose();
}
/// 处理无效通话状态
void _handleInvalidTalkStatus() {
state.listData.value = Uint8List(0);
// 停止播放音频
_stopPlayG711Data();
2025-01-14 17:57:33 +08:00
stopProcessingAudio();
}
/// 更新发送预期数据
void updateTalkExpect() {
TalkExpectReq talkExpectReq = TalkExpectReq();
2025-02-19 16:22:01 +08:00
state.isOpenVoice.value = !state.isOpenVoice.value;
if (!state.isOpenVoice.value) {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
2025-02-19 16:22:01 +08:00
audioType: [],
);
2025-02-19 16:22:01 +08:00
showToast('已静音'.tr);
} else {
talkExpectReq = TalkExpectReq(
videoType: [VideoTypeE.IMAGE],
2025-02-19 16:22:01 +08:00
audioType: [AudioTypeE.G711],
);
}
/// 修改发送预期数据
StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
talkExpect: talkExpectReq);
}
/// 截图并保存到相册
Future<void> 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<void> remoteOpenLock() async {
final LockListInfoItemEntity currentKeyInfo =
CommonDataManage().currentKeyInfo;
var lockId = currentKeyInfo.lockId ?? 0;
var remoteUnlock = currentKeyInfo.lockSetting?.remoteUnlock ?? 0;
final lockPeerId = StartChartManage().lockPeerId;
final LockListInfoGroupEntity? lockListInfoGroupEntity =
2025-08-28 13:41:53 +08:00
await Storage.getLockMainListData();
if (lockListInfoGroupEntity != null) {
lockListInfoGroupEntity!.groupList?.forEach((element) {
final lockList = element.lockList;
if (lockList != null && lockList.length != 0) {
for (var lockInfo in lockList) {
final peerId = lockInfo.network?.peerId;
if (peerId != null && peerId != '') {
if (peerId == lockPeerId) {
lockId = lockInfo.lockId ?? 0;
remoteUnlock = lockInfo.lockSetting?.remoteUnlock ?? 0;
}
}
}
}
});
}
if (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);
}
}
2024-12-30 11:53:42 +08:00
/// 初始化音频录制器
void _initAudioRecorder() {
state.voiceProcessor = VoiceProcessor.instance;
}
2025-08-15 13:55:39 +08:00
Timer? _startProcessingAudioTimer;
2024-12-30 11:53:42 +08:00
//开始录音
Future<void> 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(<VoiceProcessorFrameListener>[_onFrame]);
state.voiceProcessor?.addErrorListener(_onError);
2024-12-30 11:53:42 +08:00
} else {
// state.errorMessage.value = 'Recording permission not granted';
}
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to start recorder: $ex';
}
}
/// 停止录音
Future<void> stopProcessingAudio() async {
try {
await state.voiceProcessor?.stop();
state.voiceProcessor?.removeFrameListener(_onFrame);
state.udpSendDataFrameNumber = 0;
// 记录结束时间
state.endRecordingAudioTime.value = DateTime.now();
// 计算录音的持续时间
2025-02-24 19:01:38 +08:00
final Duration duration = state.endRecordingAudioTime.value
.difference(state.startRecordingAudioTime.value);
2024-12-30 11:53:42 +08:00
state.recordingAudioTime.value = duration.inSeconds;
} on PlatformException catch (ex) {
// state.errorMessage.value = 'Failed to stop recorder: $ex';
} finally {
// 延迟关闭定时器,确保剩余数据能发送出去
if (_startProcessingAudioTimer != null) {
// 插入5个320长度的全0数据包
for (int i = 0; i < 5; i++) {
_bufferedAudioFrames.addAll(List.filled(chunkSize, 0));
}
Future.delayed(const Duration(milliseconds: 300), () {
_startProcessingAudioTimer?.cancel();
_startProcessingAudioTimer = null;
_bufferedAudioFrames.clear();
});
} else {
_bufferedAudioFrames.clear();
}
2024-12-30 11:53:42 +08:00
final bool? isRecording = await state.voiceProcessor?.isRecording();
state.isRecordingAudio.value = isRecording!;
}
}
2025-08-15 13:55:39 +08:00
static const int chunkSize = 320; // 每次发送320字节10ms G.711
static const int intervalMs = 40; // 每40ms发送一次4个chunk
void _sendAudioChunk(Timer timer) async {
if (_bufferedAudioFrames.length < chunkSize) {
// 数据不足,等待下一周期
2025-04-07 14:44:49 +08:00
return;
}
2025-08-15 13:55:39 +08:00
// 截取前 chunkSize 个字节
final chunk = _bufferedAudioFrames.sublist(0, chunkSize);
// 更新缓冲区:移除已发送部分
_bufferedAudioFrames.removeRange(0, chunkSize);
// 获取时间戳(相对时间)
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000;
print('Send chunk ${timer.tick}: ${chunk.take(10).toList()}...');
await StartChartManage().sendTalkDataMessage(
talkData: TalkData(
content: chunk,
contentType: TalkData_ContentTypeE.G711,
durationMs: ms,
),
);
}
// 音频帧处理
Future<void> _onFrame(List<int> frame) async {
final applyGain = _applyGain(frame, 1.6);
2025-04-02 17:36:27 +08:00
// 编码为G711数据
2025-08-15 13:55:39 +08:00
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
2025-04-02 17:36:27 +08:00
_bufferedAudioFrames.addAll(encodedData);
2025-02-24 19:01:38 +08:00
2025-08-15 13:55:39 +08:00
// 启动定时发送器(仅启动一次)
2025-08-28 13:41:53 +08:00
if (_startProcessingAudioTimer == null &&
_bufferedAudioFrames.length > chunkSize) {
_startProcessingAudioTimer =
Timer.periodic(Duration(milliseconds: intervalMs), _sendAudioChunk);
2025-02-24 19:01:38 +08:00
}
2024-12-30 11:53:42 +08:00
}
2025-02-21 14:30:21 +08:00
// 错误监听
2024-12-30 11:53:42 +08:00
void _onError(VoiceProcessorException error) {
AppLog.log(error.message!);
}
2025-04-03 10:36:30 +08:00
// 添加音频增益处理方法
List<int> _applyGain(List<int> pcmData, double gainFactor) {
return pcmData.map((sample) {
// 增益并裁剪
int amplified = (sample * gainFactor).round();
return amplified.clamp(-32768, 32767);
}).toList();
2025-04-03 10:36:30 +08:00
}
}