diff --git a/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt index 53ac72076..58ec8338d 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/AzureTtsHelper.kt @@ -244,7 +244,8 @@ class AzureTtsHelper { try { synthesizer?.StopSpeakingAsync() - Log.d(TAG, "已停止语音合成") + + Log.d(TAG, "已停止语音合成kt") return true } catch (e: Exception) { Log.e(TAG, "停止语音合成失败: ${e.message}") diff --git a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt index d0ac2a7b1..8843aac09 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt @@ -72,13 +72,30 @@ class MainActivity: FlutterActivity() { var azureSpeechKey: String = "2K2VOHSuL8ZSEnbKgHSyTwo1ohj7C2gbxyVimsjpVUt6aETxVwlvJQQJ99BCACqBBLyXJ3w3AAAYACOGqCMj" var azureSpeechRegion: String = "southeastasia" var volcanoAiApiKey: String = "0fd7c93b-b9bf-4b0b-bca6-e284491792bf" + + // 标记是否已初始化 + var isAzureHelpersInitialized = false } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // 初始化本地的 Azure 语音服务实例 + Log.d(TAG, "初始化本地的 Azure 语音服务实例") azureAsrHelper = AzureAsrHelper() 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() @@ -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 "空"}") + // 如果配置发生变化,需要重新初始化本地实例 + // if (key.isNotEmpty() && region.isNotEmpty()) { + // Log.d(TAG, "配置已更新,重新初始化本地实例") + // azureAsrHelper.initialize(key, region, "zh-CN") + // azureTtsHelper.initialize(key, region, "zh-CN") + // } + // 创建 Intent 并启动服务 val serviceIntent = Intent(this, VoiceInteractionService::class.java) @@ -618,8 +642,10 @@ class MainActivity: FlutterActivity() { Log.e(TAG, "注销广播接收器失败: ${e.message}") } + // 在这里释放本地实例 azureAsrHelper.dispose() azureTtsHelper.dispose() + super.onDestroy() } } diff --git a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt index 56b398417..3f80fe6cf 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt @@ -90,6 +90,7 @@ class VoiceInteractionService : Service() { 请保持回答简短、准确,避免过长的解释。 如果用户的问题不清楚,请礼貌地请求澄清。 不要使用复杂的术语,除非用户明确要求。 + 用户用语音和你交互. """.trimIndent() override fun onCreate() { @@ -139,7 +140,8 @@ class VoiceInteractionService : Service() { * 初始化所有服务 */ private fun initServices() { - // 初始化Azure服务 + // 创建新的Azure服务实例 + Log.d(TAG, "创建新的Azure服务实例") azureAsrHelper = AzureAsrHelper() azureTtsHelper = AzureTtsHelper() @@ -185,6 +187,8 @@ class VoiceInteractionService : Service() { mediaSession.setCallback(object : MediaSessionCompat.Callback() { override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent") + handleDoubleClick() + return true val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) @@ -328,6 +332,14 @@ class VoiceInteractionService : Service() { private fun handleDoubleClick() { Log.d(TAG, "处理双击事件") + //屏蔽短时间重复响应的问题 + val currentTime = System.currentTimeMillis() + val timeDiff = currentTime - lastKeyEventTime + if (timeDiff < 500) { + Log.d(TAG, "短时间重复响应,忽略") + return + } + // 停止当前TTS播放 stopCurrentTTS() @@ -362,7 +374,9 @@ class VoiceInteractionService : Service() { playNotification("需要录音权限,请在设置中授予权限") return } - + // 通知 Flutter 语音识别已启动 + notifyRecognitionStarted() + isActive = true isRecognitionActive = true hasSpeechDetected = false @@ -390,34 +404,33 @@ class VoiceInteractionService : Service() { override fun onSessionStarted() { updateLastActivityTime() - // 通知 Flutter 语音识别已启动 - notifyRecognitionStarted() + } override fun onSessionStopped() { isRecognitionActive = false - // 如果不是因超时暂停,尝试重新启动 - if (isActive && !isTimeoutPaused) { - handler.postDelayed({ - if (isActive && !isRecognitionActive && !isTtsSpeaking) { - startVoiceRecognition() - } - }, 1000) - } + // // 如果不是因超时暂停,尝试重新启动 + // if (isActive && !isTimeoutPaused) { + // handler.postDelayed({ + // if (isActive && !isRecognitionActive && !isTtsSpeaking) { + // startVoiceRecognition() + // } + // }, 1000) + // } } override fun onCanceled(reason: String, errorDetails: String) { isRecognitionActive = false - // 如果不是因超时暂停,尝试重新启动 - if (isActive && !isTimeoutPaused) { - handler.postDelayed({ - if (isActive && !isRecognitionActive && !isTtsSpeaking) { - startVoiceRecognition() - } - }, 1000) - } + // // 如果不是因超时暂停,尝试重新启动 + // if (isActive && !isTimeoutPaused) { + // handler.postDelayed({ + // if (isActive && !isRecognitionActive && !isTtsSpeaking) { + // startVoiceRecognition() + // } + // }, 1000) + // } } override fun onError(error: String) { @@ -425,13 +438,13 @@ class VoiceInteractionService : Service() { playNotification("语音识别出错") // 如果不是因超时暂停,尝试重新启动 - if (isActive && !isTimeoutPaused) { - handler.postDelayed({ - if (isActive && !isRecognitionActive && !isTtsSpeaking) { - startVoiceRecognition() - } - }, 2000) - } + // if (isActive && !isTimeoutPaused) { + // handler.postDelayed({ + // if (isActive && !isRecognitionActive && !isTtsSpeaking) { + // startVoiceRecognition() + // } + // }, 2000) + // } } }) } catch (e: Exception) { @@ -439,11 +452,11 @@ class VoiceInteractionService : Service() { playNotification("启动语音识别失败") // 延迟重试 - handler.postDelayed({ - if (isActive && !isRecognitionActive && !isTtsSpeaking) { - startVoiceRecognition() - } - }, 3000) + // handler.postDelayed({ + // if (isActive && !isRecognitionActive && !isTtsSpeaking) { + // startVoiceRecognition() + // } + // }, 3000) } } @@ -725,22 +738,30 @@ class VoiceInteractionService : Service() { override fun onDestroy() { Log.d(TAG, "onDestroy - 语音交互服务正在销毁") - super.onDestroy() // 停止监控 stopMonitoring() - // 取消注册媒体按钮接收器 - val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name) - audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver) + // 停止语音识别 + if (isRecognitionActive) { + stopVoiceRecognition() + } + + // 停止TTS + stopCurrentTTS() - // 释放资源 - mediaSession.isActive = false + // 释放 MediaSession mediaSession.release() + + // 释放Azure服务实例 + Log.d(TAG, "释放Azure服务实例") azureAsrHelper.dispose() azureTtsHelper.dispose() + // 更新服务状态 isRunning.set(false) + + super.onDestroy() } /** diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index d27dd3199..f3da3c799 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -22,8 +22,8 @@ class InitialBinding extends Bindings { // 火山翻译服务 Get.lazyPut(() => VolcanoTranslationService(), fenix: true); - - // 语音交互服务 - 使用 putAsync 确保 init 方法被调用 + + // 注意:VoiceInteractionService 在 HomeController 中初始化 Get.lazyPut(() => VoiceInteractionService(), fenix: true); } } \ No newline at end of file diff --git a/lib/core/routes/app_pages.dart b/lib/core/routes/app_pages.dart index 6adaf2c70..b04ee1937 100644 --- a/lib/core/routes/app_pages.dart +++ b/lib/core/routes/app_pages.dart @@ -26,6 +26,11 @@ import './app_routes.dart'; abstract class AppPages { static final pages = [ + GetPage( + name: Routes.initial, + page: () => const HomeView(), + binding: HomeBinding(), + ), GetPage( name: Routes.home, page: () => const HomeView(), diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index ae5e19d87..c112313bf 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -1,5 +1,5 @@ abstract class Routes { - static const initial = '/'; + static const initial = '/home'; static const home = '/home'; static const explore = '/explore'; static const chat = '/chat'; diff --git a/lib/data/services/azure_asr_service.dart b/lib/data/services/azure_asr_service.dart index 48e78fc58..25766c2ed 100644 --- a/lib/data/services/azure_asr_service.dart +++ b/lib/data/services/azure_asr_service.dart @@ -2,11 +2,12 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '../../../core/utils/logger.dart'; - +import 'package:get/get.dart'; /// 微软语音识别服务 /// /// 该服务提供了通过平台通道与 Android 上的 Microsoft Speech SDK 交互的接口 class AzureAsrService { + static final AzureAsrService to = Get.put(AzureAsrService()); static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_asr'); static const EventChannel _eventChannel = EventChannel('com.deep_voice.azure_asr_events'); diff --git a/lib/data/services/azure_tts_service.dart b/lib/data/services/azure_tts_service.dart index e38807494..47d136de3 100644 --- a/lib/data/services/azure_tts_service.dart +++ b/lib/data/services/azure_tts_service.dart @@ -18,6 +18,7 @@ class AzureTtsException implements Exception { /// 该服务通过平台通道与 Android 上的 Microsoft Speech SDK 交互, /// 提供文本转语音功能。 class AzureTtsService extends GetxService { + static final AzureTtsService to = Get.put(AzureTtsService()); static const MethodChannel _channel = MethodChannel('com.deep_voice.azure_tts'); bool _isInitialized = false; diff --git a/lib/data/services/voice_interaction_service.dart b/lib/data/services/voice_interaction_service.dart index e979302bd..4245b41bc 100644 --- a/lib/data/services/voice_interaction_service.dart +++ b/lib/data/services/voice_interaction_service.dart @@ -1,49 +1,48 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import '../../core/utils/logger.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 '../../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 { 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 personalAssistantMessages = [].obs; - - // 方法通道 + // 方法通道和事件通道 static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction'); - - // 事件通道 static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events'); - // 配置信息 - late final String _azureSpeechKey; - late final String _azureSpeechRegion; - late final String _volcanoAiApiKey; + // 服务状态 + final _isServiceRunning = false.obs; + bool get isServiceRunning => _isServiceRunning.value; + + // 事件流控制器 + StreamController? _eventStreamController; + Stream? _eventStream; + Stream? get eventStream => _eventStream; // 事件通道状态 bool _isEventChannelReady = false; Completer? _eventChannelReadyCompleter; + StreamSubscription? _eventSubscription; + + // 配置信息 + late String _azureSpeechKey; + late String _azureSpeechRegion; + late String _volcanoAiApiKey; - // 初始化标记,避免重复初始化 - bool _isInitialized = false; + // 初始化状态标志 + static bool _isInitialized = false; + /// 构造函数 VoiceInteractionService() { - _loadConfig(); - _setupEventChannel(); + } /// 从环境变量加载配置 @@ -53,39 +52,44 @@ class VoiceInteractionService extends GetxService { _volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? ''; 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) { - Logger.error('未找到火山 AI 配置。请在 .env 文件中设置 VOLCANO_AI_API_KEY'); + Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY'); } } - /// 设置事件通道监听 + /// 设置事件通道 void _setupEventChannel() { _eventChannelReadyCompleter = Completer(); - _eventChannel.receiveBroadcastStream().listen( - (event) { - if (event is Map) { - final String eventType = event['type'] as String? ?? ''; - - // 处理通道准备好的事件 - if (eventType == 'channelReady') { - _isEventChannelReady = true; - if (!_eventChannelReadyCompleter!.isCompleted) { - _eventChannelReadyCompleter!.complete(); + _eventSubscription = _eventChannel + .receiveBroadcastStream() + .listen((event) { + if (event is Map) { + final String eventType = event['type'] as String? ?? ''; + + // 处理通道准备好的事件 + if (eventType == 'channelReady') { + _isEventChannelReady = true; + if (!_eventChannelReadyCompleter!.isCompleted) { + _eventChannelReadyCompleter!.complete(); + } + return; } - return; + + _handleVoiceInteractionEvent(event); } - - _handleEvent(event); - } - }, - onError: (error) { - Logger.error('语音交互事件通道错误: $error'); - } - ); + }, onError: (error) { + Logger.error('语音交互事件通道错误: $error'); + }); + } + + /// 创建事件流 + void _createEventStream() { + _eventStreamController = StreamController.broadcast(); + _eventStream = _eventStreamController?.stream; } /// 等待事件通道准备好 @@ -96,152 +100,67 @@ class VoiceInteractionService extends GetxService { await _eventChannelReadyCompleter!.future.timeout(timeout); return true; } on TimeoutException { - Logger.error('等待事件通道准备好超时'); + Logger.error('等待语音交互事件通道准备好超时'); return false; } } /// 初始化服务 - Future init() async { - // 如果已经初始化过,直接返回 - if (_isInitialized) { - return this; - } - + Future initialize() async { try { - // 等待事件通道准备好 - final bool eventChannelReady = await _waitForEventChannel(); - if (!eventChannelReady) { - Logger.error('事件通道未准备好,初始化失败'); + if (_isInitialized) { return this; } + _isInitialized = true; + + _loadConfig(); + _setupEventChannel(); + _createEventStream(); + // 检查服务是否正在运行 + await _checkServiceStatus(); - // 获取服务运行状态 - await _updateServiceStatus(); - - // 加载个人助手的历史记录 - _loadPersonalAssistantHistory(); + // 等待事件通道准备好 + await _waitForEventChannel(); - // 标记为已初始化 - _isInitialized = true; + Logger.info('语音交互服务初始化完成'); return this; } catch (e) { - Logger.error('VoiceInteractionService 初始化失败: $e'); + Logger.error('语音交互服务初始化失败: $e'); return this; } } - /// 加载个人助手的历史记录 - void _loadPersonalAssistantHistory() { - if (Get.isRegistered()) { - final historyService = Get.find(); - 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 event) { + /// 检查服务状态 + Future _checkServiceStatus() async { try { - final agentId = event['agentId'] as String?; - final userMessage = event['userMessage'] as String?; - final assistantMessage = event['assistantMessage'] as String?; - 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()) { - final historyService = Get.find(); - historyService.saveHistory('personal_assistant', personalAssistantMessages); - } + final bool isRunning = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false; + _isServiceRunning.value = isRunning; + Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}'); } catch (e) { - Logger.error('处理聊天记录事件失败: $e'); - } - } - - /// 停止 Flutter 端的语音服务 - void _stopFlutterVoiceServices() { - // 停止 TTS - if (Get.isRegistered()) { - Get.find().stop(); - } - - // 停止 ASR - if (Get.isRegistered()) { - Get.find().stopContinuousRecognition(); + Logger.error('检查语音交互服务状态失败: $e'); + _isServiceRunning.value = false; } } /// 启动语音交互服务 Future startService() async { - if (!_isInitialized) { - await init(); + if (_isServiceRunning.value) { + Logger.info('语音交互服务已经在运行'); + return true; } try { - // 启动服务 - final result = await _channel.invokeMethod('startVoiceInteractionService', { + final bool result = await _channel.invokeMethod('startVoiceInteractionService', { 'azure_speech_key': _azureSpeechKey, 'azure_speech_region': _azureSpeechRegion, 'volcano_ai_api_key': _volcanoAiApiKey, }) ?? false; - // 更新状态 if (result) { - await _updateServiceStatus(); + _isServiceRunning.value = true; + Logger.info('语音交互服务启动成功'); + } else { + Logger.error('语音交互服务启动失败'); } return result; @@ -253,12 +172,19 @@ class VoiceInteractionService extends GetxService { /// 停止语音交互服务 Future stopService() async { + if (!_isServiceRunning.value) { + Logger.info('语音交互服务未运行'); + return true; + } + try { - final result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false; + final bool result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false; - // 更新状态 if (result) { - await _updateServiceStatus(); + _isServiceRunning.value = false; + Logger.info('语音交互服务停止成功'); + } else { + Logger.error('语音交互服务停止失败'); } return result; @@ -268,10 +194,22 @@ class VoiceInteractionService extends GetxService { } } - /// 暂停语音交互 + /// 暂停语音交互(停止TTS和语音识别,但保持服务运行) Future pauseVoiceInteraction() async { + if (!_isServiceRunning.value) { + Logger.info('语音交互服务未运行,无法暂停'); + return false; + } + try { - final result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false; + final bool result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false; + + if (result) { + Logger.info('语音交互暂停成功'); + } else { + Logger.error('语音交互暂停失败'); + } + return result; } catch (e) { Logger.error('暂停语音交互失败: $e'); @@ -279,34 +217,116 @@ class VoiceInteractionService extends GetxService { } } - /// 更新服务运行状态 - Future _updateServiceStatus() async { - try { - final status = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false; - _isRunning.value = status; - } catch (e) { - Logger.error('获取语音交互服务状态失败: $e'); - _isRunning.value = false; + /// 处理来自原生端的语音交互事件 + void _handleVoiceInteractionEvent(dynamic event) { + if (event is! Map || _eventStreamController == null) return; + + final Map eventMap = event; + final String eventType = eventMap['type'] as String? ?? ''; + final int timestamp = eventMap['timestamp'] as int? ?? 0; + + // 添加时间戳日志,帮助调试 + 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; } } - /// 释放资源 - Future dispose() async { + @override + void onClose() { + // 清理资源 + _eventSubscription?.cancel(); + _eventStreamController?.close(); + super.onClose(); + } + + /// 保存聊天记录 + void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) { try { - if (_isRunning.value) { - await stopService(); - } + // 获取ChatHistoryService实例 + final chatHistoryService = Get.find(); + + // 创建用户消息和助手消息 + 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) { - Logger.error('释放语音交互服务资源失败: $e'); + Logger.error('保存聊天记录失败: $e'); } } +} + +/// 语音交互事件基类 +abstract class VoiceInteractionEvent { + final int timestamp; - @override - void onClose() { - // 清理资源 - dispose(); - super.onClose(); - } + VoiceInteractionEvent({required this.timestamp}); +} + +/// 聊天历史事件 +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); } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 9c84dfc4c..2304c63b9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'core/routes/app_routes.dart'; import 'dart:io'; import 'package:window_size/window_size.dart'; import 'app/translations/app_translations.dart'; +import 'data/services/voice_interaction_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -54,10 +55,10 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { return ScreenUtilInit( - designSize: const Size(375, 812), // 设计稿尺寸 + designSize: const Size(375, 812), minTextAdapt: true, splitScreenMode: true, - builder: (context, child) { + builder: (_, child) { return GetMaterialApp( title: 'Deep Voice', theme: ThemeData( @@ -65,14 +66,16 @@ class MainApp extends StatelessWidget { visualDensity: VisualDensity.adaptivePlatformDensity, ), initialBinding: InitialBinding(), - initialRoute: Routes.home, + initialRoute: Routes.initial, getPages: AppPages.pages, debugShowCheckedModeBanner: false, translations: AppTranslations(), locale: const Locale('zh', 'CN'), fallbackLocale: const Locale('en', 'US'), + // 注意:VoiceInteractionService 在 HomeController 的 onInit 方法中初始化 + // 不要在这里重复初始化,以避免多次注册事件监听器 ); - } + }, ); } } diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index f444a76a8..abbfb3bb0 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -9,6 +9,7 @@ import '../../../data/providers/agent_provider.dart'; import '../../../data/models/agent.dart'; import '../../../core/utils/logger.dart'; import '../../../data/services/chat_history_service.dart'; +import '../../../data/services/voice_interaction_service.dart'; class ChatController extends GetxController { // 服务 @@ -16,6 +17,7 @@ class ChatController extends GetxController { final AzureTtsService _ttsService = Get.find(); final AzureAsrService _asrService = Get.find(); final ChatHistoryService _historyService = Get.find(); + final VoiceInteractionService _voiceInteractionService = Get.find(); // 添加ScrollController用于控制列表滚动 final ScrollController scrollController = ScrollController(); @@ -46,6 +48,7 @@ class ChatController extends GetxController { // 流式响应控制 StreamSubscription? _aiResponseSubscription; StreamSubscription? _recognitionSubscription; + StreamSubscription? _voiceInteractionSubscription; // 取消标志 bool _shouldCancelAiResponse = false; @@ -93,12 +96,101 @@ class ChatController extends GetxController { startVoiceRecognition(); } + // 订阅VoiceInteractionService事件 + _subscribeToVoiceInteractionEvents(); + // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _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 void onClose() { Logger.info('ChatController onClose'); @@ -107,6 +199,7 @@ class ChatController extends GetxController { scrollController.dispose(); _aiResponseSubscription?.cancel(); _recognitionSubscription?.cancel(); + _voiceInteractionSubscription?.cancel(); // 停止TTS _ttsService.stop(); @@ -321,6 +414,7 @@ class ChatController extends GetxController { Future startVoiceRecognition() async { if (isVoiceRecognitionActive.value) return; + _voiceInteractionService.pauseVoiceInteraction(); try { // 初始化语音识别 await _asrService.initialize(language: 'zh-CN'); diff --git a/lib/modules/explore/controllers/explore_controller.dart b/lib/modules/explore/controllers/explore_controller.dart index 6831cc882..a13432868 100644 --- a/lib/modules/explore/controllers/explore_controller.dart +++ b/lib/modules/explore/controllers/explore_controller.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; import '../../../data/providers/agent_provider.dart'; - +import '../../../data/services/voice_interaction_service.dart'; class ChatItem { final String id; @@ -28,4 +28,5 @@ class ExploreController extends GetxController { agents.map((agent) => ChatItem(id: agent.id)).toList(), ); } + } \ No newline at end of file diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index d29d6ab43..ef1b5ca81 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -2,6 +2,7 @@ import 'package:get/get.dart'; import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import '../../../data/services/voice_interaction_service.dart'; enum ConnectStatus { initial, // 初始状态,显示添加设备 @@ -25,8 +26,10 @@ class HomeController extends GetxController { void onInit() { super.onInit(); _connectStatus.value = ConnectStatus.initial; + VoiceInteractionService.to.initialize(); } + @override void onClose() { _isDisposed = true; diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index e3f6821e3..9f953dc1c 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/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_tts_service.dart'; import '../../../core/utils/logger.dart'; -import '../../../data/services/voice_interaction_service.dart'; class TranslationController extends GetxController { // Services @@ -81,11 +80,6 @@ class TranslationController extends GetxController { void onInit() { super.onInit(); - // 暂停后台语音交互服务,避免与翻译页面的语音功能相互干扰 - if (Get.isRegistered()) { - VoiceInteractionService.to.pauseVoiceInteraction(); - Logger.info('已暂停后台语音交互服务'); - } // 加载历史记录 loadTranslationHistory();