import 'dart:io'; import 'package:deep_voice/core/utils/permission_util.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'dart:convert'; import 'package:get_storage/get_storage.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../../data/services/volcano_translation_service.dart'; import '../../../data/services/tts_service.dart'; import '../../../data/services/language_manager.dart'; import '../../../core/utils/logger.dart'; import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/services/asr_service.dart'; import '../../../data/services/ble_manager.dart'; class TranslationController extends GetxController { // 服务 final AsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = Get.find(); final TtsService _ttsService = Get.find(); final LanguageManager _languageManager = Get.find(); final GetStorage _storage = GetStorage(); // 蓝牙服务 final _bleManager = Get.find(); // 音频输入源 bool _audioSourceType = false; // 存储相关 static const String _historyKey = 'translation_history'; static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; static const int _maxHistoryItems = 10; // 状态变量 final isRecognizing = false.obs; final isTranslating = false.obs; final isTtsEnabled = true.obs; final isRecording = false.obs; // 语言相关 final sourceLanguage = '中文(简体)'.obs; final targetLanguage = '英语'.obs; final sourceLanguageCode = 'zh-CN'.obs; final targetLanguageCode = 'en-US'.obs; var detectedLanguageCode = ''; final currentModeTitle = 'simultaneousTranslation'.obs; final currentMode = 'simultaneous'.obs; // 翻译历史和当前项 final translationHistory = [].obs; String? currentSessionId; final appDocDir = getApplicationDocumentsDirectory(); var appDir; var dir; Future changeTranslationMode(String mode) async { currentMode.value = mode; currentModeTitle.value = _getModeTitle(mode); stopAll(); } String _getModeTitle(String mode) { switch (mode) { case 'audioVideo': return 'audioVideoTranslation'; case 'call': return 'callTranslation'; case 'faceToFace': return 'faceToFaceTranslation'; default: return 'simultaneousTranslation'; } } // 兼容旧版视图的计算属性 Rx get currentItem { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { return Rx(translationHistory.last); } return Rx(null); } // UI相关 final ScrollController scrollController = ScrollController(); // 其他 StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; Timer? _translationDebounceTimer; // 获取支持的语言列表 Map get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); @override void onInit() { super.onInit(); //_setupEventListeners(); // 加载上次保存的语言设置 _loadSavedLanguages(); _requestStoragePermission(); // 初始化语言代码 final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); if (sourceAsrCode != null) { sourceLanguageCode.value = sourceAsrCode; } final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); if (targetAsrCode != null) { targetLanguageCode.value = targetAsrCode; } // 加载历史记录 loadTranslationHistory(); // 初始化服务 _initServices(); // 使用微任务在布局完成后自动滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } // 加载保存的语言设置 void _loadSavedLanguages() { final savedSourceLanguage = _storage.read(_sourceLanguageKey); final savedTargetLanguage = _storage.read(_targetLanguageKey); if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) { sourceLanguage.value = savedSourceLanguage; } if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) { targetLanguage.value = savedTargetLanguage; } } // 保存当前语言设置 void _saveLanguageSettings() { _storage.write(_sourceLanguageKey, sourceLanguage.value); _storage.write(_targetLanguageKey, targetLanguage.value); } Future _requestRecordPermission() async { try { final bool granted = await PermissionUtil.instance.requestPermission( permissionType: Permission.microphone, permissionName: "录音", explanationText: "我们需要访问麦克风权限,以便您录制音频内容。", permanentDenialText: "检测到您已永久拒绝录音权限。如需继续使用此功能,请前往应用设置中手动开启麦克风权限。", ); if (granted) { Logger.d("Permission", "用户授予了录音权限"); return true; } else { Logger.d("Permission", "用户未授予录音权限"); return false; } } catch (e) { Logger.e("Permission", "请求录音权限时出错: $e"); return false; } } // 请求存储权限 void _requestStoragePermission() async { // final BuildContext context = Get.overlayContext!; PermissionUtil.instance .requestPermission( // context: context, permissionType: Permission.storage, permissionName: "存储", explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", ) .then((granted) { if (granted) { Logger.d("Permission", "用户授予了存储权限"); } else { Logger.d("Permission", "用户未授予存储权限"); // 可以提示用户无法进行文件操作 } }); } // /// 设置事件监听器 // void _setupEventListeners() { // // 监听扫描结果 // _scanSubscription = jieli.scanResults.listen((devices) { // scannedDevices.assignAll(devices); // }); // // 监听唤醒信号 // _wakeupSubscription = jieli.wakeupStream.listen((wakeup) { // wakeupReceived.value = true; // Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号'); // }); // } // 初始化服务 Future _initServices() async { try { await _translationService.initialize(); // 初始化 ASR 服务时,明确指定需要支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); appDir = await appDocDir; // 先 await 获取目录 dir = Directory("${appDir.path}/TranslatAudio"); if (!await dir.exists()) { await dir.create(recursive: true); // Add recursive creation } Logger.info('所有服务初始化完成'); } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } @override void onClose() { stopRecognition(); _recognitionSubscription?.cancel(); _voiceInteractionSubscription?.cancel(); _translationDebounceTimer?.cancel(); scrollController.dispose(); super.onClose(); } // 切换TTS功能 void toggleTts() => isTtsEnabled.toggle(); // 切换录音功能 Future toggleisRecord() async { isRecording.toggle(); if (isRecording.value && isRecognizing.value) { // 使用可读性更好的时间格式 final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); await _asrService.enableRecord( "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); } else if (!isRecording.value) { await _asrService.stopRecord(true); } else {} } // 开始语音识别 Future startRecognition() async { Logger.info('开始语音识别'); // 等待权限请求完成并获取结果 final bool granted = await _requestRecordPermission(); // 如果权限被拒绝,直接返回 if (!granted) { Logger.d("Permission", "录音权限被拒绝,无法开始录音"); return; } if (isRecognizing.value) return; try { // 每次开始识别前检查 ASR 服务是否已配置为当前语言对 final List currentLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages); if (needsReinit) { Logger.info('识别开始前发现语言变更,重新初始化ASR服务'); await _reinitializeAsrService(); } currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); // // 初始化新的翻译项 // final newItem = TranslationItem( // sourceText: '', // translatedText: '', // sourceLanguageCode: sourceLanguageCode.value, // targetLanguageCode: targetLanguageCode.value, // timestamp: DateTime.now(), // sessionId: currentSessionId!, // isFirstInSession: true, // isIntermediate: true, // ); // // 添加到历史记录 // translationHistory.add(newItem); // 开始连续语音识别 if (currentMode.value == "audioVideo") { _bleManager.openDecoder(); // 发送ble音乐或者通话远端声音 _audioSourceType = true; isTtsEnabled.value = false; // 禁用TTS以避免干扰 Logger.info('发送ble音乐或者通话远端声音'); } else if (currentMode.value == "call") { // 发送ble系统mic和dac(音乐或者通话远端)声音 _bleManager.openA2DPDecoder(); _audioSourceType = true; isTtsEnabled.value = true; Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音'); } else { // 开始连续语音识别 _audioSourceType = false; isTtsEnabled.value = true; } final recognitionStream = await _asrService.startContinuousRecognition(_audioSourceType); isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); await _asrService.enableRecord( "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); } // 监听识别结果 _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); } catch (e) { isRecognizing.value = false; Logger.error('启动语音识别失败: ${e.toString()}'); } } // 处理识别事件 void _handleRecognitionEvent(RecognitionEvent event) { if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; handleFinalResult(event.text); } else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) { if (event.detectedLanguage.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; } handleIntermediateResult(event.text); } else if (event.type == RecognitionEventType.error) { isRecognizing.value = false; Logger.error('语音识别错误: ${event.error}'); } else if (event.type == RecognitionEventType.canceled) { isRecognizing.value = false; Logger.error('语音识别取消: ${event.error}'); } } // 停止语音识别 Future stopRecognition() async { if (!isRecognizing.value) return; try { await _asrService.stopContinuousRecognition(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; isRecognizing.value = false; // 如果当前有中间结果,将其作为最终结果处理 if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { // 标记为最终结果 translationHistory.last.isIntermediate = false; translationHistory.refresh(); // 如果还没有翻译结果,尝试翻译 if (translationHistory.last.translatedText.isEmpty) { await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true); } saveTranslationHistory(); } currentSessionId = null; } catch (e) { Logger.error('停止语音识别失败: ${e.toString()}'); } } // 处理中间识别结果 void handleIntermediateResult(String text) { if (text.isEmpty) return; if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) { currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: sourceLanguageCode.value, targetLanguageCode: targetLanguageCode.value, timestamp: DateTime.now(), sessionId: currentSessionId!, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isIntermediate: true, ); translationHistory.add(newItem); // 添加新项后滚动到底部 _scrollToBottom(); } else { // 更新当前项 translationHistory.last.sourceText = text; translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); } // 防抖处理翻译请求 _translationDebounceTimer?.cancel(); _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false); } }); } // 处理最终识别结果 Future handleFinalResult(String text) async { if (text.isEmpty || translationHistory.isEmpty) return; // 更新当前项 if (translationHistory.last.isIntermediate) { translationHistory.last.sourceText = text; translationHistory.last.isIntermediate = false; translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); saveTranslationHistory(); // 使用项目的时间戳进行翻译 translateText(text, translationHistory.last.timestamp, isFinal: true); } else { // 如果最后一项不是中间结果,创建新项 final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: sourceLanguageCode.value, targetLanguageCode: targetLanguageCode.value, timestamp: DateTime.now(), sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(), isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isIntermediate: false, ); translationHistory.add(newItem); translationHistory.refresh(); _scrollToBottom(); saveTranslationHistory(); // 使用新项目的时间戳进行翻译 translateText(text, newItem.timestamp, isFinal: true); } } // 翻译文本 Future translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async { if (sourceText.isEmpty) return null; isTranslating.value = true; String? translationResult; try { // 根据检测到的语言确定源语言和目标语言 final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; final detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; final detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; // 调用翻译服务 translationResult = await _translationService.translateText( text: sourceText, sourceLanguageCode: detectedSourceLanguageCode, targetLanguageCode: detectedTargetLanguageCode, ); if (translationResult != null) { // 查找匹配的项目 - 优先使用timestamp查找 int index = -1; index = translationHistory .indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp)); if (index != -1) { // 更新历史项 translationHistory[index].translatedText = translationResult; translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; translationHistory.refresh(); saveTranslationHistory(); // 更新后滚动到底部 _scrollToBottom(); // 播放TTS - 只在最终结果时播放 if (isTtsEnabled.value && isFinal) { // Logger.info('播放翻译文本: ${translationResult}'); await playTranslatedText(translationResult); } } else { Logger.info( '未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); } } } catch (e) { Logger.error('翻译失败: ${e.toString()}'); } finally { isTranslating.value = false; } return translationResult; } // 播放翻译文本 Future playTranslatedText(String text) async { if (text.isEmpty) return; try { final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value; final detectedTargetLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; final voiceName = _languageManager .getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(text); } catch (e) { Logger.error('播放翻译失败: ${e.toString()}'); } } // 停止所有操作 Future stopAll() async { try { if (currentMode.value == "audioVideo" || currentMode.value == "call") { _bleManager.closeCodec(); // 发送ble音乐或者通话远端声音 Logger.info('发送ble音乐或者通话远端声音'); } if (isRecognizing.value) { await stopRecognition(); } await _asrService.stopRecord(true); _translationDebounceTimer?.cancel(); await _ttsService.stop(); } catch (e) { Logger.error('停止操作失败: ${e.toString()}'); } } // 播放特定翻译项 Future playTranslationItem(TranslationItem item) async { if (item.translatedText.isEmpty) return; try { final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(item.translatedText); } catch (e) { Logger.error('播放翻译项失败: ${e.toString()}'); } } // 播放文本 Future playText(String text, {String? languageCode}) async { if (text.isEmpty) return; try { final voiceName = languageCode != null ? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural' : _languageManager .getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(text); } catch (e) { Logger.error('播放文本失败: ${e.toString()}'); } } // 滚动到底部 void _scrollToBottom() { if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { scrollController.animateTo( scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } catch (e) { Logger.error('滚动到底部失败: ${e.toString()}'); } } } // 设置滚动监听器(简化版,不再需要实际监听) void setupScrollListener() { // 简化后的空方法,保留接口兼容性 } // 底部控制栏高度 double _bottomBarHeight = 0.0; // 设置底部控制栏高度 void setBottomBarHeight(double height) { _bottomBarHeight = height; } // 获取底部控制栏高度 double get bottomBarHeight => _bottomBarHeight; // 切换源语言和目标语言 void swapLanguages() { stopAll(); final tempName = sourceLanguage.value; sourceLanguage.value = targetLanguage.value; targetLanguage.value = tempName; final tempCode = sourceLanguageCode.value; sourceLanguageCode.value = targetLanguageCode.value; targetLanguageCode.value = tempCode; // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } // 设置源语言 void setSourceLanguage(String language) { if (sourceLanguage.value != language) { stopAll(); sourceLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { sourceLanguageCode.value = asrCode; } // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } } // 设置目标语言 void setTargetLanguage(String language) { if (targetLanguage.value != language) { stopAll(); targetLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { targetLanguageCode.value = asrCode; } // 保存语言设置 _saveLanguageSettings(); // 更新ASR支持的语言并重新初始化 _reinitializeAsrService(); } } // 重新初始化ASR服务以支持新的语言 Future _reinitializeAsrService() async { try { // 更新ASR支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); // 重新初始化ASR服务 await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); Logger.info('ASR服务重新初始化成功'); } catch (e) { Logger.error('重新初始化ASR服务失败: ${e.toString()}'); } } // 加载翻译历史 void loadTranslationHistory() { final historyJson = _storage.read(_historyKey); if (historyJson != null) { try { final List historyList = jsonDecode(historyJson); final List allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); // 只保留最新的10条记录 if (allHistory.length > _maxHistoryItems) { translationHistory.value = allHistory.sublist(allHistory.length - _maxHistoryItems); } else { translationHistory.value = allHistory; } // 使用微任务确保在视图完全渲染后滚动到底部 WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToBottom(); }); } catch (e) { _storage.remove(_historyKey); Logger.error('加载翻译历史失败: ${e.toString()}'); } } } // 保存翻译历史 void saveTranslationHistory() { if (translationHistory.length > _maxHistoryItems) { translationHistory.removeRange( 0, translationHistory.length - _maxHistoryItems); } try { final List> historyJson = translationHistory.map((item) => item.toJson()).toList(); _storage.write(_historyKey, jsonEncode(historyJson)); } catch (e) { Logger.error('保存翻译历史失败: ${e.toString()}'); } } // 清除翻译历史 void clearTranslationHistory() { translationHistory.clear(); _storage.remove(_historyKey); } // 滚动到底部 (公共方法,用于向后兼容) void scrollToBottom({bool animate = true}) { _scrollToBottom(); } /// 处理原生语音交互开始事件 void _handleVoiceInteractionStarted() { // 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS stopAll(); } } // 翻译项模型 class TranslationItem { String sourceText; String translatedText; String sourceLanguageCode; String targetLanguageCode; final DateTime timestamp; String sessionId; final bool isFirstInSession; bool isIntermediate; // 是否为中间结果 TranslationItem({ required this.sourceText, required this.translatedText, required this.sourceLanguageCode, required this.targetLanguageCode, required this.timestamp, required this.sessionId, this.isFirstInSession = false, this.isIntermediate = false, }); factory TranslationItem.fromJson(Map json) { DateTime timestamp; try { timestamp = DateTime.parse(json['timestamp']); } catch (e) { timestamp = DateTime.now(); } return TranslationItem( sourceText: json['sourceText'] ?? '', translatedText: json['translatedText'] ?? '', sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN', targetLanguageCode: json['targetLanguageCode'] ?? 'en-US', timestamp: timestamp, sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), isFirstInSession: json['isFirstInSession'] ?? false, isIntermediate: false, // 从存储加载的都是最终结果 ); } Map toJson() { return { 'sourceText': sourceText, 'translatedText': translatedText, 'sourceLanguageCode': sourceLanguageCode, 'targetLanguageCode': targetLanguageCode, 'timestamp': timestamp.toIso8601String(), 'sessionId': sessionId, 'isFirstInSession': isFirstInSession, // 不保存isIntermediate,因为存储的都是最终结果 }; } // 获取源语言的中文名称 String get sourceLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? '未知语言'; } // 获取目标语言的中文名称 String get targetLanguageName { final languageManager = Get.find(); return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? '未知语言'; } }