import 'dart:io'; import '../../../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/ast_service.dart'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/music_manager.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/services/asr_service.dart'; import '../../../data/services/translation_usage_service.dart'; import '../../../data/services/ble_manager.dart'; import 'record_timer_controller.dart'; import 'translation_history_controller.dart'; import '../models/translation_models.dart'; // 切换音频输出设备的枚举 enum AudioOutputDevice { defaultDevice, // 默认(如有耳机优先耳机,否则扬声器) headphones, // 强制耳机 speaker, // 强制扬声器 } class TranslationController extends GetxController { static const String TAG = 'TranslationController'; // 服务 final AsrService _asrService = Get.find(); final VolcanoTranslationService _translationService = Get.find(); final TtsService _ttsService = Get.find(); final AstService _astService = Get.find(); final LanguageManager _languageManager = Get.find(); final GetStorage _storage = GetStorage(); // 蓝牙服务 final BluetoothManager bluetoothManager = Get.find(); final BleManager bleManager = Get.find(); //音乐播放管理 final _musiceManager = Get.find(); // 统计服务 late final TranslationUsageService _usageService; // ASR统计相关 - 精确时长统计 DateTime? _asrSessionStartTime; // ASR会话开始时间 DateTime? _asrActiveStartTime; // ASR活跃开始时间(用户实际开始语音识别的时间) Duration _asrActiveDuration = Duration.zero; // ASR累计活跃时长 bool _isAsrActive = false; // ASR是否处于活跃状态 // 音频输入源 bool _audioSourceType = false; //是否开启播放 bool isPlayback = false; // 存储相关配置 static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; // 分页加载相关状态 final pageSize = 20; // 每页记录数量 bool _scrollListenerSetup = false; // 滚动监听器设置标志 RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2 RxInt lastActiveSpeaker = 0.obs; // 上一次状态 bool isContinuePlaying = false; // 是否继续播放 // 状态变量 final isRecognizing = false.obs; final isTranslating = false.obs; final isTtsEnabled = true.obs; final isRecording = false.obs; final lasyIsRecording = false.obs; final hasRecordPermission = 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; // 翻译历史和当前项 String? fTFTranslationResult = ''; String? currentSessionId; final appDocDir = getApplicationDocumentsDirectory(); var appDir; var dir; // 使用新的管理器 late final RecordTimerController _timerManager; late final TranslationHistoryManager _historyManager; // 提供访问器 RxString get recordDurationText => _timerManager.recordDurationText; RxList get translationHistory => _historyManager.translationHistory; ScrollController get scrollController => _historyManager.scrollController; // 添加历史管理器状态访问器 RxBool get hasMoreHistory => _historyManager.hasMoreHistory; RxBool get isLoadingMore => _historyManager.isLoadingMore; RxBool get isLoadingMoreError => _historyManager.isLoadingMoreError; // 其他 StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; Timer? _translationDebounceTimer; // 获取支持的语言列表 Map get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); // 最新的语音识别文本 final latestRecognitionText = ''.obs; // 检测到的语言代码 final detectedLanguageCode1 = ''.obs; //int num = 0; Future changeTranslationMode(String mode) async { currentMode.value = mode; currentModeTitle.value = _getModeTitle(mode); stopAll(); if (mode == 'call') { _initializeCallModeTranslationService(); } } String _getModeTitle(String mode) { switch (mode) { case 'audioVideo': return 'audioVideoTranslation'; case 'call': return 'callTranslation'; case 'faceToFace': return 'faceToFaceTranslation'; default: return 'simultaneousTranslation'; } } // 更新计时器相关方法 void pauseRecordTimer() => _timerManager.pauseTimer(); void resumeRecordTimer() => _timerManager.resumeTimer(); void resetRecordTimer() => _timerManager.resetTimer(); Future setActiveSpeaker(int id) async { activeSpeaker.value = id; await _asrService.startContinuousRecognition(_audioSourceType); if (activeSpeaker.value != 0) { await _handleActiveSpeakerOn(id); } else { await _handleActiveSpeakerOff(); } } // 拆分出的辅助方法 Future _handleActiveSpeakerOn(int id) async { isPlayback = false; isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { await _handleRecordingStart(); } _startAsrActiveTracking(); lastActiveSpeaker.value = id; } Future _handleRecordingStart() async { if (lasyIsRecording.value != isRecording.value) { lasyIsRecording.value = isRecording.value; await startRecording(); } else { await _asrService.resumeRecord(); resumeRecordTimer(); } } Future _handleActiveSpeakerOff() async { _stopAsrActiveTracking(); if (isRecording.value && isRecognizing.value) { pauseRecordTimer(); await _asrService.pauseRecord(); } await _handleTranslationPlayback(); } Future _handleTranslationPlayback() async { if (fTFTranslationResult != '' && !isPlayback) { isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; } else if ((translationHistory.isEmpty || !translationHistory.last.isIntermediate) && currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } } // 禁用蓝牙音频 Future disableBluetoothAudio() async => await _asrService.disableBluetoothAudio(); //恢复原始音频状态 Future restoreOriginalAudioState() async => await _asrService.restoreOriginalAudioState(); // 兼容旧版视图的计算属性 Rx get currentItem { if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { return Rx(translationHistory.last); } return Rx(null); } @override void onInit() { super.onInit(); // 初始化管理器 _timerManager = RecordTimerController(); _historyManager = TranslationHistoryManager(); //_setupEventListeners(); // 预先检查权限,避免在用户交互时阻塞 _preCheckPermissions(); // 初始化统计服务 _initUsageService(); // 加载上次保存的语言设置 _loadSavedLanguages(); // 初始化语言代码 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(); // 初始化历史管理器 _historyManager.initialize(); } /// 预先检查权限 Future _preCheckPermissions() async { try { await requestRecordPermission(); } catch (e) { Logger.e("Permission", "预检查权限失败: $e"); } } // 初始化统计服务 void _initUsageService() { try { // 确保统计服务已注册 if (!Get.isRegistered()) { Get.put(TranslationUsageService(), permanent: true); } _usageService = Get.find(); Logger.info('翻译统计服务初始化完成'); } catch (e) { Logger.error('翻译统计服务初始化失败: $e'); } } // 加载保存的语言设置 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 { hasRecordPermission.value = await PermissionUtil.instance.requestPermission( permissionType: Permission.microphone, permissionName: 'microphonePermission'.tr, explanationText: 'microphonePermissionExplanation'.tr, permanentDenialText: 'microphonePermissionPermanentDenial'.tr, ); if (hasRecordPermission.value) { Logger.d("Permission", "用户授予了录音权限"); return true; } else { Logger.d("Permission", "用户未授予录音权限"); return false; } } catch (e) { Logger.e("Permission", "请求录音权限时出错: $e"); return false; } } // 请求存储权限 Future _requestStoragePermission() async { // final BuildContext context = Get.overlayContext!; try { final bool granted = await PermissionUtil.instance.requestPermission( permissionType: Permission.storage, permissionName: 'storagePermission'.tr, explanationText: 'storagePermissionExplanation'.tr, permanentDenialText: 'storagePermissionPermanentDenial'.tr, ); if (granted) { Logger.d("Permission", "用户授予了存储权限"); return true; } else { Logger.d("Permission", "用户未授予存储权限"); return true; } } catch (e) { Logger.e("Permission", "请求存储权限时出错: $e"); return false; } } // 暂停其他服务 Future suspendOtherServices() async { try { var status = await _musiceManager.getStatus(); var isPlaying = status['isPlaying'] ?? false; if (isPlaying == true) { isContinuePlaying = true; await _musiceManager.pause(); // 暂停音乐 } else { isContinuePlaying = false; } } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } // 恢复其他服务 Future restoreOtherServices() async { try { if (isContinuePlaying) await _musiceManager.resume(); // 暂停音乐 } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } // 初始化服务 Future _initServices() async { try { await _translationService.initialize(); // 初始化 ASR 服务时,明确指定需要支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); // 获取识别事件流 - 这启动了异步识别过程 var recognitionStream = await _asrService.recognizeCallback(); // 监听识别结果 _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); final bool storage = await _requestStoragePermission(); if (!storage) { Logger.d("Permission", "未授予存储权限,无法提供录音功能"); return; } 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()}'); } } // 初始化通话模式的语音翻译服务 Future _initializeCallModeTranslationService() async { try { Logger.info('开始初始化通话模式语音翻译服务'); // 为通话模式配置特殊的ASR设置 final List callModeLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; // 重新初始化ASR服务以支持通话音频源 await _astService.initialize(supportedLanguages: callModeLanguages); Logger.info('通话模式语音翻译服务初始化完成'); return; } catch (e) { Logger.error('通话模式语音翻译服务初始化失败: ${e.toString()}'); } } @override void onClose() { stopAll(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; _voiceInteractionSubscription?.cancel(); _translationDebounceTimer?.cancel(); restoreOtherServices(); _asrService.dispose(); _astService.dispose(); // 释放管理器资源 _timerManager.dispose(); _historyManager.dispose(); super.onClose(); } // 切换TTS功能 void toggleTts() => isTtsEnabled.toggle(); // 切换录音功能 Future toggleisRecord() async { isRecording.toggle(); if (isRecording.value && isRecognizing.value) { startRecording(); } else if (!isRecording.value) { stopRecording(); } else {} } Future startRecording() async { //isRecording.value = true; _timerManager.startTimer(); // 启动实际录音逻辑 // ... final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); await _asrService.enableRecord( "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); if (currentMode.value == 'call') { await _astService.enableRecord( "${dir.path}/${currentModeTitle.value.tr}_${formattedTime}_mic.wav"); } return; } Future stopRecording() async { // 停止计时器 _timerManager.stopTimer(); _asrService.stopRecord(true); lasyIsRecording.value = false; Logger.info('录音已停止,计时器已重置'); } // 开始语音识别 Future startRecognition() async { Logger.info('开始语音识别'); if (!await _checkPermissions()) return; if (isRecognizing.value) return; try { await _initializeSession(); await _configureAudioMode(); await _startAsrService(); await _finalizeRecognitionStart(); } catch (e) { isRecognizing.value = false; Logger.error('启动语音识别失败: ${e.toString()}'); } } // 拆分出的辅助方法 Future _checkPermissions() async { final bool record = await requestRecordPermission(); if (!record) { Logger.d("Permission", "录音权限被拒绝,无法开始录音"); return false; } return true; } Future _initializeSession() async { currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); try { _usageService.startNewSession(); _asrSessionStartTime = DateTime.now(); _asrActiveDuration = Duration.zero; _isAsrActive = false; } catch (e) { Logger.error('开始新会话统计失败: $e'); } } Future _configureAudioMode() async { switch (currentMode.value) { case "audioVideo": await _configureAudioVideoMode(); break; case "call": await _configureCallMode(); break; default: await _configureDefaultMode(); } } Future _configureAudioVideoMode() async { bleManager.openDecoder(); _audioSourceType = true; isTtsEnabled.value = false; Logger.info('发送ble音乐或者通话远端声音'); } Future _configureCallMode() async { bleManager.openA2DPDecoder(); _audioSourceType = true; isTtsEnabled.value = false; await _astService.startContinuousTranslation(); Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音'); } Future _configureDefaultMode() async { _audioSourceType = false; isTtsEnabled.value = true; } Future _startAsrService() async { if (bluetoothManager.currentDeviceRx.value != null) { restoreOriginalAudioState(); } else { disableBluetoothAudio(); } await _asrService.startContinuousRecognition(_audioSourceType); } Future _finalizeRecognitionStart() async { _startAsrActiveTracking(); isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { startRecording(); } } // 处理识别事件 void _handleRecognitionEvent(RecognitionEvent event) { if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; print("处理识别事件:${event.text}"); handleFinalResult(event.text); } else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) { if (event.detectedLanguage.isNotEmpty) { detectedLanguageCode = event.detectedLanguage; } print("处理识别事件:${event.text}"); handleIntermediateResult(event.text); } else if (event.type == RecognitionEventType.finalResult1 && event.text.isNotEmpty) { if (event.detectedLanguage.isNotEmpty) { detectedLanguageCode1.value = event.detectedLanguage; } latestRecognitionText.value = event.text; print("处理麦:${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 stopRecording(); await _asrService.stopContinuousRecognition(); if (currentMode.value == "call") { await _astService.stopContinuousTranslation(); } // 停止ASR活跃时长计时 _stopAsrActiveTracking(); 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, ); if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { _asrService.stopContinuousRecognition(); } else { _historyManager.addTranslationItem(newItem); } } else { // 更新当前项 translationHistory.last.sourceText = text; translationHistory.refresh(); _historyManager.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; // ASR识别到最终结果,表示用户完成了一次有效的语音输入 // 更新当前项 if (translationHistory.last.isIntermediate) { translationHistory.last.sourceText = text; translationHistory.last.isIntermediate = false; translationHistory.refresh(); // 更新后滚动到底部 _scrollToBottom(); saveTranslationHistory(); if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') { _asrService.stopContinuousRecognition(); } // 使用项目的时间戳进行翻译 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, ); if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { _asrService.stopContinuousRecognition(); } //else { 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 languageCodes = _determineLanguageCodes(); translationResult = await _performTranslation( sourceText, languageCodes['source']!, languageCodes['target']!); if (translationResult != null) { await _updateTranslationHistory( sourceText, translationResult, timestamp, languageCodes, isFinal); } } catch (e) { Logger.error('翻译失败: ${e.toString()}'); } finally { isTranslating.value = false; } return translationResult; } // 拆分出的辅助方法 Map _determineLanguageCodes() { String detectedSourceLanguageCode; String detectedTargetLanguageCode; if (_isFaceToFaceSpeaker1()) { detectedLanguageCode = sourceLanguageCode.value; detectedSourceLanguageCode = sourceLanguageCode.value; detectedTargetLanguageCode = targetLanguageCode.value; } else if (_isFaceToFaceSpeaker2()) { detectedLanguageCode = targetLanguageCode.value; detectedSourceLanguageCode = targetLanguageCode.value; detectedTargetLanguageCode = sourceLanguageCode.value; } else { final shouldSwap = detectedLanguageCode == sourceLanguageCode.value; detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; } return { 'source': detectedSourceLanguageCode, 'target': detectedTargetLanguageCode, }; } bool _isFaceToFaceSpeaker1() { return (currentMode.value == 'faceToFace' && activeSpeaker.value == 1) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 1); } bool _isFaceToFaceSpeaker2() { return (currentMode.value == 'faceToFace' && activeSpeaker.value == 2) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 2); } Future _performTranslation( String sourceText, String sourceCode, String targetCode) async { return await _translationService.translateText( text: sourceText, sourceLanguageCode: sourceCode, targetLanguageCode: targetCode, ); } Future _updateTranslationHistory( String sourceText, String translationResult, DateTime timestamp, Map languageCodes, bool isFinal, ) async { final index = translationHistory .indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp)); if (index != -1) { await _updateExistingHistoryItem( index, translationResult, languageCodes, sourceText, isFinal); } else { Logger.info('未找到匹配的历史项 $sourceText ${timestamp.toIso8601String()}'); } } Future _updateExistingHistoryItem( int index, String translationResult, Map languageCodes, String sourceText, bool isFinal, ) async { // 更新历史项 translationHistory[index].translatedText = translationResult; translationHistory[index].sourceLanguageCode = languageCodes['source']!; translationHistory[index].targetLanguageCode = languageCodes['target']!; translationHistory.refresh(); if (isFinal) { saveTranslationHistory(); await _recordTranslationStats( sourceText, translationResult, languageCodes); } _scrollToBottom(); if (isFinal) { await _handleTtsPlayback(translationResult); } } Future _recordTranslationStats( String sourceText, String translationResult, Map languageCodes, ) async { try { final sourceLanguageName = _languageManager.getChineseNameByAsrCode(languageCodes['source']!) ?? '未知'; final targetLanguageName = _languageManager.getChineseNameByAsrCode(languageCodes['target']!) ?? '未知'; _usageService.recordTranslationApiCall( sourceText: sourceText, targetText: translationResult, sourceLanguage: sourceLanguageName, targetLanguage: targetLanguageName, mode: currentMode.value, ); Logger.info( '翻译统计: 源文本${sourceText.length}字符, 模式${currentMode.value}, 语言对$sourceLanguageName→$targetLanguageName'); _recordAsrUsageStats(sourceLanguageName); } catch (e) { Logger.error('记录翻译统计失败: $e'); } } Future _handleTtsPlayback(String translationResult) async { if (isTtsEnabled.value && currentMode.value != 'faceToFace') { await playTranslatedText(translationResult); } else { await _handleFaceToFaceTts(translationResult); } } Future _handleFaceToFaceTts(String translationResult) async { fTFTranslationResult = (fTFTranslationResult ?? '') + translationResult; print("translationResult=$translationResult"); if (activeSpeaker.value == 0 && !isPlayback) { isPlayback = true; await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); await Future.delayed(const Duration(milliseconds: 1300)); await playTranslatedText(fTFTranslationResult!); fTFTranslationResult = ''; } } // 播放翻译文本 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'; Logger.info("voiceName=$voiceName,text=$text"); final startTime = DateTime.now(); await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(text); final endTime = DateTime.now(); // 记录TTS API调用统计 - 优化版(基于文本字符数计费) try { final durationSeconds = endTime.difference(startTime).inSeconds; final languageName = _languageManager .getChineseNameByAsrCode(detectedTargetLanguageCode) ?? '未知'; // TTS的主要计费依据是文本字符数,播放时长仅用于分析 _usageService.recordTtsApiCall( text: text, // 文本字符数是TTS计费的主要依据 durationSeconds: durationSeconds, // 播放时长用于用户体验分析 language: languageName, ); Logger.info( 'TTS统计: 文本${text.length}字符, 播放${durationSeconds}秒, 语言$languageName'); } catch (e) { Logger.error('记录TTS统计失败: $e'); } } 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(); } // 确保停止ASR活跃时长跟踪 _stopAsrActiveTracking(); _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()}'); } } // 滚动到底部(适配reverse模式) void _scrollToBottom({bool animate = true}) { if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { // 🔑 reverse模式下:滚动到顶部(position = 0)来显示最新消息 // 正常模式下:滚动到底部(maxScrollExtent)来显示最新消息 const targetPosition = 0.0; // reverse模式下最新消息在顶部 if (animate) { // 有动画的滚动,用于新消息添加时 scrollController.animateTo( targetPosition, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } else { // 无动画的跳转,用于初始化时 scrollController.jumpTo(targetPosition); } } catch (e) { Logger.error('滚动到顶部失败(reverse模式): ${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 { // 停止当前识别 await stopRecognition(); // 更新ASR支持的语言 final List asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); // 重新初始化ASR服务 await _asrService.initialize( supportedLanguages: asrSupportedLanguages, ); // 获取识别事件流 - 这启动了异步识别过程 var recognitionStream = await _asrService.recognizeCallback(); // 监听识别结果 _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); //判断是否进入通话 if (currentMode.value == "call") { // 重新初始化ASR服务以支持通话音频源 _initializeCallModeTranslationService(); } Logger.info('ASR服务重新初始化成功'); } catch (e) { Logger.error('重新初始化ASR服务失败: ${e.toString()}'); } } void loadTranslationHistory() => _historyManager.loadTranslationHistory(); // 更新历史记录相关方法 void saveTranslationHistory() => _historyManager.saveTranslationHistory(); // 设置滚动监听器(适配reverse模式) void _setupScrollListener() { if (_scrollListenerSetup) return; // 防止重复设置 scrollController.addListener(() { // 🔑 reverse模式下:检查是否滚动到底部(实际是历史记录的顶部) // 参考Agent模块的实现 if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) { // 当距离底部100像素时触发加载更多历史记录 if (hasMoreHistory.value && !isLoadingMore.value) { Logger.d(TAG, '触发加载更多翻译历史记录(reverse模式)'); loadMoreHistory(); } } }); _scrollListenerSetup = true; } void clearTranslationHistory() => _historyManager.clearTranslationHistory(); void deleteSessionHistory(String sessionId) => _historyManager.deleteSessionHistory(sessionId); void deleteTranslationItem(String sessionId, DateTime timestamp) => _historyManager.deleteTranslationItem(sessionId, timestamp); List getAllHistory() => _historyManager.getAllHistory(); Future loadMoreHistory() => _historyManager.loadMoreHistory(); void scrollToBottom({bool animate = true}) => _historyManager.scrollToBottom(animate: animate); /// 开始ASR活跃时长跟踪 void _startAsrActiveTracking() { if (!_isAsrActive) { _asrActiveStartTime = DateTime.now(); _isAsrActive = true; Logger.info('开始ASR活跃时长跟踪'); } } /// 停止ASR活跃时长跟踪 void _stopAsrActiveTracking() { if (_isAsrActive && _asrActiveStartTime != null) { final activeDuration = DateTime.now().difference(_asrActiveStartTime!); _asrActiveDuration += activeDuration; _isAsrActive = false; _asrActiveStartTime = null; Logger.info('停止ASR活跃时长跟踪,本次活跃时长: ${activeDuration.inSeconds}秒'); } } /// 记录ASR使用统计 - 基于精确时长跟踪 void _recordAsrUsageStats(String language) { try { if (_asrSessionStartTime == null) return; // 确保停止当前的活跃时长跟踪 _stopAsrActiveTracking(); final now = DateTime.now(); final totalDurationSeconds = now.difference(_asrSessionStartTime!).inSeconds; final effectiveDurationSeconds = _asrActiveDuration.inSeconds; _usageService.recordAsrApiCall( effectiveDurationSeconds: effectiveDurationSeconds, totalDurationSeconds: totalDurationSeconds, language: language, mode: currentMode.value, audioFormat: 'pcm', sampleRate: 16000, ); Logger.info( 'ASR会话统计: 总时长${totalDurationSeconds}秒, 实际活跃时长${effectiveDurationSeconds}秒, 语言$language'); } catch (e) { Logger.error('记录ASR统计失败: $e'); } } }