Browse Source

v005:

newdev_shunjiawei
wolfplus 2 years ago
parent
commit
e741032e36
  1. 3
      android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt
  2. 26
      android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
  3. 97
      android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt
  4. 4
      lib/core/bindings/initial_binding.dart
  5. 5
      lib/core/routes/app_pages.dart
  6. 2
      lib/core/routes/app_routes.dart
  7. 3
      lib/data/services/azure_asr_service.dart
  8. 1
      lib/data/services/azure_tts_service.dart
  9. 400
      lib/data/services/voice_interaction_service.dart
  10. 11
      lib/main.dart
  11. 94
      lib/modules/chat/controllers/chat_controller.dart
  12. 3
      lib/modules/explore/controllers/explore_controller.dart
  13. 3
      lib/modules/home/controllers/home_controller.dart
  14. 6
      lib/modules/translation/controllers/translation_controller.dart

3
android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt

@ -244,7 +244,8 @@ class AzureTtsHelper {
try { try {
synthesizer?.StopSpeakingAsync() synthesizer?.StopSpeakingAsync()
Log.d(TAG, "已停止语音合成")
Log.d(TAG, "已停止语音合成kt")
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "停止语音合成失败: ${e.message}") Log.e(TAG, "停止语音合成失败: ${e.message}")

26
android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt

@ -72,13 +72,30 @@ class MainActivity: FlutterActivity() {
var azureSpeechKey: String = "2K2VOHSuL8ZSEnbKgHSyTwo1ohj7C2gbxyVimsjpVUt6aETxVwlvJQQJ99BCACqBBLyXJ3w3AAAYACOGqCMj" var azureSpeechKey: String = "2K2VOHSuL8ZSEnbKgHSyTwo1ohj7C2gbxyVimsjpVUt6aETxVwlvJQQJ99BCACqBBLyXJ3w3AAAYACOGqCMj"
var azureSpeechRegion: String = "southeastasia" var azureSpeechRegion: String = "southeastasia"
var volcanoAiApiKey: String = "0fd7c93b-b9bf-4b0b-bca6-e284491792bf" var volcanoAiApiKey: String = "0fd7c93b-b9bf-4b0b-bca6-e284491792bf"
// 标记是否已初始化
var isAzureHelpersInitialized = false
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// 初始化本地的 Azure 语音服务实例
Log.d(TAG, "初始化本地的 Azure 语音服务实例")
azureAsrHelper = AzureAsrHelper() azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper() azureTtsHelper = AzureTtsHelper()
// 初始化 Azure 服务
// if (azureSpeechKey.isNotEmpty() && azureSpeechRegion.isNotEmpty()) {
// Log.d(TAG, "初始化本地实例的 Azure 服务配置")
// // 初始化ASR
// azureAsrHelper.initialize(azureSpeechKey, azureSpeechRegion, "zh-CN")
// // 初始化TTS
// azureTtsHelper.initialize(azureSpeechKey, azureSpeechRegion, "zh-CN")
// } else {
// Log.e(TAG, "Azure配置信息不完整,无法初始化本地实例的Azure服务")
// }
// 注册广播接收器 // 注册广播接收器
registerVoiceInteractionReceiver() registerVoiceInteractionReceiver()
@ -480,6 +497,13 @@ class MainActivity: FlutterActivity() {
Log.d(TAG, "配置参数: key=${if (key.isNotEmpty()) key.take(5) + "..." else "空"}, region=$region, aiKey=${if (aiKey.isNotEmpty()) aiKey.take(5) + "..." else "空"}") Log.d(TAG, "配置参数: key=${if (key.isNotEmpty()) key.take(5) + "..." else "空"}, region=$region, aiKey=${if (aiKey.isNotEmpty()) aiKey.take(5) + "..." else "空"}")
// 如果配置发生变化,需要重新初始化本地实例
// if (key.isNotEmpty() && region.isNotEmpty()) {
// Log.d(TAG, "配置已更新,重新初始化本地实例")
// azureAsrHelper.initialize(key, region, "zh-CN")
// azureTtsHelper.initialize(key, region, "zh-CN")
// }
// 创建 Intent 并启动服务 // 创建 Intent 并启动服务
val serviceIntent = Intent(this, VoiceInteractionService::class.java) val serviceIntent = Intent(this, VoiceInteractionService::class.java)
@ -618,8 +642,10 @@ class MainActivity: FlutterActivity() {
Log.e(TAG, "注销广播接收器失败: ${e.message}") Log.e(TAG, "注销广播接收器失败: ${e.message}")
} }
// 在这里释放本地实例
azureAsrHelper.dispose() azureAsrHelper.dispose()
azureTtsHelper.dispose() azureTtsHelper.dispose()
super.onDestroy() super.onDestroy()
} }
} }

97
android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt

@ -90,6 +90,7 @@ class VoiceInteractionService : Service() {
请保持回答简短、准确,避免过长的解释。 请保持回答简短、准确,避免过长的解释。
如果用户的问题不清楚,请礼貌地请求澄清。 如果用户的问题不清楚,请礼貌地请求澄清。
不要使用复杂的术语,除非用户明确要求。 不要使用复杂的术语,除非用户明确要求。
用户用语音和你交互.
""".trimIndent() """.trimIndent()
override fun onCreate() { override fun onCreate() {
@ -139,7 +140,8 @@ class VoiceInteractionService : Service() {
* 初始化所有服务 * 初始化所有服务
*/ */
private fun initServices() { private fun initServices() {
// 初始化Azure服务 // 创建新的Azure服务实例
Log.d(TAG, "创建新的Azure服务实例")
azureAsrHelper = AzureAsrHelper() azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper() azureTtsHelper = AzureTtsHelper()
@ -185,6 +187,8 @@ class VoiceInteractionService : Service() {
mediaSession.setCallback(object : MediaSessionCompat.Callback() { mediaSession.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent") Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent")
handleDoubleClick()
return true
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
@ -328,6 +332,14 @@ class VoiceInteractionService : Service() {
private fun handleDoubleClick() { private fun handleDoubleClick() {
Log.d(TAG, "处理双击事件") Log.d(TAG, "处理双击事件")
//屏蔽短时间重复响应的问题
val currentTime = System.currentTimeMillis()
val timeDiff = currentTime - lastKeyEventTime
if (timeDiff < 500) {
Log.d(TAG, "短时间重复响应,忽略")
return
}
// 停止当前TTS播放 // 停止当前TTS播放
stopCurrentTTS() stopCurrentTTS()
@ -362,7 +374,9 @@ class VoiceInteractionService : Service() {
playNotification("需要录音权限,请在设置中授予权限") playNotification("需要录音权限,请在设置中授予权限")
return return
} }
// 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
isActive = true isActive = true
isRecognitionActive = true isRecognitionActive = true
hasSpeechDetected = false hasSpeechDetected = false
@ -390,34 +404,33 @@ class VoiceInteractionService : Service() {
override fun onSessionStarted() { override fun onSessionStarted() {
updateLastActivityTime() updateLastActivityTime()
// 通知 Flutter 语音识别已启动
notifyRecognitionStarted()
} }
override fun onSessionStopped() { override fun onSessionStopped() {
isRecognitionActive = false isRecognitionActive = false
// 如果不是因超时暂停,尝试重新启动 // // 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) { // if (isActive && !isTimeoutPaused) {
handler.postDelayed({ // handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) { // if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition() // startVoiceRecognition()
} // }
}, 1000) // }, 1000)
} // }
} }
override fun onCanceled(reason: String, errorDetails: String) { override fun onCanceled(reason: String, errorDetails: String) {
isRecognitionActive = false isRecognitionActive = false
// 如果不是因超时暂停,尝试重新启动 // // 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) { // if (isActive && !isTimeoutPaused) {
handler.postDelayed({ // handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) { // if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition() // startVoiceRecognition()
} // }
}, 1000) // }, 1000)
} // }
} }
override fun onError(error: String) { override fun onError(error: String) {
@ -425,13 +438,13 @@ class VoiceInteractionService : Service() {
playNotification("语音识别出错") playNotification("语音识别出错")
// 如果不是因超时暂停,尝试重新启动 // 如果不是因超时暂停,尝试重新启动
if (isActive && !isTimeoutPaused) { // if (isActive && !isTimeoutPaused) {
handler.postDelayed({ // handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) { // if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition() // startVoiceRecognition()
} // }
}, 2000) // }, 2000)
} // }
} }
}) })
} catch (e: Exception) { } catch (e: Exception) {
@ -439,11 +452,11 @@ class VoiceInteractionService : Service() {
playNotification("启动语音识别失败") playNotification("启动语音识别失败")
// 延迟重试 // 延迟重试
handler.postDelayed({ // handler.postDelayed({
if (isActive && !isRecognitionActive && !isTtsSpeaking) { // if (isActive && !isRecognitionActive && !isTtsSpeaking) {
startVoiceRecognition() // startVoiceRecognition()
} // }
}, 3000) // }, 3000)
} }
} }
@ -725,22 +738,30 @@ class VoiceInteractionService : Service() {
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "onDestroy - 语音交互服务正在销毁") Log.d(TAG, "onDestroy - 语音交互服务正在销毁")
super.onDestroy()
// 停止监控 // 停止监控
stopMonitoring() stopMonitoring()
// 取消注册媒体按钮接收器 // 停止语音识别
val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name) if (isRecognitionActive) {
audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver) stopVoiceRecognition()
}
// 停止TTS
stopCurrentTTS()
// 释放资源 // 释放 MediaSession
mediaSession.isActive = false
mediaSession.release() mediaSession.release()
// 释放Azure服务实例
Log.d(TAG, "释放Azure服务实例")
azureAsrHelper.dispose() azureAsrHelper.dispose()
azureTtsHelper.dispose() azureTtsHelper.dispose()
// 更新服务状态
isRunning.set(false) isRunning.set(false)
super.onDestroy()
} }
/** /**

4
lib/core/bindings/initial_binding.dart

@ -22,8 +22,8 @@ class InitialBinding extends Bindings {
// 火山翻译服务 // 火山翻译服务
Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), fenix: true); Get.lazyPut<VolcanoTranslationService>(() => VolcanoTranslationService(), fenix: true);
// 语音交互服务 - 使用 putAsync 确保 init 方法被调用 // 注意:VoiceInteractionService 在 HomeController 中初始化
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true); Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true);
} }
} }

5
lib/core/routes/app_pages.dart

@ -26,6 +26,11 @@ import './app_routes.dart';
abstract class AppPages { abstract class AppPages {
static final pages = [ static final pages = [
GetPage(
name: Routes.initial,
page: () => const HomeView(),
binding: HomeBinding(),
),
GetPage( GetPage(
name: Routes.home, name: Routes.home,
page: () => const HomeView(), page: () => const HomeView(),

2
lib/core/routes/app_routes.dart

@ -1,5 +1,5 @@
abstract class Routes { abstract class Routes {
static const initial = '/'; static const initial = '/home';
static const home = '/home'; static const home = '/home';
static const explore = '/explore'; static const explore = '/explore';
static const chat = '/chat'; static const chat = '/chat';

3
lib/data/services/azure_asr_service.dart

@ -2,11 +2,12 @@ import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import 'package:get/get.dart';
/// 微软语音识别服务 /// 微软语音识别服务
/// ///
/// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口 /// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口
class AzureAsrService { class AzureAsrService {
static final AzureAsrService to = Get.put(AzureAsrService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_asr'); static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_asr');
static const EventChannel _eventChannel = EventChannel('com.deep_voice.azure_asr_events'); static const EventChannel _eventChannel = EventChannel('com.deep_voice.azure_asr_events');

1
lib/data/services/azure_tts_service.dart

@ -18,6 +18,7 @@ class AzureTtsException implements Exception {
/// 该服务通过平台通道与 Android 上的 Microsoft Speech SDK 交互, /// 该服务通过平台通道与 Android 上的 Microsoft Speech SDK 交互,
/// 提供文本转语音功能。 /// 提供文本转语音功能。
class AzureTtsService extends GetxService { class AzureTtsService extends GetxService {
static final AzureTtsService to = Get.put(AzureTtsService());
static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_tts'); static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_tts');
bool _isInitialized = false; bool _isInitialized = false;

400
lib/data/services/voice_interaction_service.dart

@ -1,49 +1,48 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../core/utils/logger.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../services/azure_asr_service.dart';
import '../services/azure_tts_service.dart';
import '../services/chat_history_service.dart';
import '../../modules/chat/models/message_model.dart'; import '../../modules/chat/models/message_model.dart';
import '../../core/utils/logger.dart'; import 'chat_history_service.dart';
import '../../data/services/azure_asr_service.dart';
import '../../data/services/azure_tts_service.dart';
/// 语音交互服务,用于管理原生的 VoiceInteractionService /// 语音交互服务
///
/// 该服务提供了与Android端的VoiceInteractionService.kt通信的接口,
/// 用于管理后台语音交互服务的生命周期和接收语音交互事件
class VoiceInteractionService extends GetxService { class VoiceInteractionService extends GetxService {
static VoiceInteractionService get to => Get.find(); static VoiceInteractionService get to => Get.find();
// 方法通道和事件通道
// 服务运行状态
final _isRunning = false.obs;
bool get isRunning => _isRunning.value;
// 语音识别状态
final _isRecognitionActive = false.obs;
bool get isRecognitionActive => _isRecognitionActive.value;
// 聊天记录 - 可被 ChatView 监听
final RxList<Message> personalAssistantMessages = <Message>[].obs;
// 方法通道
static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction'); static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction');
// 事件通道
static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events'); static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events');
// 配置信息 // 服务状态
late final String _azureSpeechKey; final _isServiceRunning = false.obs;
late final String _azureSpeechRegion; bool get isServiceRunning => _isServiceRunning.value;
late final String _volcanoAiApiKey;
// 事件流控制器
StreamController<VoiceInteractionEvent>? _eventStreamController;
Stream<VoiceInteractionEvent>? _eventStream;
Stream<VoiceInteractionEvent>? get eventStream => _eventStream;
// 事件通道状态 // 事件通道状态
bool _isEventChannelReady = false; bool _isEventChannelReady = false;
Completer<void>? _eventChannelReadyCompleter; Completer<void>? _eventChannelReadyCompleter;
StreamSubscription? _eventSubscription;
// 配置信息
late String _azureSpeechKey;
late String _azureSpeechRegion;
late String _volcanoAiApiKey;
// 初始化标记,避免重复初始化 // 初始化状态标志
bool _isInitialized = false; static bool _isInitialized = false;
/// 构造函数
VoiceInteractionService() { VoiceInteractionService() {
_loadConfig();
_setupEventChannel();
} }
/// 从环境变量加载配置 /// 从环境变量加载配置
@ -53,39 +52,44 @@ class VoiceInteractionService extends GetxService {
_volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? ''; _volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? '';
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) { if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) {
Logger.error('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
} }
if (_volcanoAiApiKey.isEmpty) { if (_volcanoAiApiKey.isEmpty) {
Logger.error('未找到火山 AI 配置。请在 .env 文件中设置 VOLCANO_AI_API_KEY'); Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY');
} }
} }
/// 设置事件通道监听 /// 设置事件通道
void _setupEventChannel() { void _setupEventChannel() {
_eventChannelReadyCompleter = Completer<void>(); _eventChannelReadyCompleter = Completer<void>();
_eventChannel.receiveBroadcastStream().listen( _eventSubscription = _eventChannel
(event) { .receiveBroadcastStream()
if (event is Map) { .listen((event) {
final String eventType = event['type'] as String? ?? ''; if (event is Map) {
final String eventType = event['type'] as String? ?? '';
// 处理通道准备好的事件
if (eventType == 'channelReady') { // 处理通道准备好的事件
_isEventChannelReady = true; if (eventType == 'channelReady') {
if (!_eventChannelReadyCompleter!.isCompleted) { _isEventChannelReady = true;
_eventChannelReadyCompleter!.complete(); if (!_eventChannelReadyCompleter!.isCompleted) {
_eventChannelReadyCompleter!.complete();
}
return;
} }
return;
_handleVoiceInteractionEvent(event);
} }
}, onError: (error) {
_handleEvent(event); Logger.error('语音交互事件通道错误: $error');
} });
}, }
onError: (error) {
Logger.error('语音交互事件通道错误: $error'); /// 创建事件流
} void _createEventStream() {
); _eventStreamController = StreamController<VoiceInteractionEvent>.broadcast();
_eventStream = _eventStreamController?.stream;
} }
/// 等待事件通道准备好 /// 等待事件通道准备好
@ -96,152 +100,67 @@ class VoiceInteractionService extends GetxService {
await _eventChannelReadyCompleter!.future.timeout(timeout); await _eventChannelReadyCompleter!.future.timeout(timeout);
return true; return true;
} on TimeoutException { } on TimeoutException {
Logger.error('等待事件通道准备好超时'); Logger.error('等待语音交互事件通道准备好超时');
return false; return false;
} }
} }
/// 初始化服务 /// 初始化服务
Future<VoiceInteractionService> init() async { Future<VoiceInteractionService> initialize() async {
// 如果已经初始化过,直接返回
if (_isInitialized) {
return this;
}
try { try {
// 等待事件通道准备好 if (_isInitialized) {
final bool eventChannelReady = await _waitForEventChannel();
if (!eventChannelReady) {
Logger.error('事件通道未准备好,初始化失败');
return this; return this;
} }
_isInitialized = true;
_loadConfig();
_setupEventChannel();
_createEventStream();
// 检查服务是否正在运行
await _checkServiceStatus();
// 获取服务运行状态 // 等待事件通道准备好
await _updateServiceStatus(); await _waitForEventChannel();
// 加载个人助手的历史记录
_loadPersonalAssistantHistory();
// 标记为已初始化 Logger.info('语音交互服务初始化完成');
_isInitialized = true;
return this; return this;
} catch (e) { } catch (e) {
Logger.error('VoiceInteractionService 初始化失败: $e'); Logger.error('语音交互服务初始化失败: $e');
return this; return this;
} }
} }
/// 加载个人助手的历史记录 /// 检查服务状态
void _loadPersonalAssistantHistory() { Future<void> _checkServiceStatus() async {
if (Get.isRegistered<ChatHistoryService>()) {
final historyService = Get.find<ChatHistoryService>();
final messages = historyService.loadHistory('personal_assistant');
if (messages.isNotEmpty) {
personalAssistantMessages.assignAll(messages);
}
}
}
/// 处理来自原生端的事件
void _handleEvent(dynamic event) {
try {
if (event is! Map) {
return;
}
final type = event['type'] as String?;
if (type == null) {
return;
}
switch (type) {
case 'recognitionStarted':
_isRecognitionActive.value = true;
// 检测到后台服务的语音识别开始运行,主动停止Flutter端的语音识别和TTS
// _stopFlutterVoiceServices();
break;
case 'chatHistory':
// 处理聊天记录
_handleChatHistory(event);
break;
}
} catch (e) {
Logger.error('处理原生端事件时出错: $e');
}
}
/// 处理聊天记录事件
void _handleChatHistory(Map<dynamic, dynamic> event) {
try { try {
final agentId = event['agentId'] as String?; final bool isRunning = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false;
final userMessage = event['userMessage'] as String?; _isServiceRunning.value = isRunning;
final assistantMessage = event['assistantMessage'] as String?; Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
final timestamp = event['timestamp'] as int?;
if (agentId == null || userMessage == null || assistantMessage == null || timestamp == null) {
return;
}
// 只处理个人助手的聊天记录
if (agentId != 'personal_assistant') {
return;
}
// 添加用户消息
personalAssistantMessages.add(Message(
role: 'user',
content: userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp),
));
// 添加助手消息
personalAssistantMessages.add(Message(
role: 'assistant',
content: assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1000), // 助手消息时间戳稍后
));
// 保存到历史记录
if (Get.isRegistered<ChatHistoryService>()) {
final historyService = Get.find<ChatHistoryService>();
historyService.saveHistory('personal_assistant', personalAssistantMessages);
}
} catch (e) { } catch (e) {
Logger.error('处理聊天记录事件失败: $e'); Logger.error('检查语音交互服务状态失败: $e');
} _isServiceRunning.value = false;
}
/// 停止 Flutter 端的语音服务
void _stopFlutterVoiceServices() {
// 停止 TTS
if (Get.isRegistered<AzureTtsService>()) {
Get.find<AzureTtsService>().stop();
}
// 停止 ASR
if (Get.isRegistered<AzureAsrService>()) {
Get.find<AzureAsrService>().stopContinuousRecognition();
} }
} }
/// 启动语音交互服务 /// 启动语音交互服务
Future<bool> startService() async { Future<bool> startService() async {
if (!_isInitialized) { if (_isServiceRunning.value) {
await init(); Logger.info('语音交互服务已经在运行');
return true;
} }
try { try {
// 启动服务 final bool result = await _channel.invokeMethod('startVoiceInteractionService', {
final result = await _channel.invokeMethod<bool>('startVoiceInteractionService', {
'azure_speech_key': _azureSpeechKey, 'azure_speech_key': _azureSpeechKey,
'azure_speech_region': _azureSpeechRegion, 'azure_speech_region': _azureSpeechRegion,
'volcano_ai_api_key': _volcanoAiApiKey, 'volcano_ai_api_key': _volcanoAiApiKey,
}) ?? false; }) ?? false;
// 更新状态
if (result) { if (result) {
await _updateServiceStatus(); _isServiceRunning.value = true;
Logger.info('语音交互服务启动成功');
} else {
Logger.error('语音交互服务启动失败');
} }
return result; return result;
@ -253,12 +172,19 @@ class VoiceInteractionService extends GetxService {
/// 停止语音交互服务 /// 停止语音交互服务
Future<bool> stopService() async { Future<bool> stopService() async {
if (!_isServiceRunning.value) {
Logger.info('语音交互服务未运行');
return true;
}
try { try {
final result = await _channel.invokeMethod<bool>('stopVoiceInteractionService') ?? false; final bool result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false;
// 更新状态
if (result) { if (result) {
await _updateServiceStatus(); _isServiceRunning.value = false;
Logger.info('语音交互服务停止成功');
} else {
Logger.error('语音交互服务停止失败');
} }
return result; return result;
@ -268,10 +194,22 @@ class VoiceInteractionService extends GetxService {
} }
} }
/// 暂停语音交互 /// 暂停语音交互(停止TTS和语音识别,但保持服务运行)
Future<bool> pauseVoiceInteraction() async { Future<bool> pauseVoiceInteraction() async {
if (!_isServiceRunning.value) {
Logger.info('语音交互服务未运行,无法暂停');
return false;
}
try { try {
final result = await _channel.invokeMethod<bool>('pauseVoiceInteraction') ?? false; final bool result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false;
if (result) {
Logger.info('语音交互暂停成功');
} else {
Logger.error('语音交互暂停失败');
}
return result; return result;
} catch (e) { } catch (e) {
Logger.error('暂停语音交互失败: $e'); Logger.error('暂停语音交互失败: $e');
@ -279,34 +217,116 @@ class VoiceInteractionService extends GetxService {
} }
} }
/// 更新服务运行状态 /// 处理来自原生端的语音交互事件
Future<void> _updateServiceStatus() async { void _handleVoiceInteractionEvent(dynamic event) {
try { if (event is! Map || _eventStreamController == null) return;
final status = await _channel.invokeMethod<bool>('isVoiceInteractionServiceRunning') ?? false;
_isRunning.value = status; final Map<dynamic, dynamic> eventMap = event;
} catch (e) { final String eventType = eventMap['type'] as String? ?? '';
Logger.error('获取语音交互服务状态失败: $e'); final int timestamp = eventMap['timestamp'] as int? ?? 0;
_isRunning.value = false;
// 添加时间戳日志,帮助调试
Logger.info('收到原生端事件: $eventType, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
switch (eventType) {
case 'chatHistory':
final String agentId = eventMap['agentId'] as String? ?? '';
final String userMessage = eventMap['userMessage'] as String? ?? '';
final String assistantMessage = eventMap['assistantMessage'] as String? ?? '';
_eventStreamController?.add(ChatHistoryEvent(
agentId: agentId,
userMessage: userMessage,
assistantMessage: assistantMessage,
timestamp: timestamp,
));
// 保存聊天记录
_saveChatHistory(agentId, userMessage, assistantMessage, timestamp);
Logger.info('收到聊天历史事件: agentId=$agentId');
break;
case 'recognitionStarted':
_eventStreamController?.add(RecognitionStartedEvent(
timestamp: timestamp,
));
Logger.info('收到语音识别开始事件');
break;
default:
Logger.warning('收到未知类型的语音交互事件: $eventType');
break;
} }
} }
/// 释放资源 @override
Future<void> dispose() async { void onClose() {
// 清理资源
_eventSubscription?.cancel();
_eventStreamController?.close();
super.onClose();
}
/// 保存聊天记录
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) {
try { try {
if (_isRunning.value) { // 获取ChatHistoryService实例
await stopService(); final chatHistoryService = Get.find<ChatHistoryService>();
}
// 创建用户消息和助手消息
final userMsg = Message(
role: 'user',
content: userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp),
);
final assistantMsg = Message(
role: 'assistant',
content: assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息
);
// 加载现有历史记录
final existingMessages = chatHistoryService.loadHistory(agentId);
// 添加新消息
existingMessages.addAll([userMsg, assistantMsg]);
_isInitialized = false; // 保存更新后的历史记录
chatHistoryService.saveHistory(agentId, existingMessages);
} catch (e) { } catch (e) {
Logger.error('释放语音交互服务资源失败: $e'); Logger.error('保存聊天记录失败: $e');
} }
} }
}
/// 语音交互事件基类
abstract class VoiceInteractionEvent {
final int timestamp;
@override VoiceInteractionEvent({required this.timestamp});
void onClose() { }
// 清理资源
dispose(); /// 聊天历史事件
super.onClose(); class ChatHistoryEvent extends VoiceInteractionEvent {
} final String agentId;
final String userMessage;
final String assistantMessage;
ChatHistoryEvent({
required this.agentId,
required this.userMessage,
required this.assistantMessage,
required int timestamp,
}) : super(timestamp: timestamp);
}
/// 语音识别开始事件
class RecognitionStartedEvent extends VoiceInteractionEvent {
RecognitionStartedEvent({
required int timestamp,
}) : super(timestamp: timestamp);
} }

11
lib/main.dart

@ -9,6 +9,7 @@ import 'core/routes/app_routes.dart';
import 'dart:io'; import 'dart:io';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
import 'app/translations/app_translations.dart'; import 'app/translations/app_translations.dart';
import 'data/services/voice_interaction_service.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -54,10 +55,10 @@ class MainApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ScreenUtilInit( return ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸 designSize: const Size(375, 812),
minTextAdapt: true, minTextAdapt: true,
splitScreenMode: true, splitScreenMode: true,
builder: (context, child) { builder: (_, child) {
return GetMaterialApp( return GetMaterialApp(
title: 'Deep Voice', title: 'Deep Voice',
theme: ThemeData( theme: ThemeData(
@ -65,14 +66,16 @@ class MainApp extends StatelessWidget {
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
), ),
initialBinding: InitialBinding(), initialBinding: InitialBinding(),
initialRoute: Routes.home, initialRoute: Routes.initial,
getPages: AppPages.pages, getPages: AppPages.pages,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
translations: AppTranslations(), translations: AppTranslations(),
locale: const Locale('zh', 'CN'), locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'), fallbackLocale: const Locale('en', 'US'),
// 注意:VoiceInteractionService 在 HomeController 的 onInit 方法中初始化
// 不要在这里重复初始化,以避免多次注册事件监听器
); );
} },
); );
} }
} }

94
lib/modules/chat/controllers/chat_controller.dart

@ -9,6 +9,7 @@ import '../../../data/providers/agent_provider.dart';
import '../../../data/models/agent.dart'; import '../../../data/models/agent.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/services/chat_history_service.dart'; import '../../../data/services/chat_history_service.dart';
import '../../../data/services/voice_interaction_service.dart';
class ChatController extends GetxController { class ChatController extends GetxController {
// 服务 // 服务
@ -16,6 +17,7 @@ class ChatController extends GetxController {
final AzureTtsService _ttsService = Get.find<AzureTtsService>(); final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final AzureAsrService _asrService = Get.find<AzureAsrService>(); final AzureAsrService _asrService = Get.find<AzureAsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>(); final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
// 添加ScrollController用于控制列表滚动 // 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
@ -46,6 +48,7 @@ class ChatController extends GetxController {
// 流式响应控制 // 流式响应控制
StreamSubscription? _aiResponseSubscription; StreamSubscription? _aiResponseSubscription;
StreamSubscription? _recognitionSubscription; StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
// 取消标志 // 取消标志
bool _shouldCancelAiResponse = false; bool _shouldCancelAiResponse = false;
@ -93,12 +96,101 @@ class ChatController extends GetxController {
startVoiceRecognition(); startVoiceRecognition();
} }
// 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents();
// 使用微任务在布局完成后自动滚动到底部 // 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
}); });
} }
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
_handleRecognitionStartedEvent();
} else if (event is ChatHistoryEvent) {
// 收到chatHistory事件时,更新聊天界面
_handleChatHistoryEvent(event);
}
});
}
}
/// 处理recognitionStarted事件
void _handleRecognitionStartedEvent() {
Logger.info('收到后台语音识别开始事件,停止当前AI交互');
// 停止当前AI交互
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应");
}
// 停止TTS
_ttsService.stop();
// 停止语音识别
if (isVoiceRecognitionActive.value) {
stopVoiceRecognition();
}
}
/// 处理chatHistory事件
void _handleChatHistoryEvent(ChatHistoryEvent event) {
Logger.info('收到后台聊天历史事件: agentId=${event.agentId}');
// 检查是否是当前Agent的消息
if (event.agentId != agent.id) {
Logger.info('收到的消息不属于当前Agent,忽略');
return;
}
// 创建用户消息和助手消息
final userMsg = Message(
role: 'user',
content: event.userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp),
);
final assistantMsg = Message(
role: 'assistant',
content: event.assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 确保助手消息时间戳晚于用户消息
);
// 检查是否已存在相同内容的消息(避免重复添加)
final bool userMsgExists = messages.any((m) =>
m.role == userMsg.role &&
m.content == userMsg.content &&
(m.timestamp.millisecondsSinceEpoch - userMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
final bool assistantMsgExists = messages.any((m) =>
m.role == assistantMsg.role &&
m.content == assistantMsg.content &&
(m.timestamp.millisecondsSinceEpoch - assistantMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
// 如果消息不存在,则添加到聊天记录中
if (!userMsgExists) {
messages.add(userMsg);
}
if (!assistantMsgExists) {
messages.add(assistantMsg);
}
// 如果有新消息添加,刷新列表并滚动到底部
if (!userMsgExists || !assistantMsgExists) {
messages.refresh();
}
_scrollToBottom();
}
@override @override
void onClose() { void onClose() {
Logger.info('ChatController onClose'); Logger.info('ChatController onClose');
@ -107,6 +199,7 @@ class ChatController extends GetxController {
scrollController.dispose(); scrollController.dispose();
_aiResponseSubscription?.cancel(); _aiResponseSubscription?.cancel();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_voiceInteractionSubscription?.cancel();
// 停止TTS // 停止TTS
_ttsService.stop(); _ttsService.stop();
@ -321,6 +414,7 @@ class ChatController extends GetxController {
Future<void> startVoiceRecognition() async { Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return; if (isVoiceRecognitionActive.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try { try {
// 初始化语音识别 // 初始化语音识别
await _asrService.initialize(language: 'zh-CN'); await _asrService.initialize(language: 'zh-CN');

3
lib/modules/explore/controllers/explore_controller.dart

@ -1,6 +1,6 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../../data/providers/agent_provider.dart'; import '../../../data/providers/agent_provider.dart';
import '../../../data/services/voice_interaction_service.dart';
class ChatItem { class ChatItem {
final String id; final String id;
@ -28,4 +28,5 @@ class ExploreController extends GetxController {
agents.map((agent) => ChatItem(id: agent.id)).toList(), agents.map((agent) => ChatItem(id: agent.id)).toList(),
); );
} }
} }

3
lib/modules/home/controllers/home_controller.dart

@ -2,6 +2,7 @@ import 'package:get/get.dart';
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../data/services/voice_interaction_service.dart';
enum ConnectStatus { enum ConnectStatus {
initial, // 初始状态,显示添加设备 initial, // 初始状态,显示添加设备
@ -25,8 +26,10 @@ class HomeController extends GetxController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
_connectStatus.value = ConnectStatus.initial; _connectStatus.value = ConnectStatus.initial;
VoiceInteractionService.to.initialize();
} }
@override @override
void onClose() { void onClose() {
_isDisposed = true; _isDisposed = true;

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

@ -7,7 +7,6 @@ import '../../../data/services/volcano_translation_service.dart';
import '../../../data/services/azure_asr_service.dart'; import '../../../data/services/azure_asr_service.dart';
import '../../../data/services/azure_tts_service.dart'; import '../../../data/services/azure_tts_service.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/services/voice_interaction_service.dart';
class TranslationController extends GetxController { class TranslationController extends GetxController {
// Services // Services
@ -81,11 +80,6 @@ class TranslationController extends GetxController {
void onInit() { void onInit() {
super.onInit(); super.onInit();
// 暂停后台语音交互服务,避免与翻译页面的语音功能相互干扰
if (Get.isRegistered<VoiceInteractionService>()) {
VoiceInteractionService.to.pauseVoiceInteraction();
Logger.info('已暂停后台语音交互服务');
}
// 加载历史记录 // 加载历史记录
loadTranslationHistory(); loadTranslationHistory();

Loading…
Cancel
Save