Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
1fc7221948
  1. 1
      lib/core/utils/upload_oss.dart
  2. 11
      lib/data/services/asr_service.dart
  3. 38
      lib/data/services/ble_manager.dart
  4. 60
      lib/data/services/meeting/meeting_upload_service.dart
  5. 57
      lib/data/services/speech_impl/azure_asr_service.dart
  6. 18
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  7. 18
      lib/data/services/speech_impl/volcano_asr_service.dart
  8. 100
      lib/data/services/speech_impl/xunfei_asr_service.dart
  9. 13
      lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart
  10. 90
      lib/modules/agent/controllers/agent_controller.dart
  11. 32
      lib/modules/agent/views/message_bubble.dart
  12. 2
      lib/modules/devices/controllers/devices_controller.dart
  13. 9
      lib/modules/meeting/controllers/meeting_controller.dart
  14. 3
      lib/modules/meeting/controllers/meeting_details_controller.dart
  15. 31
      lib/modules/meeting/controllers/meeting_record_controller.dart
  16. 41
      lib/modules/meeting/views/meeting_view.dart
  17. 44
      lib/modules/settings/views/settings_view.dart
  18. 10
      lib/modules/speech_test/controllers/speech_test_controller.dart
  19. 34
      lib/modules/translation/controllers/translation_controller.dart
  20. 676
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  21. 86
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  22. 22
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt
  23. 53
      local_plugins/agent_service/lib/agent_service.dart
  24. 824
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  25. 128
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  26. 3
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt
  27. 55
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

1
lib/core/utils/upload_oss.dart

@ -51,6 +51,7 @@ class UploadOss {
bucket,
pathName,
filePath: filepath,
progressCallBack: onSendProgress,
resultListener: ResultListener(
(Map<String?, String?>? header, CosXmlResult? result) {
final url = result?.accessUrl ??

11
lib/data/services/asr_service.dart

@ -19,18 +19,17 @@ abstract class AsrService {
/// 开始连续语音识别
///
/// 返回一个包含识别事件的流
Future<Stream<RecognitionEvent>> startContinuousRecognition(
Future<bool> startContinuousRecognition(
bool audioSourceType,
);
/// 停止连续语音识别
Future<bool> stopContinuousRecognition();
/// 停止音频输入
Future<void> setPause();
/// 继续音频输入
Future<void> setContinue();
/// 设置识别回掉
///
/// 返回一个包含识别事件的流
Future<Stream<RecognitionEvent>> recognizeCallback();
/// 恢复原始音频状态
Future<void> restoreOriginalAudioState();

38
lib/data/services/ble_manager.dart

@ -472,37 +472,13 @@ class BleManager extends GetxService {
firmwareProductColor: '',
);
}
final commands = [
_bleService.getBatteryInfo,
_bleService.getDeviceVersion,
_bleService.getProductId,
];
final delays = [
Duration.zero, // 第一个命令立即执行
const Duration(milliseconds: 100), // 第二个命令延迟100ms
const Duration(milliseconds: 100), // 第三个命令延迟200ms
];
final futures = commands
.asMap()
.map((index, command) {
final delay = delays[index];
return MapEntry(
index,
Future.delayed(delay, command),
);
})
.values
.toList();
final results = await Future.wait(futures);
// // 并行获取所有设备信息,不使用延迟
// final results = await Future.wait([
// _bleService.getBatteryInfo(),
// _bleService.getDeviceVersion(),
// _bleService.getProductId(),
// ]);
// 并行获取所有设备信息,不使用延迟
final results = await Future.wait([
_bleService.getBatteryInfo(),
_bleService.getDeviceVersion(),
_bleService.getProductId(),
]);
Logger.info(
'设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}');

60
lib/data/services/meeting/meeting_upload_service.dart

@ -1,9 +1,13 @@
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/upload_oss.dart';
import '../db/sqflite_api.dart';
class MeetingUploadService extends GetxService {
final GetStorage _storage = GetStorage();
RxInt meetingId = 0.obs;
RxDouble uploadProgress = 0.0.obs;
RxList uploadList = [].obs;
@ -17,7 +21,10 @@ class MeetingUploadService extends GetxService {
_getUploadList();
}
void _getUploadList() {}
void _getUploadList() {
uploadList.value = _storage.read("upload_list") ?? [];
_executeUpload();
}
void addUpload(int id, String filePath, String rootDir) async {
uploadList.add({
@ -26,25 +33,56 @@ class MeetingUploadService extends GetxService {
'rootdir': rootDir,
'audiourl': '',
});
_storage.write("upload_list", uploadList);
_executeUpload();
}
void _executeUpload() async {
if (_isUpload) return;
_isUpload = true;
_handled = 0;
while (_handled < uploadList.length) {
Map element = uploadList[_handled];
_handled++;
if (element['audiourl'].isNotEmpty) {
if (element['audiourl'].isEmpty) {
meetingId.value = element['id'];
final audiourl = await UploadOss.upload(
filepath: element['filepath'],
rootDir: element['rootdir'],
);
await SqfliteApi.editMeetingTitle(
element['id'],
{'audiourl': audiourl},
);
element['audiourl'] = audiourl;
try {
final audiourl = await UploadOss.upload(
filepath: element['filepath'],
rootDir: element['rootdir'],
onSendProgress: (int count, int total) {
uploadProgress.value = count / total;
},
);
await SqfliteApi.editMeetingTitle(
element['id'],
{'audiourl': audiourl},
);
element['audiourl'] = audiourl;
uploadProgress.value = 0.0;
_storage.write("upload_list", uploadList);
} catch (e) {
Logger.error('Upload failed: $e');
}
}
}
meetingId.value = 0;
_isUpload = false;
}
void removeUpload(int id, {bool isDeleteTask = false}) {
Map uploadData = uploadList.firstWhere(
(element) => element['id'] == id,
orElse: () => {},
);
if (uploadData.isNotEmpty) {
if (isDeleteTask || uploadData['audiourl'].isNotEmpty) {
uploadList.remove(uploadData);
_storage.write("upload_list", uploadList);
if (uploadList.isEmpty) {
_handled = 0;
}
}
}
}
}

57
lib/data/services/speech_impl/azure_asr_service.dart

@ -150,8 +150,7 @@ class AzureAsrService extends GetxService implements AsrService {
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
Future<bool> startContinuousRecognition(bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}
@ -161,8 +160,6 @@ class AzureAsrService extends GetxService implements AsrService {
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别
final bool result =
await _channel.invokeMethod('startContinuousRecognition', {
@ -170,15 +167,13 @@ class AzureAsrService extends GetxService implements AsrService {
});
if (!result) {
_cleanupEventStream();
throw Exception('启动连续识别失败');
}
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
return result;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow;
}
}
@ -192,11 +187,30 @@ class AzureAsrService extends GetxService implements AsrService {
try {
final bool result =
await _channel.invokeMethod('stopContinuousRecognition');
_cleanupEventStream();
return result;
} catch (e) {
Logger.error('停止连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow;
}
}
@override
Future<Stream<RecognitionEvent>> recognizeCallback() async {
if (!_isInitialized) {
await initialize();
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别
final bool result = await _channel.invokeMethod('recognizeCallback');
if (!result) {
_cleanupEventStream();
}
return _eventStreamController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow;
}
}
@ -415,31 +429,6 @@ class AzureAsrService extends GetxService implements AsrService {
}
}
@override
Future<void> setPause() async {
// TODO: implement setPause
try {
final bool result = await _channel.invokeMethod('setPause');
return;
} catch (e) {
Logger.error('开始录音: ${e.toString()}');
rethrow;
}
}
@override
Future<void> setContinue() async {
// TODO: implement setcontinue
try {
final bool result = await _channel.invokeMethod('setcontinue');
return;
} catch (e) {
Logger.error('开始录音: ${e.toString()}');
rethrow;
}
}
@override
Future<void> disableBluetoothAudio() async {
// TODO: implement disableBluetoothAudio

18
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -842,18 +842,6 @@ class VolcanoAsrApiService implements AsrService {
throw UnimplementedError();
}
@override
Future<void> setPause() {
// TODO: implement setPause
throw UnimplementedError();
}
@override
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}
@override
Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio
@ -865,4 +853,10 @@ class VolcanoAsrApiService implements AsrService {
// TODO: implement restoreOriginalAudioState
throw UnimplementedError();
}
@override
Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement recognizeCallback
throw UnimplementedError();
}
}

18
lib/data/services/speech_impl/volcano_asr_service.dart

@ -377,18 +377,6 @@ class VolcanoAsrService extends GetxService implements AsrService {
throw UnimplementedError();
}
@override
Future<void> setPause() {
// TODO: implement setPause
throw UnimplementedError();
}
@override
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}
@override
Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio
@ -400,4 +388,10 @@ class VolcanoAsrService extends GetxService implements AsrService {
// TODO: implement restoreOriginalAudioState
throw UnimplementedError();
}
@override
Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement recognizeCallback
throw UnimplementedError();
}
}

100
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -74,48 +74,48 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
await recorder?.dispose();
recorder = AudioRecorder();
Stream<Uint8List>? stream = await recorder?.startStream(
const RecordConfig(
sampleRate: 16000,
encoder: AudioEncoder.pcm16bits,
numChannels: 1,
),
);
isRecording = true;
stream?.listen((data) {
if (isRecording) {
_buff = _appendToBuffer(data);
_originaBytes.add(data);
}
});
await _tryConnecting();
_timer = Timer.periodic(const Duration(milliseconds: 40), (timer) async {
//每40毫秒读取一次数据
var frame = _getAudioData();
if (frame.isNotEmpty) {
_channel?.sink.add(frame);
}
});
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow;
}
}
// @override
// Future<Stream<RecognitionEvent>> startContinuousRecognition(
// bool audioSourceType) async {
// if (!_isInitialized) {
// await initialize();
// }
// try {
// _eventStreamController = StreamController<RecognitionEvent>.broadcast();
// await recorder?.dispose();
// recorder = AudioRecorder();
// Stream<Uint8List>? stream = await recorder?.startStream(
// const RecordConfig(
// sampleRate: 16000,
// encoder: AudioEncoder.pcm16bits,
// numChannels: 1,
// ),
// );
// isRecording = true;
// stream?.listen((data) {
// if (isRecording) {
// _buff = _appendToBuffer(data);
// _originaBytes.add(data);
// }
// });
// await _tryConnecting();
// _timer = Timer.periodic(const Duration(milliseconds: 40), (timer) async {
// //每40毫秒读取一次数据
// var frame = _getAudioData();
// if (frame.isNotEmpty) {
// _channel?.sink.add(frame);
// }
// });
// _isContinuousRecognitionActive = true;
// return _eventStreamController!.stream;
// } catch (e) {
// Logger.error('开始连续语音识别失败: ${e.toString()}');
// rethrow;
// }
// }
//写入音频数据到缓存区中
Uint8List _appendToBuffer(Uint8List newData) {
@ -302,26 +302,26 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
@override
Future<void> setPause() {
// TODO: implement setPause
Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio
throw UnimplementedError();
}
@override
Future<void> setContinue() {
// TODO: implement setcontinue
Future<void> restoreOriginalAudioState() {
// TODO: implement restoreOriginalAudioState
throw UnimplementedError();
}
@override
Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio
Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement recognizeCallback
throw UnimplementedError();
}
@override
Future<void> restoreOriginalAudioState() {
// TODO: implement restoreOriginalAudioState
Future<bool> startContinuousRecognition(bool audioSourceType) {
// TODO: implement startContinuousRecognition
throw UnimplementedError();
}
}

13
lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart

@ -49,6 +49,7 @@ class FTFTranslationController extends GetxController
Get.put(XunfeiAsrService());
final TtsService _ttsService = Get.find<TtsService>();
_asrService = Get.find<XunfeiAsrService>();
_InfoRecorder();
}
@override
@ -58,15 +59,19 @@ class FTFTranslationController extends GetxController
super.onClose();
}
//开始录音
Future<void> _startRecorder() async {
final recognitionStream =
await _asrService.startContinuousRecognition(false);
//开始录音
Future<void> _InfoRecorder() async {
final recognitionStream = await _asrService.recognizeCallback();
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,
);
}
//开始录音
Future<void> _startRecorder() async {
await _asrService.startContinuousRecognition(false);
}
//结束录音
Future<void> _stopRecorder() async {
await _asrService.stopContinuousRecognition();

90
lib/modules/agent/controllers/agent_controller.dart

@ -295,7 +295,8 @@ class AgentController extends GetxController {
currentPage.value = 1;
hasMoreMessages.value = result.totalPages > 1;
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}');
Logger.i(TAG,
'成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}');
_logMessageDetails(historyMessages);
} else {
// 没有历史记录时添加欢迎消息
@ -318,7 +319,8 @@ class AgentController extends GetxController {
Future<void> loadMoreMessages() async {
// 防止重复加载
if (isLoadingMore.value || !hasMoreMessages.value) {
Logger.d(TAG, '跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreMessages=${hasMoreMessages.value}');
Logger.d(TAG,
'跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreMessages=${hasMoreMessages.value}');
return;
}
@ -353,7 +355,8 @@ class AgentController extends GetxController {
currentPage.value = nextPage;
hasMoreMessages.value = nextPage < result.totalPages;
Logger.i(TAG, '成功加载 ${newMessages.length} 条历史消息,当前页: $nextPage/${result.totalPages}');
Logger.i(TAG,
'成功加载 ${newMessages.length} 条历史消息,当前页: $nextPage/${result.totalPages}');
} else {
// 没有更多消息
hasMoreMessages.value = false;
@ -416,7 +419,8 @@ class AgentController extends GetxController {
void _setupScrollListener() {
scrollController.addListener(() {
// 检查是否滚动到顶部(因为使用了reverse: true,顶部实际是最大滚动位置)
if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 100) {
// 当距离顶部100像素时触发加载更多
if (hasMoreMessages.value && !isLoadingMore.value) {
Logger.d(TAG, '触发加载更多历史消息');
@ -432,7 +436,8 @@ class AgentController extends GetxController {
Logger.i(TAG, '手动触发加载更多历史消息');
loadMoreMessages();
} else {
Logger.w(TAG, '无法加载更多:hasMoreMessages=${hasMoreMessages.value}, isLoadingMore=${isLoadingMore.value}');
Logger.w(TAG,
'无法加载更多:hasMoreMessages=${hasMoreMessages.value}, isLoadingMore=${isLoadingMore.value}');
}
}
@ -981,7 +986,8 @@ class AgentController extends GetxController {
permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风
explanationText: 'voiceRecognitionPermissionText'.tr, // 语音识别功能需要访问麦克风。
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
permanentDenialText:
'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
settingsButtonText: 'goToSettings'.tr, // 去设置
cancelButtonText: 'cancel'.tr, // 取消
);
@ -1007,13 +1013,14 @@ class AgentController extends GetxController {
// 开始语音输入
Future<void> startVoiceInput() async {
if (isListening.value) return;
print('startVoiceInput'.tr); // 开始语音输入
print('开始语音输入'); // 开始语音输入
final hasPermission = await PermissionUtil.instance.requestPermission(
// context: Get.context!,
permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风
explanationText: 'voiceTranslationPermissionText'.tr, // 语音识别与翻译功能需要访问麦克风。
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
permanentDenialText:
'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
settingsButtonText: 'goToSettings'.tr, // 去设置
cancelButtonText: 'cancel'.tr, // 取消
);
@ -1022,11 +1029,11 @@ class AgentController extends GetxController {
return;
}
try {
await AgentService.startConversation();
} catch (e) {
Logger.e(TAG, '启动语音输入失败: $e');
}
// try {
// } catch (e) {
// Logger.e(TAG, '启动语音输入失败: $e');
// }
}
// 停止语音输入
@ -1278,32 +1285,32 @@ class AgentController extends GetxController {
isPhoneCallMode.value = true;
// 如果当前是文本模式,切换到语音模式????
if (isListening.value) {
// 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission(
permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风
explanationText: 'phoneCallPermissionText'.tr, // 打电话功能需要访问麦克风进行语音通话。
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
settingsButtonText: 'goToSettings'.tr, // 去设置
cancelButtonText: 'cancel'.tr, // 取消
);
if (!hasPermission) {
// 权限未获取,退出打电话模式并恢复之前状态
isPhoneCallMode.value = false;
_previousInputMode = null; // 清除历史记录
return;
}
// if (isListening.value) {
// 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission(
permissionType: Permission.microphone,
permissionName: '麦克风',
explanationText: '打电话功能需要访问麦克风进行语音通话。',
permanentDenialText: '您已永久拒绝麦克风权限,请前往系统设置开启权限。',
settingsButtonText: '去设置',
cancelButtonText: '取消',
);
// 切换到语音模式并开始语音输入
isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入
//isPushToTalkMode.value = false;
// 开始语音识别
print('startPhoneCallMode'.tr); // 开始打电话模式,开始语音输入
await AgentService.audioContinue();
if (!hasPermission) {
// 权限未获取,退出打电话模式并恢复之前状态
isPhoneCallMode.value = false;
_previousInputMode = null; // 清除历史记录
return;
}
// 切换到语音模式并开始语音输入
isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入
//isPushToTalkMode.value = false;
// 开始语音识别
print("开始打电话模式,开始语音输入");
await AgentService.startConversation();
//}
}
// 结束打电话模式
@ -1316,7 +1323,7 @@ class AgentController extends GetxController {
// 停止语音输入
//await stopVoiceInput();
// 开始语音识别
await AgentService.audioPause();
await AgentService.stopConversation();
// 恢复之前的输入模式,而不是强制切换到文字模式
_restorePreviousInputMode();
}
@ -1411,9 +1418,9 @@ class AgentController extends GetxController {
// 设置按住说话状态
isPushToTalkActive.value = true;
//await AgentService.recognizeCallback();
// 开始语音识别
await AgentService.audioContinue();
await AgentService.startConversation();
// await startVoiceInput();
}
@ -1426,7 +1433,8 @@ class AgentController extends GetxController {
// 清除按住说话状态(但保持按住说话模式)
isPushToTalkActive.value = false;
await AgentService.audioPause();
await AgentService.stopConversation();
// 停止语音识别
// await stopVoiceInput();
}

32
lib/modules/agent/views/message_bubble.dart

@ -1,7 +1,3 @@
import 'dart:convert';
import 'dart:ffi';
import 'package:common_utils/common_utils.dart';
import 'package:deep_voice/modules/agent/views/navigation_card.dart';
import 'weather_card.dart';
import 'music_card.dart';
@ -9,7 +5,6 @@ import 'travel_card.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:get_storage/get_storage.dart';
import 'dart:io';
import 'package:flutter_markdown/flutter_markdown.dart';
@ -257,18 +252,21 @@ class MessageBubble extends GetView<AgentController> {
return buildMusicContent(context, playlist);
}
} else if (meta!.keys.contains('maps_weather')) {
final city = meta!['maps_weather']['city'] as String? ?? '未知城市';
final rawList = meta!['maps_weather']['forecasts'] as List? ?? [];
final daylist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
final now = daylist.isNotEmpty ? daylist[0] : <String, dynamic>{};
//天气
return buildWeatherContent(
context,
city,
now,
daylist,
);
final maps_weather = meta!['maps_weather'] as Map<String, dynamic>;
final city = maps_weather['city'];
if (city != null) {
final rawList = maps_weather['forecasts'] as List? ?? [];
final daylist =
rawList.map((e) => Map<String, dynamic>.from(e)).toList();
final now = daylist.isNotEmpty ? daylist[0] : <String, dynamic>{};
//天气
return buildWeatherContent(
context,
city,
now,
daylist,
);
}
} else if (meta!.keys.contains('card_travel')) {
final rawList = meta!['card_travel']['items'] as List? ?? [];
final travellist =

2
lib/modules/devices/controllers/devices_controller.dart

@ -48,7 +48,7 @@ class DevicesController extends GetxController {
void unbindearphone() async {
final response = await Api.unbindearphone({
"code": earphoneCode.value,
"code": earphoneLicense.value,
"earphonname": earphoneMac.value,
});
if (response != null) {

9
lib/modules/meeting/controllers/meeting_controller.dart

@ -23,7 +23,7 @@ import '../../../data/services/meeting/meeting_upload_service.dart';
import '../../../routes/app_routes.dart';
class MeetingController extends GetxController {
final _meetingUploadService = Get.find<MeetingUploadService>();
final meetingUploadService = Get.find<MeetingUploadService>();
final ScrollController listController = ScrollController();
final AudioPlayer _audioPlayer = AudioPlayer();
@ -245,7 +245,7 @@ class MeetingController extends GetxController {
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
// 上传OSS
_meetingUploadService.addUpload(meetingId, newPath, 'LocalAudio');
meetingUploadService.addUpload(meetingId, newPath, 'LocalAudio');
// 更新UI数据
dataList.insert(0, {
@ -293,7 +293,7 @@ class MeetingController extends GetxController {
};
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
_meetingUploadService.addUpload(meetingId, filePath, 'TranslatAudio');
meetingUploadService.addUpload(meetingId, filePath, 'TranslatAudio');
dataList.insert(0, {
'id': meetingId,
@ -356,7 +356,7 @@ class MeetingController extends GetxController {
};
final meetingId = await SqfliteApi.insertMeeting(data);
if (meetingId != null) {
_meetingUploadService.addUpload(meetingId, filePath, 'ExternalAudio');
meetingUploadService.addUpload(meetingId, filePath, 'ExternalAudio');
dataList.insert(0, {
'id': meetingId,
@ -400,6 +400,7 @@ class MeetingController extends GetxController {
// 删除数据库中的记录
SqfliteApi.deleteMeeting(id);
_deleteFile(filepath);
meetingUploadService.removeUpload(id, isDeleteTask: true);
addQuantity--;
}

3
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -19,6 +19,7 @@ import '../../../core/utils/upload_oss.dart';
import '../../../data/models/appconfig.dart';
import '../../../data/services/db/sqflite_api.dart';
import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/meeting/meeting_upload_service.dart';
import '../model/meeting_details_model.dart';
import '../model/meeting_model.dart';
import 'meeting_controller.dart';
@ -30,6 +31,7 @@ class MeetingDetailsController extends GetxController
late AnimationController lottieController;
final _meetingUploadService = Get.find<MeetingUploadService>();
final _meetingController = Get.find<MeetingController>();
final PlayerController playerController = PlayerController();
@ -141,6 +143,7 @@ class MeetingDetailsController extends GetxController
_meetingController.dataListTask,
isAdd: false,
);
_meetingUploadService.removeUpload(id);
}
void _getAgent() async {

31
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -66,6 +66,7 @@ class MeetingRecordController extends GetxController
_initializeComponents();
_setupFromArguments();
generateWaveData();
_reinitializeAsrService();
}
@override
@ -74,6 +75,18 @@ class MeetingRecordController extends GetxController
super.onClose();
}
// 重新初始化ASR服务以支持新的语言
Future<void> _reinitializeAsrService() async {
try {
// 重新初始化ASR服务
await _asrService.initialize();
Logger.info('ASR服务重新初始化成功');
} catch (e) {
Logger.error('重新初始化ASR服务失败: ${e.toString()}');
}
}
// 请求录音权限
Future<bool> _requestRecordPermission() async {
try {
@ -132,6 +145,7 @@ class MeetingRecordController extends GetxController
void _cleanupResources() {
_timer?.cancel();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
titleEditingController.dispose();
titleFocusNode.dispose();
if (audioType.value != 0) {
@ -170,7 +184,13 @@ class MeetingRecordController extends GetxController
}
/// Toggles visibility of transcribed text content
void toggleTextDisplay() => showTextContent.toggle();
Future<void> toggleTextDisplay() async {
final recognitionStream = await _asrService.recognizeCallback();
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
showTextContent.toggle();
}
/// Toggles important mark status
void toggleMarked() => isMarked.toggle();
@ -211,10 +231,7 @@ class MeetingRecordController extends GetxController
// Initialize speech recognition
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
_asrService.setContinue();
// Prepare storage directory
final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/");
@ -263,9 +280,7 @@ class MeetingRecordController extends GetxController
Future<void> _resumeRecording() async {
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
startTimer();
}

41
lib/modules/meeting/views/meeting_view.dart

@ -562,6 +562,47 @@ class MeetingView extends GetView<MeetingController> {
),
),
),
Obx(() {
Map uploadData =
controller.meetingUploadService.uploadList.firstWhere(
(element) => element['id'] == data.id,
orElse: () => {},
);
return uploadData.isNotEmpty
? Stack(
alignment: Alignment.center,
children: [
if (uploadData['id'] ==
controller.meetingUploadService.meetingId.value)
Obx(
() => SizedBox(
width: 20.w,
height: 20.w,
child: CircularProgressIndicator(
value: controller.meetingUploadService
.uploadProgress.value,
valueColor:
const AlwaysStoppedAnimation<Color>(
Colors.red),
strokeWidth: 1.w,
),
),
),
SizedBox(
width: 20.w,
height: 20.w,
child: Icon(
uploadData['audiourl'].isNotEmpty
? Icons.cloud_done
: Icons.cloud_upload,
color: Colors.blue,
size: 16.w,
),
),
],
)
: const SizedBox();
}),
if (data.tasktype == 1)
Padding(
padding: EdgeInsets.only(top: 5.w),

44
lib/modules/settings/views/settings_view.dart

@ -567,28 +567,28 @@ class SettingsView extends GetView<SettingsController> {
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// // BLE测试
// _buildSimpleNavigationSetting(
// title: 'opus解码测试',
// subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!,
// iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () {
// Get.toNamed(Routes.opusTest);
// },
// isDarkMode: isDarkMode,
// ),
// Divider(
// height: 1,
// color: isDarkMode
// ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]),
// BLE测试
_buildSimpleNavigationSetting(
title: 'opus解码测试',
subtitle: '测试opus解码',
icon: Icons.bluetooth_searching,
iconBgColor: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[100]!,
iconColor:
isDarkMode ? Colors.green[300]! : Colors.green[600]!,
titleColor: isDarkMode ? Colors.white : null,
subtitleColor: isDarkMode ? Colors.white70 : null,
onTap: () {
Get.toNamed(Routes.opusTest);
},
isDarkMode: isDarkMode,
),
Divider(
height: 1,
color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200]),
// BLE测试
// _buildSimpleNavigationSetting(

10
lib/modules/speech_test/controllers/speech_test_controller.dart

@ -76,7 +76,10 @@ class SpeechTestController extends GetxController {
// 监听TTS事件
_setupTtsEventListener();
// 获取识别事件流 - 这启动了异步识别过程
final stream = await _asrService.recognizeCallback();
// 设置事件监听 - 异步处理识别结果
_setupAsrEventListener(stream);
isInitialized.value = true;
ttsStatus.value = '准备就绪';
recognitionStatus.value = '准备就绪';
@ -346,9 +349,6 @@ class SpeechTestController extends GetxController {
// 获取识别事件流 - 这启动了异步识别过程
final stream =
await _asrService.startContinuousRecognition(_audioSourceType);
// 设置事件监听 - 异步处理识别结果
_setupAsrEventListener(stream);
} catch (e) {
recognitionStatus.value = '启动连续识别异常: $e';
Logger.error('启动连续识别异常: ${e.toString()}');
@ -413,4 +413,4 @@ class SpeechTestController extends GetxController {
super.onClose();
}
}
}

34
lib/modules/translation/controllers/translation_controller.dart

@ -122,7 +122,9 @@ class TranslationController extends GetxController {
// 监听识别结果
if (activeSpeaker.value != 0) {
isPlayback = false;
_asrService.setContinue();
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
lastActiveSpeaker.value = id;
} else {
if (fTFTranslationResult != '' && !isPlayback) {
@ -134,7 +136,7 @@ class TranslationController extends GetxController {
} else if ((translationHistory.isEmpty ||
!translationHistory.last.isIntermediate) &&
currentMode.value == 'faceToFace') {
_asrService.setPause();
_asrService.stopContinuousRecognition();
}
}
}
@ -305,6 +307,12 @@ class TranslationController extends GetxController {
await _asrService.initialize(
supportedLanguages: asrSupportedLanguages,
);
// 获取识别事件流 - 这启动了异步识别过程
var recognitionStream = await _asrService.recognizeCallback();
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
final bool storage = await _requestStoragePermission();
if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能");
@ -325,6 +333,7 @@ class TranslationController extends GetxController {
void onClose() {
stopRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
_voiceInteractionSubscription?.cancel();
_translationDebounceTimer?.cancel();
scrollController.dispose();
@ -425,22 +434,18 @@ class TranslationController extends GetxController {
_audioSourceType = false;
isTtsEnabled.value = true;
}
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
if (currentMode.value == 'faceToFace') {
_asrService.setPause();
_asrService.stopContinuousRecognition();
} else {
await _ttsService.setAudioOutputDevice(0);
_asrService.setContinue();
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
}
isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) {
startRecording();
}
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
} catch (e) {
isRecognizing.value = false;
Logger.error('启动语音识别失败: ${e.toString()}');
@ -473,10 +478,9 @@ class TranslationController extends GetxController {
if (!isRecognizing.value) return;
try {
restoreOriginalAudioState();
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isRecognizing.value = false;
// 如果当前有中间结果,将其作为最终结果处理
@ -523,7 +527,7 @@ class TranslationController extends GetxController {
);
// 添加新项后滚动到底部
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause();
_asrService.stopContinuousRecognition();
} else {
translationHistory.add(newItem);
}
@ -563,7 +567,7 @@ class TranslationController extends GetxController {
_scrollToBottom();
saveTranslationHistory();
if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') {
_asrService.setPause();
_asrService.stopContinuousRecognition();
}
// 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp, isFinal: true);
@ -582,7 +586,7 @@ class TranslationController extends GetxController {
isIntermediate: false,
);
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause();
_asrService.stopContinuousRecognition();
} //else {
translationHistory.add(newItem);
translationHistory.refresh();

676
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

File diff suppressed because it is too large

86
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -15,7 +15,8 @@ import kotlinx.coroutines.launch
import android.content.Intent
import android.os.Build
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, AgentServiceListener {
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler,
AgentServiceListener {
private val TAG = "AgentServicePlugin"
private lateinit var context: Context
private lateinit var methodChannel: MethodChannel
@ -24,12 +25,16 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service")
methodChannel =
MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service")
methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service/events")
eventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
"com.yunqiinnovation.agent_service/events"
)
eventChannel.setStreamHandler(this)
// 添加监听器
AgentService.addListener(this)
@ -45,37 +50,42 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INVALID_ARGUMENTS", "配置参数不能为空", null)
return
}
Log.d(TAG, "启动BleMediaService,配置参数: $config")
// 创建Intent启动BleMediaService
val intent = Intent(context, BleMediaService::class.java)
// 将参数添加到Intent
config["azureSpeechKey"]?.toString()?.let { intent.putExtra("azureSpeechKey", it) }
config["azureSpeechRegion"]?.toString()?.let { intent.putExtra("azureSpeechRegion", it) }
config["azureSpeechKey"]?.toString()
?.let { intent.putExtra("azureSpeechKey", it) }
config["azureSpeechRegion"]?.toString()
?.let { intent.putExtra("azureSpeechRegion", it) }
config["openaiApiKey"]?.toString()?.let { intent.putExtra("openaiApiKey", it) }
config["openaiBaseUrl"]?.toString()?.let { intent.putExtra("openaiBaseUrl", it) }
config["openaiBaseUrl"]?.toString()
?.let { intent.putExtra("openaiBaseUrl", it) }
config["openaiModel"]?.toString()?.let { intent.putExtra("openaiModel", it) }
config["openaivisionModel"]?.toString()?.let { intent.putExtra("openaivisionModel", it) }
config["openaivisionModel"]?.toString()
?.let { intent.putExtra("openaivisionModel", it) }
config["systemPrompt"]?.toString()?.let { intent.putExtra("systemPrompt", it) }
config["mcpServer"]?.toString()?.let { intent.putExtra("mcpServer", it) }
config["volcanoAppId"]?.toString()?.let { intent.putExtra("volcanoAppId", it) }
config["volcanoToken"]?.toString()?.let { intent.putExtra("volcanoToken", it) }
// 启动前台服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "启动AgentService失败", e)
result.error("SERVICE_START_ERROR", "启动AgentService失败: ${e.message}", null)
}
}
"stopAgentService" -> {
try {
val intent = Intent(context, BleMediaService::class.java)
@ -86,6 +96,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("SERVICE_STOP_ERROR", "停止AgentService失败: ${e.message}", null)
}
}
"startConversation" -> {
try {
val isStarted = AgentService.startRecognition(false)
@ -95,6 +106,11 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("START_ERROR", "启动对话失败: ${e.message}", null)
}
}
"recognizeCallback" -> {
val isStarted = AgentService.recognizeCallback()
result.success(isStarted)
}
"stopConversation" -> {
try {
AgentService.stopRecognition()
@ -104,45 +120,29 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_ERROR", "停止对话失败: ${e.message}", null)
}
}
"audioContinue" -> {
try {
AgentService.audioContinue()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
}
"audioPause" -> {
try {
AgentService.audioPause()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
}
"processTextInput" -> {
val text = call.argument<String>("text")
if (text.isNullOrEmpty()) {
result.error("INVALID_ARGUMENTS", "文本不能为空", null)
return
}
val speakResponse = call.argument<Boolean>("speakResponse") ?: false
try {
val isProcessing = AgentService.processTextInput(
text = text,
speakResponse = speakResponse
)
result.success(isProcessing)
} catch (e: Exception) {
Log.e(TAG, "处理文本输入失败", e)
result.error("TEXT_PROCESS_ERROR", "处理文本输入失败: ${e.message}", null)
}
}
"processImageInput" -> {
val imagePath = call.argument<String>("imagePath")
if (imagePath.isNullOrEmpty()) {
@ -156,7 +156,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
}
val text = call.argument<String>("text") ?: ""
val speakResponse = call.argument<Boolean>("speakResponse") ?: false
try {
val isProcessing = AgentService.processImageInput(
imagePath = imagePath,
@ -164,20 +164,21 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
text = text,
speakResponse = speakResponse
)
result.success(isProcessing)
} catch (e: Exception) {
Log.e(TAG, "处理图片输入失败", e)
result.error("IMAGE_PROCESS_ERROR", "处理图片输入失败: ${e.message}", null)
}
}
"speakText" -> {
val text = call.argument<String>("text")
if (text.isNullOrEmpty()) {
result.error("INVALID_ARGUMENTS", "文本不能为空", null)
return
}
try {
AgentService.speakText(text)
result.success(true)
@ -186,6 +187,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("TTS_ERROR", "语音合成失败: ${e.message}", null)
}
}
"stopTts" -> {
try {
AgentService.stopTts()
@ -195,6 +197,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null)
}
}
"stopAiSteam" -> {
try {
AgentService.stopAiStream()
@ -215,6 +218,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("CLEAR_HISTORY_ERROR", "清除聊天历史失败: ${e.message}", null)
}
}
"interruptCurrentResponse" -> {
try {
AgentService.interruptCurrentResponse()
@ -224,6 +228,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INTERRUPT_ERROR", "中断响应失败: ${e.message}", null)
}
}
"dispose" -> {
try {
AgentService.dispose()
@ -233,6 +238,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("DISPOSE_ERROR", "释放资源失败: ${e.message}", null)
}
}
else -> {
result.notImplemented()
}
@ -246,7 +252,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
override fun onCancel(arguments: Any?) {
this.eventSink = null
}
/**
* 实现AgentServiceListener接口
* 接收来自AgentService的事件,并转发到Flutter
@ -257,7 +263,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
put("event", eventName)
put("data", JSONObject(data))
}
// 在主线程发送事件到Flutter
MainScope().launch {
eventSink?.success(eventData.toString())

22
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt

@ -65,7 +65,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
Log.e(TAG, "语音代理未初始化")
return false
}
Log.d(TAG, "手动启动语音识别: ")
return AgentService.startRecognition(true)
}
@ -73,7 +73,12 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 停止语音识别
*/
fun stopListening() {
AgentService.stopRecognition()
if (!isInitialized) {
Log.e(TAG, "语音代理未初始化")
return
}
AgentService.stopRecognition()
return
}
/**
@ -121,7 +126,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
} else if (state == BleConst.STATE_DISCONNECTED) {
Log.d(TAG, "Ble设备已断开,停止语音服务")
// 设备断开连接,停止语音服务
// stopListening()
stopListening()
}
}
@ -136,18 +141,19 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 在收到唤醒信号时启动语音识别
*/
override fun onWakeupSignalReceived() {
Log.d(TAG, "收到唤醒信号,启动语音识别")
Log.d(TAG, "收到唤醒信号,启动语音识别${AgentService.isRecognitionActive}")
// 停止当前TTS,避免冲突
AgentService.interruptCurrentResponse()
// AgentService.speakText("我在!")
// 启动语音识别
if (AgentService.isRecognitionActive&&AgentService.azureAsrHelper!!.isPaused) {
// 如果已经在识别,则重新启动
stopListening()
}
//stopListening()
startListening()
//stopListening()
startListening()
}
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {

53
local_plugins/agent_service/lib/agent_service.dart

@ -248,16 +248,16 @@ class AgentService {
}
}
/// 停止对话
/// 设备识别器
///
/// 停止语音识别
/// 返回是否成功停止对话
static Future<bool> stopConversation() async {
/// 启动语音识别,开始监听用户语音输入
/// 返回是否成功开始对话
static Future<bool> recognizeCallback() async {
try {
final bool result = await _channel.invokeMethod('stopConversation');
final bool result = await _channel.invokeMethod('recognizeCallback');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
throw AgentServiceException(e.code, e.message ?? '设置识别器失败', e.details);
}
}
@ -265,27 +265,40 @@ class AgentService {
///
/// 停止语音识别
/// 返回是否成功停止对话
static Future<bool> audioContinue() async {
static Future<bool> stopConversation() async {
try {
final bool result = await _channel.invokeMethod('audioContinue');
final bool result = await _channel.invokeMethod('stopConversation');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
}
}
/// 停止对话
///
/// 停止语音识别
/// 返回是否成功停止对话
static Future<bool> audioPause() async {
try {
final bool result = await _channel.invokeMethod('audioPause');
return result;
} on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
}
}
// /// 停止对话
// ///
// /// 停止语音识别
// /// 返回是否成功停止对话
// static Future<bool> audioContinue() async {
// try {
// final bool result = await _channel.invokeMethod('audioContinue');
// return result;
// } on PlatformException catch (e) {
// throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
// }
// }
// /// 停止对话
// ///
// /// 停止语音识别
// /// 返回是否成功停止对话
// static Future<bool> audioPause() async {
// try {
// final bool result = await _channel.invokeMethod('audioPause');
// return result;
// } on PlatformException catch (e) {
// throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
// }
// }
/// 处理文本输入
///

824
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

File diff suppressed because it is too large

128
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -255,24 +255,15 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
}
}
"setPause" -> {
azureAsrHelper.audioPause();
return
}
"setcontinue" -> {
azureAsrHelper.audioContinue();
return
}
"disableBluetoothAudio" -> {
azureAsrHelper.disableBluetoothAudio();
return
return
}
"restoreOriginalAudioState" -> {
azureAsrHelper.restoreOriginalAudioState();
return
return
}
"startContinuousRecognition" -> {
@ -296,62 +287,70 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
val success = azureAsrHelper.startContinuousRecognition(object :
AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(
mapOf(
"type" to "result",
"text" to text,
"detectedLanguage" to detectedLanguage
)
val success = azureAsrHelper.startContinuousRecognition(audioSourceType)
result.success(true)
} catch (e: Exception) {
result.error("START_RECOGNITION_ERROR", e.message, null)
}
}
"recognizeCallback" -> {
val success = azureAsrHelper.setupEventListeners(object :
AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(
mapOf(
"type" to "result",
"text" to text,
"detectedLanguage" to detectedLanguage
)
}
)
}
override fun onRecognizing(
recognizing: String,
detectedLanguage: String
) {
sendAsrEvent(
mapOf(
"type" to "recognizing",
"text" to recognizing,
"detectedLanguage" to detectedLanguage
)
override fun onRecognizing(
recognizing: String,
detectedLanguage: String
) {
sendAsrEvent(
mapOf(
"type" to "recognizing",
"text" to recognizing,
"detectedLanguage" to detectedLanguage
)
}
)
}
override fun onSessionStarted() {
sendAsrEvent(mapOf("type" to "sessionStarted"))
}
override fun onSessionStarted() {
sendAsrEvent(mapOf("type" to "sessionStarted"))
}
override fun onSessionStopped() {
sendAsrEvent(mapOf("type" to "sessionStopped"))
}
override fun onSessionStopped() {
sendAsrEvent(mapOf("type" to "sessionStopped"))
}
override fun onCanceled(reason: String, errorDetails: String) {
sendAsrEvent(
mapOf(
"type" to "canceled",
"reason" to reason,
"errorDetails" to errorDetails
)
override fun onCanceled(reason: String, errorDetails: String) {
sendAsrEvent(
mapOf(
"type" to "canceled",
"reason" to reason,
"errorDetails" to errorDetails
)
}
)
}
override fun onError(error: String) {
sendAsrEvent(
mapOf(
"type" to "error",
"message" to error
)
override fun onError(error: String) {
sendAsrEvent(
mapOf(
"type" to "error",
"message" to error
)
}
}, audioSourceType)
result.success(success)
} catch (e: Exception) {
result.error("START_RECOGNITION_ERROR", e.message, null)
}
)
}
})
FileLogger.d(tag, "recognizeCallback: ${success}") //
result.success(success)
}
"stopContinuousRecognition" -> {
@ -378,7 +377,8 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"音频数据不能为空",
null
)
azureAsrHelper.pushAudioData(data)
azureAsrHelper.audioStream?.saveAudioDataTo(data)
result.success(true)
}
@ -525,16 +525,16 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"setAudioOutputDevice" -> {
val type = call.argument<Int>("type")
var success =false
var success = false
if (type == 0) {
// 默认(如果有耳机选耳机,否则使用系统扬声器)
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
} else if (type == 1) {
// 强制使用声器
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
} else if (type == 2) {
// 强制使用耳机
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
}
@ -583,7 +583,7 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
override fun onAudioDataReceived(data: ByteArray) {
if (azureAsrHelper.audioSourceType == AzureAsrHelper.AudioSourceType.EXTERNAL) {
azureAsrHelper.pushAudioData(data)
azureAsrHelper.audioStream?.saveAudioDataTo(data)
}
// 可选:处理音频数据
}

3
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt

@ -64,7 +64,7 @@ object RecordFile {
writeThread = Thread {
try {
while (isWriting.get() || writeQueue.isNotEmpty()) {
val data = writeQueue.poll() ?: continue
val data = writeQueue.poll() ?: continue
fos?.write(data)
}
} catch (e: Exception) {
@ -248,6 +248,7 @@ object RecordFile {
} finally {
fos = null
currentAudioFile = null
writeThread = null
}
return false
}

55
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -1,5 +1,7 @@
package com.yunqiinnovation.ble_service
import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.*
import android.bluetooth.le.*
import android.content.Context
@ -22,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue
import android.os.Handler
import java.util.concurrent.atomic.AtomicBoolean
import android.util.Log
import androidx.annotation.RequiresPermission
/**
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能
@ -61,7 +64,7 @@ object BleService {
// 上下文
private lateinit var context: Context
private val commandQueue: Queue<ByteArray> = LinkedList()
// 蓝牙相关组件
private lateinit var bluetoothManager: BluetoothManager
private var bluetoothAdapter: BluetoothAdapter? = null
@ -83,6 +86,10 @@ object BleService {
// 是否是ota模式
var isEnterOta = false
// 是否回复指令
var isReply = true
// 是否回复指令码
var cmdReplyType =0xff
// 连接状态
val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED)
@ -93,9 +100,6 @@ object BleService {
private var opusManager: OpusManager? = null
private val mainHandler = Handler(Looper.getMainLooper())
// ota解码器实例
// var otaManager: OTAManager? = null
// 初始化状态
private var isInitialized = false
@ -663,6 +667,7 @@ object BleService {
private fun processAudioData(data: ByteArray) {
try {
if (opusManager?.isDecodeStream == true) {
recordfile?.saveAudioDataToWav(data)
opusManager?.writeAudioStream(data)
} else {
@ -677,6 +682,8 @@ object BleService {
/**
* 处理设备响应数据 - 帧头为0xBB
*/
@SuppressLint("StaticFieldLeak")
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun processDeviceResponse(data: ByteArray) {
Log.d(
TAG,
@ -741,7 +748,21 @@ object BleService {
}
}"
)
isReply=true;
cmdReplyType=cmdType;
if(!commandQueue.isEmpty())
{
val nextCmd = commandQueue.poll()
writeChar!!.value=nextCmd
var isSeccess = bluetoothGatt?.writeCharacteristic(writeChar)
if(isSeccess==true)
{
Log.d(
TAG,
"收到设备响应:"
)
}
}
// 根据命令类型处理不同的响应
when (cmdType) {
BleConst.CMD_GET_VERSION -> {
@ -1005,7 +1026,7 @@ object BleService {
BleConst.CMD_WAKE_UP -> {
Log.i(TAG, "收到唤醒指令")
// 接收到唤醒信号,打开编码器 (设备侧)
openEncoder()
notifyWakeupSignalReceived()
}
@ -1316,6 +1337,7 @@ object BleService {
* @param cmdData 命令数据
* @return 是否成功发送
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
if (!checkConn()) {
Log.e(TAG, "发送命令失败: 设备未连接")
@ -1358,7 +1380,26 @@ object BleService {
// 发送数据
writeChar!!.value = cmdPacket
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false
// 加入队列并触发发送
if(isReply)
{
var isSeccess = bluetoothGatt?.writeCharacteristic(writeChar)
if(isSeccess == true)
{
isReply=false;
cmdType;
return true
}
else
return false
}
else
{
commandQueue.offer(cmdPacket)
}
return true
} catch (e: Exception) {
Log.e(TAG, "发送命令异常: ${e.message}", e)
return false

Loading…
Cancel
Save