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.

767 lines
24 KiB

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