You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1243 lines
39 KiB

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<AsrService>();
final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final TtsService _ttsService = Get.find<TtsService>();
final AstService _astService = Get.find<AstService>();
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
// 蓝牙服务
final BluetoothManager bluetoothManager = Get.find<BluetoothManager>();
final BleManager bleManager = Get.find<BleManager>();
//音乐播放管理
final _musiceManager = Get.find<MusicManager>();
// 统计服务
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 = '';
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<TranslationItem> 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<String, String> get supportedLanguages =>
_languageManager.getChineseNameToAsrCodeMap();
// 最新的语音识别文本
final latestRecognitionText = ''.obs;
// 检测到的语言代码
final detectedLanguageCode1 = ''.obs;
//int num = 0;
Future<void> changeTranslationMode(String mode) async {
currentMode.value = mode;
currentModeTitle.value = _getModeTitle(mode);
stopAll();
if (mode == 'call') {
await _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<void> setActiveSpeaker(int id) async {
activeSpeaker.value = id;
await _asrService.startContinuousRecognition(_audioSourceType);
if (activeSpeaker.value != 0) {
await _handleActiveSpeakerOn(id);
} else {
await _handleActiveSpeakerOff();
}
}
// 拆分出的辅助方法
Future<void> _handleActiveSpeakerOn(int id) async {
isPlayback = false;
isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) {
await _handleRecordingStart();
}
_startAsrActiveTracking();
lastActiveSpeaker.value = id;
}
Future<void> _handleRecordingStart() async {
if (lasyIsRecording.value != isRecording.value) {
lasyIsRecording.value = isRecording.value;
await startRecording();
} else {
await _asrService.resumeRecord();
resumeRecordTimer();
}
}
Future<void> _handleActiveSpeakerOff() async {
_stopAsrActiveTracking();
if (isRecording.value && isRecognizing.value) {
pauseRecordTimer();
await _asrService.pauseRecord();
}
await _handleTranslationPlayback();
}
Future<void> _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<void> disableBluetoothAudio() async =>
await _asrService.disableBluetoothAudio();
//恢复原始音频状态
Future<void> restoreOriginalAudioState() async =>
await _asrService.restoreOriginalAudioState();
// 兼容旧版视图的计算属性
Rx<TranslationItem?> get currentItem {
if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate) {
return Rx<TranslationItem?>(translationHistory.last);
}
return Rx<TranslationItem?>(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<void> _preCheckPermissions() async {
try {
await requestRecordPermission();
} catch (e) {
Logger.e("Permission", "预检查权限失败: $e");
}
}
// 初始化统计服务
void _initUsageService() {
try {
// 确保统计服务已注册
if (!Get.isRegistered<TranslationUsageService>()) {
Get.put(TranslationUsageService(), permanent: true);
}
_usageService = Get.find<TranslationUsageService>();
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<bool> 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<bool> _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<void> 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<void> restoreOtherServices() async {
try {
if (isContinuePlaying) await _musiceManager.resume(); // 暂停音乐
} catch (e) {
Logger.error('初始化服务失败: ${e.toString()}');
}
}
// 初始化服务
Future<void> _initServices() async {
try {
await _translationService.initialize();
// 初始化 ASR 服务时,明确指定需要支持的语言
final List<String> 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<void> _initializeCallModeTranslationService() async {
try {
Logger.info('开始初始化通话模式语音翻译服务');
// 为通话模式配置特殊的ASR设置
final List<String> 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<void> toggleisRecord() async {
isRecording.toggle();
if (isRecording.value && isRecognizing.value) {
startRecording();
} else if (!isRecording.value) {
stopRecording();
} else {}
}
Future<void> 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<void> stopRecording() async {
// 停止计时器
_timerManager.stopTimer();
_asrService.stopRecord(true);
lasyIsRecording.value = false;
Logger.info('录音已停止,计时器已重置');
}
// 开始语音识别
Future<void> 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<bool> _checkPermissions() async {
final bool record = await requestRecordPermission();
if (!record) {
Logger.d("Permission", "录音权限被拒绝,无法开始录音");
return false;
}
return true;
}
Future<void> _initializeSession() async {
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
try {
_usageService.startNewSession();
_asrSessionStartTime = DateTime.now();
_asrActiveDuration = Duration.zero;
_isAsrActive = false;
} catch (e) {
Logger.error('开始新会话统计失败: $e');
}
}
Future<void> _configureAudioMode() async {
switch (currentMode.value) {
case "audioVideo":
await _configureAudioVideoMode();
break;
case "call":
await _configureCallMode();
break;
default:
await _configureDefaultMode();
}
}
Future<void> _configureAudioVideoMode() async {
bleManager.openDecoder();
_audioSourceType = true;
isTtsEnabled.value = false;
Logger.info('发送ble音乐或者通话远端声音');
}
Future<void> _configureCallMode() async {
bleManager.openA2DPDecoder();
_audioSourceType = true;
isTtsEnabled.value = false;
Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音');
}
Future<void> _configureDefaultMode() async {
_audioSourceType = false;
isTtsEnabled.value = true;
}
Future<void> _startAsrService() async {
// if (bluetoothManager.currentDeviceRx.value != null) {
// restoreOriginalAudioState();
// } else {
// disableBluetoothAudio();
// }
await _asrService.startContinuousRecognition(_audioSourceType);
}
Future<void> _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) {
Get.snackbar('error'.tr, event.error);
isRecognizing.value = false;
Logger.error('语音识别错误: ${event.error}');
} else if (event.type == RecognitionEventType.canceled) {
isRecognizing.value = false;
Logger.error('语音识别取消: ${event.error}');
}
}
// 停止语音识别
Future<void> 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<void> 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<String?> 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<String, String> _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<String?> _performTranslation(
String sourceText, String sourceCode, String targetCode) async {
return await _translationService.translateText(
text: sourceText,
sourceLanguageCode: sourceCode,
targetLanguageCode: targetCode,
);
}
Future<void> _updateTranslationHistory(
String sourceText,
String translationResult,
DateTime timestamp,
Map<String, String> 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<void> _updateExistingHistoryItem(
int index,
String translationResult,
Map<String, String> 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<void> _recordTranslationStats(
String sourceText,
String translationResult,
Map<String, String> 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<void> _handleTtsPlayback(String translationResult) async {
if (isTtsEnabled.value && currentMode.value != 'faceToFace') {
await playTranslatedText(translationResult);
} else {
await _handleFaceToFaceTts(translationResult);
}
}
Future<void> _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<void> 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) ??
'未知';
// 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<void> 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<void> 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(currsessionid, item.translatedText);
} catch (e) {
Logger.error('播放翻译项失败: ${e.toString()}');
}
}
// 播放文本
Future<void> 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(currsessionid, 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<void> _reinitializeAsrService() async {
try {
// 停止当前识别
await stopRecognition();
// 更新ASR支持的语言
final List<String> 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<TranslationItem> getAllHistory() => _historyManager.getAllHistory();
Future<void> 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');
}
}
}