diff --git a/lib/data/services/background_agent_service.dart b/lib/data/services/background_agent_service.dart index b2bb3cf95..59fcca7f6 100644 --- a/lib/data/services/background_agent_service.dart +++ b/lib/data/services/background_agent_service.dart @@ -46,31 +46,95 @@ class BackgroundAgentService extends GetxService { // 添加一个标志,表示系统是否正在播放 TTS bool _isSpeaking = false; - // 添加一个订阅,用于监听 TTS 状态变化 - StreamSubscription? _ttsSpeakingSubscription; - // 添加一个变量来跟踪当前的AI响应流订阅 StreamSubscription? _aiResponseSubscription; // 添加一个标志,表示是否应该取消当前的AI响应 bool _shouldCancelAiResponse = false; + // 添加一个变量来跟踪最后一次 TTS 状态变化的时间 + DateTime? _lastTtsStateChangeTime; + BackgroundAgentService() : _aiService = VolcanoAIService(), _ttsService = Get.find(), _voiceRecognitionService = Get.find() { - // 监听 TTS 播放状态 - _ttsSpeakingSubscription = _ttsService.isPlaying.listen((speaking) { - _isSpeaking = speaking; - print('TTS 播放状态变化: $_isSpeaking'); + // 不再监听 TTS 播放状态,而是使用定期检查 + // 初始化 TTS 状态 + _isSpeaking = _ttsService.isActuallyPlaying(); + _lastTtsStateChangeTime = DateTime.now(); + + // 添加定期检查实际播放状态的计时器 + Timer.periodic(const Duration(seconds: 1), (timer) { + // 检查实际播放状态 + final actuallyPlaying = _ttsService.isActuallyPlaying(); - // 如果 TTS 停止播放,且正在进行语音识别,重新启动无语音超时计时器 - if (!speaking && _isListening && _hasRecognizedSpeech && _noSpeechTimer == null) { - _startNoSpeechTimer(); + // 如果状态发生变化,更新状态并记录时间 + if (_isSpeaking != actuallyPlaying) { + print('TTS 播放状态变化: $_isSpeaking -> $actuallyPlaying'); + _isSpeaking = actuallyPlaying; + _lastTtsStateChangeTime = DateTime.now(); + + // 如果 TTS 服务的 isPlaying 与实际状态不一致,尝试修正 + if (actuallyPlaying != _ttsService.isPlaying.value) { + try { + _ttsService.isPlaying.value = actuallyPlaying; + print('已修正 TTS 服务的播放状态'); + } catch (e) { + print('修正 TTS 播放状态失败: $e'); + } + } + + // 如果 TTS 停止播放,且正在进行语音识别,重新启动无语音超时计时器 + if (!actuallyPlaying && _isListening && _hasRecognizedSpeech && _noSpeechTimer == null) { + _startNoSpeechTimer(); + } + } + + // 如果正在处理交互,进行更全面的检查 + if (_isProcessing) { + _checkAndUpdateTtsState(); } }); } + // 添加一个方法来检查和更新 TTS 状态 + void _checkAndUpdateTtsState() { + try { + // 获取实际播放状态 + final actuallyPlaying = _ttsService.isActuallyPlaying(); + + // 如果状态显示正在播放,但已经很长时间没有状态变化,强制重置 + if (actuallyPlaying && _lastTtsStateChangeTime != null) { + final now = DateTime.now(); + final difference = now.difference(_lastTtsStateChangeTime!); + + // 如果超过15秒没有状态变化,强制重置 + if (difference.inSeconds > 15) { + print('强制重置 TTS 状态:已经 ${difference.inSeconds} 秒没有状态变化'); + + try { + // 停止 TTS 播放 + _ttsService.stop(); + + // 强制更新状态 + _isSpeaking = false; + _ttsService.isPlaying.value = false; + + // 更新最后状态变化时间 + _lastTtsStateChangeTime = DateTime.now(); + + print('TTS 状态已强制重置'); + } catch (e) { + print('强制重置 TTS 状态失败: $e'); + } + } + } + } catch (e) { + print('检查 TTS 播放状态失败: $e'); + } + } + // 处理蓝牙耳机按钮触发的交互 Future handleAgentInteraction(String systemPrompt) async { if (_isProcessing) { @@ -110,9 +174,26 @@ class BackgroundAgentService extends GetxService { // 启动无语音超时计时器 void _startNoSpeechTimer() { + // 检查 TTS 播放状态,使用 isActuallyPlaying 获取实际状态 + _isSpeaking = _ttsService.isActuallyPlaying(); + // 如果系统正在播放 TTS,不启动计时器 if (_isSpeaking) { - print('系统正在播放 TTS,不启动无语音超时计时器'); + print('系统正在播放 TTS,不启动无语音超时计时器 (isPlaying.value: ${_ttsService.isPlaying.value}, 实际播放状态: $_isSpeaking)'); + + // 即使TTS正在播放,也设置一个更长的安全超时,防止系统永远卡在循环中 + _noSpeechTimer?.cancel(); + _noSpeechTimer = Timer(const Duration(seconds: 30), () { + // 再次检查 TTS 状态,使用 isActuallyPlaying + _isSpeaking = _ttsService.isActuallyPlaying(); + + print('安全超时触发:即使TTS正在播放,30秒内没有新的语音输入,强制退出循环交互 (实际播放状态: $_isSpeaking)'); + if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) { + _recognitionCompleter!.complete(''); + } + _shouldContinueInteraction = false; + }); + return; } @@ -121,10 +202,17 @@ class BackgroundAgentService extends GetxService { // 设置新的计时器,如果 10 秒内没有新的识别结果,且系统没有在播放 TTS,则认为用户已经停止交互 _noSpeechTimer = Timer(const Duration(seconds: 10), () { - // 再次检查是否正在播放 TTS + // 再次检查是否正在播放 TTS,使用 isActuallyPlaying + _isSpeaking = _ttsService.isActuallyPlaying(); + + print('无语音超时计时器触发,当前 TTS 播放状态: $_isSpeaking (isPlaying.value: ${_ttsService.isPlaying.value}, 实际播放状态: $_isSpeaking)'); + if (!_isSpeaking && _isListening && _recognitionCompleter != null && !_recognitionCompleter!.isCompleted) { print('10秒内没有新的语音输入,且系统没有在播放 TTS,退出循环交互'); _recognitionCompleter!.complete(''); + + // 直接设置标志,停止循环交互 + _shouldContinueInteraction = false; } }); @@ -138,6 +226,9 @@ class BackgroundAgentService extends GetxService { _hasFinalResult = false; try { + // 强制重置 TTS 状态,确保不会因为错误的状态导致系统卡住 + _resetTtsStateIfNeeded(); + // 先播放一个简短的提示音或提示语,表示开始监听 try { await _ttsService.speak("我在听"); @@ -175,8 +266,8 @@ class BackgroundAgentService extends GetxService { _noSpeechTimer = null; // 如果没有识别到语音,则停止循环交互 - if (!_hasRecognizedSpeech) { - print('没有识别到用户语音,退出循环交互'); + if (!_hasRecognizedSpeech || userInput.trim().isEmpty) { + print('没有识别到用户语音或识别结果为空,退出循环交互'); // 停止语音识别 if (_isListening) { @@ -199,17 +290,7 @@ class BackgroundAgentService extends GetxService { // 只有在退出交互时才停止语音识别 } catch (e) { print('语音识别过程出错: $e'); - // 如果语音识别失败,尝试使用默认问候语 - userInput = '你好,请帮我回答一个问题'; - - // 尝试重置语音识别服务 - try { - await stopVoiceRecognition(); - await Future.delayed(const Duration(milliseconds: 500)); - await _voiceRecognitionService.initialize(); - } catch (resetError) { - print('重置语音识别服务失败: $resetError'); - } + } finally { // 清理资源,但保持语音识别状态 _recognitionCompleter = null; @@ -405,6 +486,18 @@ class BackgroundAgentService extends GetxService { print('收到最终识别结果,立即处理: ${event.text}'); _recognitionCompleter!.complete(event.text); } + } else { + // 如果最终结果为空,但之前有中间结果,仍然标记为有语音 + if (_hasRecognizedSpeech) { + print('最终识别结果为空,但之前有中间结果,使用最后的中间结果'); + if (_recognitionCompleter != null && !_recognitionCompleter!.isCompleted) { + _recognitionCompleter!.complete(recognizedText.value); + } + } else { + // 如果最终结果为空,且没有中间结果,重新启动无语音超时计时器 + print('最终识别结果为空,且没有中间结果,重新启动无语音超时计时器'); + _startNoSpeechTimer(); + } } print('最终识别结果: ${event.text}'); } else if (event.type == RecognitionEventType.intermediateResult) { @@ -537,9 +630,42 @@ class BackgroundAgentService extends GetxService { _recognitionSubscription?.cancel(); _silenceTimer?.cancel(); _noSpeechTimer?.cancel(); - _ttsSpeakingSubscription?.cancel(); _aiResponseSubscription?.cancel(); _shouldContinueInteraction = false; super.onClose(); } + + // 添加一个方法来强制重置 TTS 状态 + void _resetTtsStateIfNeeded() { + // 检查 TTS 状态,使用 isActuallyPlaying 获取实际状态 + bool isCurrentlyPlaying = false; + try { + isCurrentlyPlaying = _ttsService.isActuallyPlaying(); + } catch (e) { + print('获取实际播放状态失败: $e'); + isCurrentlyPlaying = _ttsService.isPlaying.value; + } + + // 如果实际状态与当前状态不一致,更新它 + if (_isSpeaking != isCurrentlyPlaying) { + print('重置前检测到 TTS 播放状态不一致,修正: _isSpeaking=$_isSpeaking, 实际=${isCurrentlyPlaying}'); + _isSpeaking = isCurrentlyPlaying; + + // 如果实际状态与 TTS 服务的 isPlaying 不一致,尝试修正 TTS 服务的状态 + if (isCurrentlyPlaying != _ttsService.isPlaying.value) { + try { + _ttsService.isPlaying.value = isCurrentlyPlaying; + print('已修正 TTS 服务的播放状态'); + } catch (e) { + print('修正 TTS 播放状态失败: $e'); + } + } + + // 更新最后一次状态变化的时间 + _lastTtsStateChangeTime = DateTime.now(); + } + + // 检查是否需要强制重置 + _checkAndUpdateTtsState(); + } } \ No newline at end of file diff --git a/lib/data/services/volcano_tts_service.dart b/lib/data/services/volcano_tts_service.dart index 8a6c06ac8..6a9ec9402 100644 --- a/lib/data/services/volcano_tts_service.dart +++ b/lib/data/services/volcano_tts_service.dart @@ -576,6 +576,31 @@ class VolcanoTtsService extends GetxService { } } + // 添加一个方法来检查音频播放器是否真正在播放 + bool isActuallyPlaying() { + try { + // 检查播放器状态 + final isPlayerPlaying = _audioPlayer.playing; + + // 检查播放列表是否为空 + final hasAudioSource = _playlist.length > 0; + + // 只有当播放器正在播放且播放列表不为空时,才认为真正在播放 + final actuallyPlaying = isPlayerPlaying && hasAudioSource; + + // 如果状态不一致,记录日志 + if (isPlaying.value != actuallyPlaying) { + print('音频播放状态不一致: isPlaying.value=${isPlaying.value}, 实际播放状态=$actuallyPlaying (播放器状态=$isPlayerPlaying, 播放列表长度=${_playlist.length})'); + } + + return actuallyPlaying; + } catch (e) { + print('检查实际播放状态失败: $e'); + // 如果检查失败,返回当前的 isPlaying 值 + return isPlaying.value; + } + } + Future _reinitializePlayer() async { try { await stop();