import 'dart:io'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:intl/intl.dart'; import 'package:native_plugin/native_plugin.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:pip/pip.dart'; import '../../../data/services/ast_service.dart'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/music_manager.dart'; import '../../../data/services/translation_usage_service.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 '../../../core/utils/permission_util.dart'; import '../../../core/services/floating_window_manager.dart'; import '../../../data/services/asr_service.dart'; import '../../../data/services/usage_stats_service.dart'; import '../../../data/services/ble_manager.dart'; import 'record_timer_controller.dart'; import 'translation_history_controller.dart'; import '../models/translation_models.dart'; import '../views/dialogs/translation_mode_selector.dart'; /// 音频输出设备枚举 enum AudioOutputDevice { defaultDevice, // 默认(如有耳机优先耳机,否则扬声器) headphones, // 强制耳机 speaker, // 强制扬声器 } /// 翻译主控制器 - 负责管理语音识别、翻译和TTS功能(主控制器) class TranslationController extends GetxController with WidgetsBindingObserver { 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 UsageStatsService _usageService; // 安卓悬浮窗管理器 late final FloatingWindowManager _floatingWindowManager; // 苹果悬浮窗管理器 late final Pip _pip; late final NativePlugin _nativePlugin; int _pipContentView = 0; // ==================== 悬浮窗相关 ==================== final isFloatingWindowEnabled = false.obs; // 悬浮窗状态 // ==================== ASR统计相关 ==================== DateTime? _asrSessionStartTime; // ASR会话开始时间 DateTime? _asrActiveStartTime; // ASR活跃开始时间(用户实际开始语音识别的时间) Duration _asrActiveDuration = Duration.zero; // ASR累计活跃时长 bool _isAsrActive = false; // ASR是否处于活跃状态 // ==================== 配置相关 ==================== bool _audioSourceType = false; // 音频输入源类型 bool isPlayback = false; // 是否开启播放 bool isContinuePlaying = false; // 是否继续播放 // 存储相关配置 static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; // 添加悬浮按钮位置存储键 static const String _floatingButtonPositionKey = 'floating_button_position'; // 分页加载相关状态 final pageSize = 20; // 每页记录数量 bool _scrollListenerSetup = false; // 滚动监听器设置标志 // ==================== 状态变量 ==================== RxInt activeSpeaker = 0.obs; // 0=无激活, 1=按钮1, 2=按钮2 RxInt lastActiveSpeaker = 0.obs; // 上一次状态 final isRecognizing = false.obs; final isPreparing = false.obs; final isTranslating = false.obs; final isTtsEnabled = true.obs; final isRecording = false.obs; final isCreateRecord = false.obs; final hasRecordPermission = false.obs; // 添加悬浮按钮位置状态 final floatingButtonPosition = Rx(Offset(2.0, 100.0)); // 默认位置 // ==================== 语言相关 ==================== final sourceLanguage = '中文(简体)'.obs; final targetLanguage = '英语'.obs; final sourceLanguageCode = 'zh-CN'.obs; final targetLanguageCode = 'en-US'.obs; var detectedLanguageCode = ''; final detectedLanguageCode1 = ''.obs; final latestRecognitionText = ''.obs; final faceToFaceRecognitionText = ''.obs; // ==================== 翻译相关 ==================== var currsessionid = ''; 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; Map get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); // ==================== 流订阅 ==================== StreamSubscription? _recognitionSubscription; StreamSubscription? _voiceInteractionSubscription; Timer? _translationDebounceTimer; // 播放防重复机制 String? _lastPlayedItemKey; // 最后播放的项目标识 int _lastPlayTime = 0; // 最后播放时间 static const int _playDebounceInterval = 500; // 防抖间隔:500毫秒 // 底部控制栏高度 double _bottomBarHeight = 0.0; /// 初始化安卓悬浮窗管理器 void _initFloatingWindowManager() { try { if (!Get.isRegistered()) { Get.put(FloatingWindowManager(), permanent: true); } _floatingWindowManager = Get.find(); Logger.info('悬浮窗管理器初始化完成'); } catch (e) { Logger.error('悬浮窗管理器初始化失败: $e'); } } /// 初始化苹果悬浮窗管理器 Future initPlatformState() async { _pip = Pip(); _nativePlugin = NativePlugin(); try { isFloatingWindowEnabled.value = await _pip.isActived(); await _pip.registerStateChangedObserver( PipStateChangedObserver( onPipStateChanged: (state, error) { isFloatingWindowEnabled.value = state == PipState.pipStateStarted; if (state == PipState.pipStateFailed) { _pip.dispose(); } }, ), ); } on PlatformException { isFloatingWindowEnabled.value = false; } } /// 切换悬浮窗功能 Future toggleFloatingWindow() async { if (isFloatingWindowEnabled.value) { if (Platform.isAndroid) { await _disableFloatingWindow(); } else if (Platform.isIOS) { await _pip.dispose(); } } else { if (Platform.isAndroid) { await _enableFloatingWindow(); } else if (Platform.isIOS) { await _startPip(); } } } Future _startPip() async { if (_pipContentView == 0) { _pipContentView = await _nativePlugin.createPipContentView(); } final options = PipOptions( autoEnterEnabled: true, aspectRatioX: 375, aspectRatioY: 400, sourceRectHintLeft: 0, sourceRectHintTop: 0, sourceRectHintRight: 0, sourceRectHintBottom: 0, seamlessResizeEnabled: true, useExternalStateMonitor: true, externalStateMonitorInterval: 100, contentView: _pipContentView, preferredContentWidth: 375, preferredContentHeight: 400, controlStyle: 2, ); await _pip.setup(options); await _pip.start(); } /// 启用悬浮窗功能 Future _enableFloatingWindow() async { // 检查是否在支持悬浮窗的模式 if (!_isFloatingWindowSupportedMode()) { // Get.snackbar( // 'unsupportedMode'.tr, // '悬浮窗仅支持通话和音视频翻译模式', // snackPosition: SnackPosition.TOP, // ); return; } final bool success = await _floatingWindowManager.enableFloatingWindow(); if (success) { isFloatingWindowEnabled.value = true; await _floatingWindowManager.showFloatingWindow(); // 如果当前有翻译内容,立即更新到悬浮窗 if (translationHistory.isNotEmpty) { final latestItem = translationHistory.last; await _updateFloatingWindowWithTranslation(latestItem); } // Get.snackbar( // '悬浮窗已启用', // '翻译结果将在悬浮窗中显示', // snackPosition: SnackPosition.TOP, // ); } } /// 禁用悬浮窗功能 Future _disableFloatingWindow() async { await _floatingWindowManager.disableFloatingWindow(); isFloatingWindowEnabled.value = false; // Get.snackbar( // '悬浮窗已禁用', // '翻译结果将在应用内显示', // snackPosition: SnackPosition.TOP, // ); } /// 检查当前模式是否支持悬浮窗 bool _isFloatingWindowSupportedMode() { return currentMode.value == 'call' || currentMode.value == 'audioVideo'; } /// 更新悬浮窗翻译内容 Future _updateFloatingWindowWithTranslation( TranslationItem item) async { if (!isFloatingWindowEnabled.value) return; final sourceLanguageName = _languageManager.getChineseNameByAsrCode(item.sourceLanguageCode) ?? 'unknown'; final targetLanguageName = _languageManager.getChineseNameByAsrCode(item.targetLanguageCode) ?? 'unknown'; if (Platform.isAndroid) { final data = FloatingWindowData( sourceText: item.sourceText, translatedText: item.translatedText, sourceLanguage: sourceLanguageName, targetLanguage: targetLanguageName, timestamp: item.timestamp, isIntermediate: item.isIntermediate, ); await _floatingWindowManager.updateFloatingWindowContent(data); } else if (Platform.isIOS) { await _nativePlugin.updatePipContentView({ 'viewId': _pipContentView, 'cn': item.sourceText, 'en': item.translatedText, 'lang': '$sourceLanguageName -> $targetLanguageName', }); } } /// 更改翻译模式 /// [mode] 翻译模式:simultaneous(同声传译)、audioVideo(音视频)、call(通话)、faceToFace(面对面) Future changeTranslationMode(String mode) async { currentMode.value = mode; currentModeTitle.value = _getModeTitle(mode); _historyManager.setMode(mode); loadTranslationHistory(); //stopAll(); // 检查悬浮窗支持情况 if (isFloatingWindowEnabled.value && !_isFloatingWindowSupportedMode()) { // 当前模式不支持悬浮窗,自动禁用 await _disableFloatingWindow(); // Get.snackbar( // '模式已切换', // '悬浮窗仅支持通话和音视频翻译模式', // snackPosition: SnackPosition.TOP, // ); } else if (!isFloatingWindowEnabled.value && _isFloatingWindowSupportedMode()) { // 切换到支持悬浮窗的模式,提示用户可以启用 // Get.snackbar( // '模式已切换', // '可以启用悬浮窗显示翻译结果', // snackPosition: SnackPosition.TOP, // duration: const Duration(seconds: 4), // ); } if (mode == 'call') { await _initializeCallModeTranslationService(); } else { _reinitializeAsrService(); } } /// 根据模式获取标题 /// [mode] 翻译模式 /// 返回对应的标题字符串 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(); /// 设置激活的说话者(支持按住说话模式) /// [id] 说话者ID:0=无激活, 1=按钮1, 2=按钮2 Future setActiveSpeaker(int id) async { await _handleFaceToFaceHoldToTalk(id); } /// 处理面对面翻译的按住说话逻辑 /// [id] 说话者ID Future _handleFaceToFaceHoldToTalk(int id) async { if (id == 0) { // 松开按钮,停止当前激活的说话者 if (activeSpeaker.value != 0) { lastActiveSpeaker.value = activeSpeaker.value; // 记录上一次激活的说话者 activeSpeaker.value = id; await _stopFaceToFaceRecognition(); } } else { // 按下按钮,只有在没有其他按钮激活时才能激活 if (activeSpeaker.value == 0) { activeSpeaker.value = id; await _startFaceToFaceRecognition(id); } } } /// 开始面对面翻译的语音识别 /// [speakerId] 说话者ID Future _startFaceToFaceRecognition(int speakerId) async { try { if (_asrSessionStartTime == null) { await _initializeSession(); } await _asrService.startContinuousRecognition(_audioSourceType); //开启识别 _startAsrActiveTracking(); //开启识别活动跟踪 if (isRecording.value) { if (isCreateRecord.value) { //已经创建文件 await continueRecording(); } else { await startRecording(); } } Logger.info('面对面翻译:激活说话者 $speakerId'); } catch (e) { Logger.error('启动面对面翻译失败: ${e.toString()}'); activeSpeaker.value = 0; } } /// 停止面对面翻译的语音识别 Future _stopFaceToFaceRecognition() async { try { if (isRecording.value) { await pauseRecording(); //暂停 } if (faceToFaceRecognitionText.value.isNotEmpty) { handleFinalResult(faceToFaceRecognitionText.value); faceToFaceRecognitionText.value = ''; } await _asrService.stopContinuousRecognition(); _stopAsrActiveTracking(); //停止识别活动跟踪 _recordAsrUsageStats(sourceLanguage.value); isRecognizing.value = false; currentSessionId = null; Logger.info('面对面翻译:停止语音识别'); } catch (e) { Logger.error('停止面对面翻译失败: ${e.toString()}'); } } /// 获取当前翻译项(兼容旧版视图) 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(); if (Platform.isAndroid) { // 初始化安卓悬浮窗管理器 _initFloatingWindowManager(); } else if (Platform.isIOS) { // 初始化苹果悬浮窗管理器 initPlatformState(); } // 预先检查权限,避免在用户交互时阻塞 _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; } // 初始化翻译模式(用于历史隔离的初始加载) final initMode = Get.arguments?['translationId'] ?? 'simultaneous'; _historyManager.setMode(initMode); // 按初始模式加载历史 loadTranslationHistory(); // 初始化服务 _initServices(); // 初始化历史管理器 _historyManager.initialize(); // 应用模式逻辑(含ASR/TTS配置等) changeTranslationMode(initMode); // 监听应用生命周期:当应用切到后台/失焦时兜底重置 activeSpeaker WidgetsBinding.instance.addObserver(this); } // @override // void onReady() { // super.onReady(); // // 初始化翻译模式,根据跳转参数 // final mode = Get.arguments?['translationId'] ?? 'simultaneous'; // changeTranslationMode(mode); // } /// 预先检查权限 Future _preCheckPermissions() async { try { await requestRecordPermission(); } catch (e) { Logger.e("Permission", "预检查权限失败: $e"); } } /// 初始化用量统计服务 void _initUsageService() { try { // 确保统计服务已注册 if (!Get.isRegistered()) { Get.put(UsageStatsService(), 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; } // 加载悬浮按钮位置 _loadFloatingButtonPosition(); } /// 加载悬浮按钮位置 void _loadFloatingButtonPosition() { final savedPosition = _storage.read(_floatingButtonPositionKey); if (savedPosition != null && savedPosition is Map) { final dx = savedPosition['dx']?.toDouble() ?? 2.0; final dy = savedPosition['dy']?.toDouble() ?? 100.0; floatingButtonPosition.value = Offset(dx, dy); } } /// 保存悬浮按钮位置 void saveFloatingButtonPosition(Offset position) { floatingButtonPosition.value = position; _storage.write(_floatingButtonPositionKey, { 'dx': position.dx, 'dy': position.dy, }); } /// 保存当前语言设置 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 { // 只在Android 10及以下版本请求存储权限 if (Platform.isAndroid) { try { final deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; final sdkVersion = androidInfo.version.sdkInt; // Android 11+ (API 30+) 不需要存储权限 if (sdkVersion >= 30) { Logger.d("Permission", "Android 11+版本,无需请求存储权限"); return true; // 直接返回true,表示可以使用存储 } } catch (e) { Logger.e("Permission", "获取Android版本信息失败: $e"); // 如果获取版本失败,为了兼容性还是请求权限 } } 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('2初始化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; dir = Directory("${appDir.path}/TranslatAudio"); if (!await dir.exists()) { await dir.create(recursive: true); } Logger.info('所有服务初始化完成'); } catch (e) { Logger.error('初始化服务失败: ${e.toString()}'); } } /// 初始化通话模式的语音翻译服务 Future _initializeCallModeTranslationService() async { try { Logger.info('开始初始化通话模式语音翻译服务'); // 初始化 ASR 服务时,明确指定需要支持的语言 final List asrSupportedLanguages = [targetLanguageCode.value]; Logger.info('1初始化ASR服务,支持语言: $asrSupportedLanguages'); await _asrService.initialize(supportedLanguages: asrSupportedLanguages); // 获取识别事件流 var recognitionStream = await _asrService.recognizeCallback(); _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); final List callModeLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('1初始化AST服务,支持语言: $callModeLanguages'); //ast=asr+translate await _astService.initialize(supportedLanguages: callModeLanguages); Logger.info('通话模式语音翻译服务初始化完成'); } catch (e) { Logger.error('通话模式语音翻译服务初始化失败: ${e.toString()}'); } } @override void onClose() { stopAll(); _recognitionSubscription?.cancel(); _recognitionSubscription = null; _voiceInteractionSubscription?.cancel(); _translationDebounceTimer?.cancel(); restoreOtherServices(); // 移除对服务的 dispose,避免频繁销毁/重建造成卡顿 // _asrService.dispose(); // _astService.dispose(); if (Platform.isAndroid) { _disableFloatingWindow(); } else if (Platform.isIOS) { _pip.dispose(); if (_pipContentView != 0) { _nativePlugin.disposePipContentView(_pipContentView); } } // 释放管理器资源 _timerManager.dispose(); _historyManager.dispose(); // 取消应用生命周期监听 WidgetsBinding.instance.removeObserver(this); super.onClose(); } /// 应用生命周期变化回调 /// 当应用进入后台、失去焦点或分离时,兜底将 activeSpeaker 重置为 0 @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.inactive || state == AppLifecycleState.paused || state == AppLifecycleState.detached) { if (activeSpeaker.value != 0) { Future.microtask(() { setActiveSpeaker(0); }); } } } /// 切换TTS功能开关 void toggleTts() => isTtsEnabled.toggle(); /// 切换录音功能开关 Future toggleisRecord() async { isRecording.toggle(); if (isRecording.value && isRecognizing.value && currentMode.value != 'faceToFace') { startRecording(); } else if (!isRecording.value) { stopRecording(); } } /// 开始录音 Future startRecording() async { _timerManager.startTimer(); final formattedTime = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); if (currentMode.value == 'call' || currentMode.value == 'audioVideo') { await _asrService.enableRecord( true, "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); } else { await _asrService.enableRecord( false, "${dir.path}/${currentModeTitle.value.tr}_$formattedTime.wav"); } // if (currentMode.value == 'call') { // await _astService.enableRecord( // "${dir.path}/${currentModeTitle.value.tr}_${formattedTime}_mic.wav"); // } isCreateRecord.value = true; } /// 停止录音 Future stopRecording() async { _timerManager.stopTimer(); _asrService.stopRecord(true); isCreateRecord.value = false; Logger.info('录音已停止,计时器已重置'); } /// 暂停录音 Future pauseRecording() async { _timerManager.pauseTimer(); _asrService.pauseRecord(); Logger.info(' 录音已暂停'); } /// 继续录音 Future continueRecording() async { _timerManager.resumeTimer(); _asrService.resumeRecord(); Logger.info(' 录音已继续'); } /// 开始语音识别 Future startRecognition() async { Logger.info('开始语音识别'); if (!await _checkPermissions()) return; if (isRecognizing.value) return; try { await _initializeSession(); await _configureAudioMode(); if (currentMode.value != 'faceToFace') { if ((currentMode.value == 'audioVideo' || currentMode.value == 'call') && !bleManager.isCodecActive) { return; } await _startAsrService(); await _finalizeRecognitionStart(); } } catch (e) { isPreparing.value = false; 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 { isPreparing.value = true; await bleManager.openDecoder(); int attempts = 0; while (!bleManager.isCodecActive && attempts < 10) { await Future.delayed(const Duration(milliseconds: 150)); attempts++; } isPreparing.value = false; if (!bleManager.isCodecActive) { Get.snackbar('tip'.tr, 'operationFailedPleaseRetry'.tr); throw Exception('lxm--BLE编解码打开失败'); } _audioSourceType = true; isTtsEnabled.value = false; Logger.info('发送ble音乐或者通话远端声音'); } /// 配置通话模式 Future _configureCallMode() async { final List callModeLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; Logger.info('1初始化AST服务,支持语言: $callModeLanguages'); //ast=asr+translate await _astService.initialize(supportedLanguages: callModeLanguages); isPreparing.value = true; await bleManager.openA2DPDecoder(); int attempts = 0; while (!bleManager.isCodecActive && attempts < 10) { await Future.delayed(const Duration(milliseconds: 150)); attempts++; } isPreparing.value = false; if (!bleManager.isCodecActive) { Get.snackbar('tip'.tr, 'operationFailedPleaseRetry'.tr); throw Exception('lxm--BLE编解码打开失败'); } _audioSourceType = true; isTtsEnabled.value = false; Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音'); } /// 配置默认模式 Future _configureDefaultMode() async { _audioSourceType = false; isTtsEnabled.value = true; } /// 启动语音识别 Future _startAsrService() async { await _asrService.startContinuousRecognition(_audioSourceType); } /// 完成识别启动 Future _finalizeRecognitionStart() async { _startAsrActiveTracking(); isRecognizing.value = true; if (isRecording.value && isRecognizing.value) { startRecording(); } } /// 处理语音识别事件 /// [event] 识别事件 void _handleRecognitionEvent(RecognitionEvent event) { // Logger.info("处理识别事件:${event}"); switch (event.type) { case RecognitionEventType.finalResult: if (event.text.isEmpty) break; // 通话模式下:目标语言即为识别语言,无需判断 detectedLanguageCode = currentMode.value == 'call' ? targetLanguageCode.value : event.detectedLanguage; Logger.info("处理识别完成:${event.text}"); if (currentMode.value != 'faceToFace') { handleFinalResult(event.text); } else { faceToFaceRecognitionText.value += event.text; Logger.info('lxm-面对面翻译识别文本更新: ${faceToFaceRecognitionText.value}'); } if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { handleFinalResult(faceToFaceRecognitionText.value); faceToFaceRecognitionText.value = ''; } break; case RecognitionEventType.finalResult1: // 通话模式下:己方(麦克风)识别完成事件(对应ast服务) if (event.text.isEmpty) break; //通话模式下:己方语言即为源语言,无需判断 detectedLanguageCode = sourceLanguageCode.value; Logger.info("处理己方识别完成:${event.text}"); handleFinalResult(event.text); break; case RecognitionEventType.intermediateResult: if (event.text.isEmpty) break; // 通话模式下:目标语言即为识别语言,无需判断 detectedLanguageCode = currentMode.value == 'call' ? targetLanguageCode.value : event.detectedLanguage; Logger.info("处理识别中事件:${event.text}"); handleIntermediateResult(event.text); break; case RecognitionEventType.error: Get.snackbar('error'.tr, event.error); isRecognizing.value = false; Logger.error('语音识别错误: ${event.error}'); break; case RecognitionEventType.canceled: isRecognizing.value = false; Logger.error('语音识别取消: ${event.error}'); break; case RecognitionEventType.sessionStarted: // 可按需处理会话开始事件 break; case RecognitionEventType.sessionStopped: // 可按需处理会话结束事件 break; } } /// 停止语音识别 Future stopRecognition() async { if (!isRecognizing.value) return; try { await stopRecording(); await _asrService.stopContinuousRecognition(); if (currentMode.value == "call") { await _astService.stopContinuousTranslation(); } _stopAsrActiveTracking(); final langName = _languageManager.getChineseNameByAsrCode( (detectedLanguageCode.isNotEmpty ? detectedLanguageCode : sourceLanguageCode.value)) ?? '未知'; _recordAsrUsageStats(langName); isRecognizing.value = false; currentSessionId = null; } catch (e) { Logger.error('停止语音识别失败: ${e.toString()}'); } } /// 处理中间识别结果 /// [text] 识别的文本 void handleIntermediateResult(String text) { if (text.isEmpty) return; Logger.info( "handleIntermediateResult:${translationHistory.isEmpty}${translationHistory.isNotEmpty ? translationHistory.last.isIntermediate : 'N/A'}"); if (translationHistory.isEmpty || (translationHistory.isNotEmpty && !translationHistory.last.isIntermediate)) { currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); // 关键调整:使用与最终结果一致的语言方向,避免左右“跳变” final languageCodes = _determineLanguageCodes(); final newItem = TranslationItem( sourceText: text, translatedText: '', sourceLanguageCode: languageCodes['source']!, targetLanguageCode: languageCodes['target']!, 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 { // 更新当前项 if (currentMode.value != 'faceToFace') { translationHistory.last.sourceText = text; } else { translationHistory.last.sourceText = faceToFaceRecognitionText.value + 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); } }); } /// 处理最终识别结果 /// [text] 识别的文本 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(); //如果识别中,且是面对面翻译,则停止识别 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(); } translationHistory.add(newItem); translationHistory.refresh(); _scrollToBottom(); saveTranslationHistory(); translateText(text, newItem.timestamp, isFinal: true); } } /// 翻译文本 /// [sourceText] 源文本 /// [timestamp] 时间戳 /// [isFinal] 是否为最终结果 /// 返回翻译结果 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, }; } /// 判断是否为面对面说话者1 bool _isFaceToFaceSpeaker1() { return (currentMode.value == 'faceToFace' && activeSpeaker.value == 1) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 1); } /// 判断是否为面对面说话者2 bool _isFaceToFaceSpeaker2() { return (currentMode.value == 'faceToFace' && activeSpeaker.value == 2) || (currentMode.value == 'faceToFace' && activeSpeaker.value == 0 && lastActiveSpeaker.value == 2); } /// 执行翻译 /// [sourceText] 源文本 /// [sourceCode] 源语言代码 /// [targetCode] 目标语言代码 /// 返回翻译结果 Future _performTranslation( String sourceText, String sourceCode, String targetCode) async { return await _translationService.translateText( text: sourceText, sourceLanguageCode: sourceCode, targetLanguageCode: targetCode, ); } /// 更新翻译历史 /// [sourceText] 源文本 /// [translationResult] 翻译结果 /// [timestamp] 时间戳 /// [languageCodes] 语言代码映射 /// [isFinal] 是否为最终结果 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()}'); } } /// 更新现有历史项 /// [index] 历史项索引 /// [translationResult] 翻译结果 /// [languageCodes] 语言代码映射 /// [sourceText] 源文本 /// [isFinal] 是否为最终结果 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 (isFloatingWindowEnabled.value) { await _updateFloatingWindowWithTranslation(translationHistory[index]); } // 添加TranslationItem日志打印 if (isFinal) { final item = translationHistory[index]; Logger.info('lxm-翻译完成 - TranslationItem详情: ' '源文本: ${item.sourceText}, ' '翻译文本: ${item.translatedText}, ' '源语言: ${item.sourceLanguageCode}, ' '目标语言: ${item.targetLanguageCode}, ' '时间戳: ${item.timestamp.toIso8601String()}, ' '会话ID: ${item.sessionId}, ' '是否会话首项: ${item.isFirstInSession}, ' '是否中间结果: ${item.isIntermediate}'); } if (isFinal) { saveTranslationHistory(); await _recordTranslationStats( sourceText, translationResult, languageCodes); } _scrollToBottom(); if (isFinal) { await _handleTtsPlayback(translationResult); } } /// 记录翻译统计 /// [sourceText] 源文本 /// [translationResult] 翻译结果 /// [languageCodes] 语言代码映射 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'); } catch (e) { Logger.error('记录翻译统计失败: $e'); } } /// 处理TTS播放 /// [translationResult] 翻译结果 Future _handleTtsPlayback(String translationResult) async { if (isTtsEnabled.value) { await playTranslatedText(translationResult); } } // /// 处理面对面TTS // /// [translationResult] 翻译结果 // Future _handleFaceToFaceTts(String translationResult) async { // fTFTranslationResult = (fTFTranslationResult ?? '') + translationResult; // print("translationResult=$translationResult"); // if (activeSpeaker.value == 0 && !isPlayback) { // isPlayback = true; // await playTranslatedText(fTFTranslationResult!); // fTFTranslationResult = ''; // } // } /// 播放翻译文本 /// [text] 要播放的文本 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(currsessionid, text); final endTime = DateTime.now(); // 记录TTS API调用统计 try { final durationSeconds = endTime.difference(startTime).inSeconds; final languageName = _languageManager .getChineseNameByAsrCode(detectedTargetLanguageCode) ?? '未知'; _usageService.recordTtsApiCall( text: text, 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 { isPreparing.value = false; if (currentMode.value == "audioVideo" || currentMode.value == "call") { bleManager.closeCodec(); Logger.info('关闭ble编解码器'); } if (isRecognizing.value) { await stopRecognition(); } _stopAsrActiveTracking(); _translationDebounceTimer?.cancel(); await _ttsService.stop(); } catch (e) { Logger.error('停止操作失败: ${e.toString()}'); } } /// 播放特定翻译项 /// [item] 翻译项 Future playTranslationItem(TranslationItem item) async { if (item.translatedText.isEmpty) return; // 防重复点击:如果短时间内重复点击同一项,忽略后续点击 final currentTime = DateTime.now().millisecondsSinceEpoch; final itemKey = '${item.sessionId}_${item.timestamp.millisecondsSinceEpoch}'; // 检查是否是重复点击同一项 if (_lastPlayedItemKey == itemKey && currentTime - _lastPlayTime < _playDebounceInterval) { Logger.info('防重复播放:忽略短时间内的重复点击'); return; } // 如果正在播放,先停止当前播放 if (_ttsService.isSpeaking) { await _ttsService.stop(); // 短暂延迟确保停止完成 await Future.delayed(const Duration(milliseconds: 100)); } try { // 更新播放记录 _lastPlayedItemKey = itemKey; _lastPlayTime = currentTime; final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(currsessionid, item.translatedText); } catch (e) { Logger.error('播放翻译项失败: ${e.toString()}'); } } /// 播放文本 /// [text] 要播放的文本 /// [languageCode] 语言代码(可选) Future playText(String text, {String? languageCode}) async { if (text.isEmpty) return; // 防重复点击:基于文本内容和语言代码生成唯一标识 final currentTime = DateTime.now().millisecondsSinceEpoch; final textKey = '${text}_${languageCode ?? targetLanguageCode.value}'; // 检查是否是重复点击同一文本 if (_lastPlayedItemKey == textKey && currentTime - _lastPlayTime < _playDebounceInterval) { Logger.info('防重复播放:忽略短时间内的重复文本播放请求'); return; } // 如果正在播放,先停止当前播放 if (_ttsService.isSpeaking) { await _ttsService.stop(); // 短暂延迟确保停止完成 await Future.delayed(const Duration(milliseconds: 100)); } try { // 更新播放记录 _lastPlayedItemKey = textKey; _lastPlayTime = currentTime; final voiceName = languageCode != null ? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural' : _languageManager .getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural'; await _ttsService.setVoice(voiceName); await _ttsService.speakOnce(currsessionid, text); } catch (e) { Logger.error('播放文本失败: ${e.toString()}'); } } /// 滚动到底部(适配reverse模式) /// [animate] 是否使用动画 void _scrollToBottom({bool animate = true}) { if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { try { 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() { // 简化后的空方法,保留接口兼容性 } /// 设置底部控制栏高度 /// [height] 高度值 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(); _reinitializeAsrService(); } /// 设置源语言 /// [language] 语言名称 void setSourceLanguage(String language) { if (sourceLanguage.value != language) { stopAll(); sourceLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { sourceLanguageCode.value = asrCode; } _saveLanguageSettings(); _reinitializeAsrService(); } } /// 设置目标语言 /// [language] 语言名称 void setTargetLanguage(String language) { if (targetLanguage.value != language) { stopAll(); targetLanguage.value = language; final asrCode = _languageManager.getAsrCodeByChineseName(language); if (asrCode != null) { targetLanguageCode.value = asrCode; } _saveLanguageSettings(); _reinitializeAsrService(); } } /// 重新初始化ASR服务 Future _reinitializeAsrService() async { try { //1.统一先停止旧识别 await stopRecognition(); late final List asrSupportedLanguages; if (currentMode.value == 'audioVideo') { asrSupportedLanguages = [sourceLanguageCode.value]; } else { asrSupportedLanguages = [ sourceLanguageCode.value, targetLanguageCode.value ]; } if (currentMode.value == "call") { _initializeCallModeTranslationService(); } else { Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); await _asrService.initialize(supportedLanguages: asrSupportedLanguages); var recognitionStream = await _asrService.recognizeCallback(); _recognitionSubscription?.cancel(); _recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); } 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(() { if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) { if (hasMoreHistory.value && !isLoadingMore.value) { Logger.d(TAG, '触发加载更多翻译历史记录(reverse模式)'); loadMoreHistory(); } } }); _scrollListenerSetup = true; } /// 清空翻译历史 void clearTranslationHistory() => _historyManager.clearTranslationHistory(); /// 删除会话历史 /// [sessionId] 会话ID void deleteSessionHistory(String sessionId) => _historyManager.deleteSessionHistory(sessionId); /// 删除翻译项 /// [sessionId] 会话ID /// [timestamp] 时间戳 void deleteTranslationItem(String sessionId, DateTime timestamp) => _historyManager.deleteTranslationItem(sessionId, timestamp); /// 获取所有历史记录 /// 返回所有翻译项列表 List getAllHistory() => _historyManager.getAllHistory(); /// 加载更多历史记录 Future loadMoreHistory() => _historyManager.loadMoreHistory(); /// 滚动到底部 /// [animate] 是否使用动画 void scrollToBottom({bool animate = true}) => _historyManager.scrollToBottom(animate: animate); // ==================== ASR活跃时长跟踪 ==================== /// 开始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使用统计 - 基于精确时长跟踪 /// [language] 语言名称 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'); _asrSessionStartTime = null; _asrActiveDuration = Duration.zero; _isAsrActive = false; _asrActiveStartTime = null; } catch (e) { Logger.error('记录ASR统计失败: $e'); } } }