import 'dart:async'; import 'dart:math'; import 'package:get/get.dart'; import 'package:flutter/foundation.dart'; import '../../../data/services/voice_recognition_service.dart'; import '../../../data/services/volcano_tts_service.dart'; class VoiceInputController extends GetxController { // Observable states final isConnecting = true.obs; final isMuted = false.obs; final isRecording = false.obs; final recognizedText = ''.obs; // 添加一个新的可观察状态,表示用户是否正在说话 final isUserSpeaking = false.obs; // 语音识别服务 late final VoiceRecognitionService _voiceService; late final VolcanoTtsService _ttsService; // 连续识别相关 StreamSubscription? _recognitionSubscription; // 添加一个标志,表示是否已经识别到语音 bool _hasRecognizedSpeech = false; // 添加一个 Completer 用于在识别到最终结果时完成 Completer? _recognitionCompleter; // 添加一个标志,表示是否已经收到最终结果 bool _hasFinalResult = false; // 添加一个计时器,用于在一段时间没有新的识别结果时提交当前结果 Timer? _silenceTimer; // 添加一个计时器,用于定期更新用户说话状态 Timer? _speakingCheckTimer; // 最后一次识别到语音的时间 DateTime? _lastSpeechTime; // 添加一个变量来跟踪空结果的数量 int _emptyResultCount = 0; static const int _maxEmptyResults = 3; // Callbacks final Function(String) onRecordingResult; final VoidCallback onClosePanel; final Function(String) onRecognizing; VoiceInputController({ required this.onRecordingResult, required this.onClosePanel, required this.onRecognizing, }); @override void onInit() { super.onInit(); _initializeVoiceService(); _ttsService = Get.find(); // 启动定期检查用户是否正在说话的计时器 _startSpeakingCheckTimer(); } Future _initializeVoiceService() async { try { // 创建语音识别服务实例 _voiceService = VoiceRecognitionService(); // 初始化语音识别服务 await _voiceService.initialize(); // 初始化完成后更新状态 isConnecting.value = false; // 自动开始连续语音识别 await startContinuousRecognition(); } catch (e) { print('初始化语音识别服务失败: $e'); Get.snackbar( 'Error', '初始化语音识别服务失败: $e', snackPosition: SnackPosition.BOTTOM, ); } } Future startContinuousRecognition() async { if (isConnecting.value) return; if (isRecording.value) return; // 已经在录音中 try { isRecording.value = true; recognizedText.value = ''; // 清空识别文本,但不再在面板中显示 _hasRecognizedSpeech = false; _hasFinalResult = false; _emptyResultCount = 0; // 创建一个 Completer 来处理语音识别完成 _recognitionCompleter = Completer(); // 开始连续语音识别 final stream = await _voiceService.startContinuousRecognition(); // 订阅识别事件流 _recognitionSubscription = stream.listen((event) { switch (event.type) { case RecognitionEventType.intermediateResult: // 不再更新面板中的识别文本,而是通过回调传递给ChatController if (event.text.isNotEmpty) { _hasRecognizedSpeech = true; _lastSpeechTime = DateTime.now(); _emptyResultCount = 0; // 重置空结果计数 // 设置用户正在说话状态 isUserSpeaking.value = true; // 如果系统正在播放TTS,检测到用户开始说话时立即中断 bool isSpeaking = false; try { isSpeaking = _ttsService.isActuallyPlaying(); } catch (e) { print('获取 TTS 播放状态失败: $e'); // 如果获取失败,假设为false isSpeaking = false; } if (isSpeaking && event.text.trim().isNotEmpty) { print('检测到用户开始说话,中断TTS播放'); _ttsService.stop(); // 停止当前TTS播放 } // 取消之前的静默计时器 _silenceTimer?.cancel(); // 设置新的静默计时器,如果 2 秒内没有新的识别结果,则认为用户已经停止说话 _silenceTimer = Timer(const Duration(seconds: 2), () { if (_hasRecognizedSpeech && !_hasFinalResult && _recognitionCompleter != null && !_recognitionCompleter!.isCompleted) { print('用户停止说话 2 秒,使用当前识别结果: ${event.text}'); _recognitionCompleter!.complete(event.text); // 调用最终结果回调,发送识别到的句子 onRecordingResult(event.text); // 清空识别状态,准备下一句 _hasRecognizedSpeech = false; _hasFinalResult = false; } }); } // 调用识别中回调,将中间结果传递给ChatController onRecognizing(event.text); break; case RecognitionEventType.finalResult: // 更新最终识别结果 _hasFinalResult = true; // 重置用户说话状态 isUserSpeaking.value = false; if (event.text.isNotEmpty) { // 不再更新面板中的识别文本 _hasRecognizedSpeech = true; _lastSpeechTime = DateTime.now(); _emptyResultCount = 0; // 重置空结果计数 // 调用最终结果回调,发送识别到的句子 onRecordingResult(event.text); // 清空识别状态,准备下一句 _hasRecognizedSpeech = false; _hasFinalResult = false; } else { // 如果最终结果为空,但之前有中间结果,仍然标记为有语音 if (_hasRecognizedSpeech) { print('最终识别结果为空,但之前有中间结果,使用最后的中间结果'); // 这里不再需要使用recognizedText.value,因为我们已经在中间结果中传递了文本 // 而且ChatController已经在UI中显示了最新的识别结果 // 清空识别状态,准备下一句 _hasRecognizedSpeech = false; _hasFinalResult = false; } else { // 如果最终结果为空,且没有中间结果,增加空结果计数 _emptyResultCount++; print('收到空的最终结果,当前空结果计数: $_emptyResultCount / $_maxEmptyResults'); } } break; case RecognitionEventType.error: case RecognitionEventType.canceled: // 处理错误 print('语音识别错误: ${event.error}'); Get.snackbar( 'Error', '语音识别错误: ${event.error}', snackPosition: SnackPosition.BOTTOM, ); // 尝试重新启动识别 restartRecognition(); break; case RecognitionEventType.sessionStopped: // 会话结束,尝试重新启动 isRecording.value = false; restartRecognition(); break; default: break; } }, onError: (error) { print('语音识别流错误: $error'); Get.snackbar( 'Error', '语音识别流错误: $error', snackPosition: SnackPosition.BOTTOM, ); isRecording.value = false; // 尝试重新启动识别 restartRecognition(); }); // 等待语音识别完成 final result = await _recognitionCompleter!.future; } catch (e) { print('语音识别失败: $e'); Get.snackbar( 'Error', '启动语音识别失败: $e', snackPosition: SnackPosition.BOTTOM, ); isRecording.value = false; } finally { // 清理资源 _recognitionCompleter = null; _silenceTimer?.cancel(); _silenceTimer = null; } } // 重新启动识别 Future restartRecognition() async { try { // 先停止当前识别 await stopRecording(); // 延迟一下再重新启动 await Future.delayed(const Duration(milliseconds: 500)); // 重新启动连续识别 await startContinuousRecognition(); } catch (e) { print('重新启动语音识别失败: $e'); } } Future stopRecording() async { if (!isRecording.value) return; try { // 取消事件订阅 await _recognitionSubscription?.cancel(); _recognitionSubscription = null; // 取消计时器 _silenceTimer?.cancel(); _silenceTimer = null; // 停止连续识别 if (_voiceService.isContinuousRecognitionActive()) { await _voiceService.stopContinuousRecognition(); } isRecording.value = false; _hasRecognizedSpeech = false; _hasFinalResult = false; _emptyResultCount = 0; } catch (e) { print('停止语音识别失败: $e'); isRecording.value = false; } } void toggleMute() { // 只切换静音状态,不再影响录音 // 因为已启用回声消除,不需要在录音时停止TTS isMuted.value = !isMuted.value; } // 手动开始录音(用户点击按钮) void startRecording() { if (!isRecording.value) { startContinuousRecognition(); } } // 启动定期检查用户是否正在说话的计时器 void _startSpeakingCheckTimer() { _speakingCheckTimer?.cancel(); _speakingCheckTimer = Timer.periodic(const Duration(milliseconds: 200), (_) { _checkIfUserIsSpeaking(); }); } // 检查用户是否正在说话 void _checkIfUserIsSpeaking() { // 如果有最近的识别文本,且距离现在不超过1秒,认为用户正在说话 if (_hasRecognizedSpeech && !_hasFinalResult && _lastSpeechTime != null) { final now = DateTime.now(); final timeSinceLastSpeech = now.difference(_lastSpeechTime!); // 如果最近1秒内有语音识别,认为用户正在说话 if (timeSinceLastSpeech.inMilliseconds < 1000) { isUserSpeaking.value = true; return; } } // 默认情况下,如果正在录音但没有最近的语音识别,随机生成一个说话状态 if (isRecording.value) { // 随机生成一个值,有20%的概率认为用户正在说话 isUserSpeaking.value = Random().nextDouble() < 0.2; } else { isUserSpeaking.value = false; } } @override void onClose() { // 停止录音 stopRecording(); // 取消计时器 _speakingCheckTimer?.cancel(); // 重置状态 isConnecting.value = false; isMuted.value = false; isRecording.value = false; recognizedText.value = ''; isUserSpeaking.value = false; super.onClose(); } }