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.

646 lines
20 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/azure_asr_service.dart';
import '../../../data/services/azure_tts_service.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/voice_interaction_service.dart';
class TranslationController extends GetxController {
// Services
final AzureAsrService _asrService = Get.find<AzureAsrService>();
final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final GetStorage _storage = GetStorage();
// Storage key
static const String _historyKey = 'translation_history';
static const int _maxHistoryItems = 100; // 最多保存100条历史记录
// State variables
final isRecognizing = false.obs;
final isTranslating = false.obs;
final isTtsEnabled = true.obs;
final sourceLanguage = '中文 (Chinese)'.obs;
final targetLanguage = '英语 (English)'.obs;
final translationHistory = <TranslationItem>[].obs;
// 支持的语言列表 - 改为Map结构,键为显示名称,值为语言代码
final Map<String, String> supportedLanguages = {
'中文 (Chinese)': 'zh',
'英语 (English)': 'en',
'日语 (Japanese)': 'ja',
'韩语 (Korean)': 'ko',
'法语 (French)': 'fr',
'德语 (German)': 'de',
'西班牙语 (Spanish)': 'es',
'意大利语 (Italian)': 'it',
'俄语 (Russian)': 'ru',
'泰语 (Thai)': 'th',
'葡萄牙语 (Portuguese)': 'pt',
'阿拉伯语 (Arabic)': 'ar',
'越南语 (Vietnamese)': 'vi',
'印尼语 (Indonesian)': 'id',
'印地语 (Hindi)': 'hi',
'土耳其语 (Turkish)': 'tr',
'波兰语 (Polish)': 'pl',
'荷兰语 (Dutch)': 'nl',
'瑞典语 (Swedish)': 'sv',
'捷克语 (Czech)': 'cs',
'希腊语 (Greek)': 'el',
'罗马尼亚语 (Romanian)': 'ro',
'匈牙利语 (Hungarian)': 'hu',
'乌克兰语 (Ukrainian)': 'uk',
'保加利亚语 (Bulgarian)': 'bg',
'丹麦语 (Danish)': 'da',
'芬兰语 (Finnish)': 'fi',
'挪威语 (Norwegian)': 'no',
'马来语 (Malay)': 'ms',
};
// Current translation item
final Rx<TranslationItem?> currentItem = Rx<TranslationItem?>(null);
// Subscription for recognition stream
StreamSubscription? _recognitionSubscription;
// Scroll controller for ListView
final ScrollController scrollController = ScrollController();
// Timer for debouncing translation requests
Timer? _translationDebounceTimer;
// 当前会话ID
String? currentSessionId;
@override
void onInit() {
super.onInit();
// 暂停后台语音交互服务,避免与翻译页面的语音功能相互干扰
if (Get.isRegistered<VoiceInteractionService>()) {
VoiceInteractionService.to.pauseVoiceInteraction();
Logger.info('已暂停后台语音交互服务');
}
// 加载历史记录
loadTranslationHistory();
// Initialize the translation service
_initTranslationService();
// 初始化 Azure ASR 服务
_initAzureAsrService();
// 监听部分识别结果和识别状态
_setupListeners();
// 添加后处理,确保在历史记录加载后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
// 延迟执行滚动操作,确保视图已完全渲染
Future.delayed(const Duration(milliseconds: 300), () {
_scrollToBottom();
});
});
}
@override
void onClose() {
stopRecognition();
_recognitionSubscription?.cancel();
_translationDebounceTimer?.cancel();
scrollController.dispose();
super.onClose();
}
// Initialize the translation service
Future<void> _initTranslationService() async {
try {
await _translationService.initialize();
} catch (e) {
Get.snackbar(
'Error',
'Failed to initialize translation service: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// 初始化 Azure ASR 服务
Future<void> _initAzureAsrService() async {
try {
// 设置识别语言
final sourceCode = supportedLanguages[sourceLanguage.value] ?? 'zh';
final recognitionLanguage = _getRecognitionLanguage(sourceCode);
await _asrService.initialize(language: recognitionLanguage);
Logger.info('Azure ASR 服务初始化成功');
} catch (e) {
Logger.error('初始化 Azure ASR 服务失败: $e');
}
}
// Toggle TTS feature
void toggleTts() {
isTtsEnabled.toggle();
}
// Start voice recognition
Future<void> startRecognition() async {
if (isRecognizing.value) return;
try {
// 生成新的会话ID
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
// Initialize a new translation item
currentItem.value = TranslationItem(
sourceText: '',
translatedText: '',
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
timestamp: DateTime.now(),
sessionId: currentSessionId!,
isFirstInSession: true, // 标记为会话中的第一条
);
// 设置识别语言
final sourceCode = supportedLanguages[sourceLanguage.value] ?? 'zh';
final recognitionLanguage = _getRecognitionLanguage(sourceCode);
// 确保使用正确的语言
await _asrService.setRecognitionLanguage(recognitionLanguage);
// 开始连续语音识别
final recognitionStream = await _asrService.startContinuousRecognition();
isRecognizing.value = true;
// Reset the current item
if (currentItem.value != null) {
currentItem.value!.sourceText = '';
currentItem.value!.translatedText = '';
}
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription = recognitionStream.listen((event) {
if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) {
handleFinalResult(event.text);
} else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) {
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}');
}
});
} catch (e) {
isRecognizing.value = false;
Get.snackbar(
'Error',
'启动语音识别失败: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// 将语言代码转换为识别语言代码
String _getRecognitionLanguage(String languageCode) {
// 语言代码到识别语言代码的映射
final Map<String, String> languageToRecognitionCode = {
'zh': 'zh-CN',
'en': 'en-US',
'ja': 'ja-JP',
'ko': 'ko-KR',
'fr': 'fr-FR',
'de': 'de-DE',
'es': 'es-ES',
'it': 'it-IT',
'ru': 'ru-RU',
'pt': 'pt-BR',
'ar': 'ar-EG',
'th': 'th-TH',
};
return languageToRecognitionCode[languageCode] ?? 'zh-CN';
}
// Stop voice recognition
Future<void> stopRecognition() async {
if (!isRecognizing.value) return;
try {
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isRecognizing.value = false;
// 重置当前会话ID
currentSessionId = null;
// 重置当前项
currentItem.value = null;
} catch (e) {
Get.snackbar('Error', '停止语音识别失败: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM);
}
}
// Handle intermediate recognition result
void handleIntermediateResult(String text) {
if (text.isEmpty) return;
// 如果当前没有活跃的翻译项,创建一个新的
if (currentItem.value == null) {
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString();
currentItem.value = TranslationItem(
sourceText: text,
translatedText: '',
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
timestamp: DateTime.now(),
sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty ||
translationHistory.last.sessionId != currentSessionId,
);
currentItem.refresh();
} else {
// 更新当前翻译项
currentItem.value!.sourceText = text;
// 通知 GetX 更新
currentItem.refresh();
}
// 防抖处理翻译请求
_translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
translateCurrentItem(isIntermediate: true);
});
}
// 翻译当前项
Future<void> translateCurrentItem({bool isIntermediate = false}) async {
if (currentItem.value == null || currentItem.value!.sourceText.isEmpty) {
return;
}
final sourceText = currentItem.value!.sourceText;
final translationResult = await translateText(sourceText);
if (translationResult != null && isIntermediate) {
// 如果是中间结果,不播放TTS
// 滚动到底部显示最新内容
_scrollToBottom();
}
}
// Handle final recognition result
void handleFinalResult(String text) async {
// 如果没有文本或当前项为空,直接返回
if (text.isEmpty || currentItem.value == null) return;
// 更新当前项的源文本
currentItem.value!.sourceText = text;
currentItem.refresh();
// 添加到历史记录
translationHistory.add(currentItem.value!);
saveTranslationHistory();
// 重置当前项
currentItem.value = null;
// 对最终结果进行一次翻译,确保翻译结果是最新的
final translationResult = await translateText(text);
if (translationResult != null) {
if (isTtsEnabled.value) {
await playTranslatedText(translationResult);
}
}
// 滚动到底部显示最新内容
_scrollToBottom();
}
// 检测文本语言
String detectLanguage(String text) {
if (text.isEmpty) return sourceLanguage.value;
// 检测各种语言特征
final bool containsChinese = RegExp(r'[\u4e00-\u9fa5]').hasMatch(text);
final bool containsLatin = RegExp(r'[a-zA-Z]').hasMatch(text);
final bool containsJapanese =
RegExp(r'[\u3040-\u309F\u30A0-\u30FF]').hasMatch(text);
final bool containsKorean =
RegExp(r'[\uAC00-\uD7AF\u1100-\u11FF]').hasMatch(text);
final bool containsCyrillic = RegExp(r'[\u0400-\u04FF]').hasMatch(text);
final bool containsArabic = RegExp(r'[\u0600-\u06FF]').hasMatch(text);
final bool containsThai = RegExp(r'[\u0E00-\u0E7F]').hasMatch(text);
// 获取当前设置的源语言代码
final String sourceCode = supportedLanguages[sourceLanguage.value] ?? 'zh';
// 根据文本特征判断语言
if (containsChinese && sourceCode != 'zh') {
return '中文 (Chinese)';
} else if (containsJapanese && sourceCode != 'ja') {
return '日语 (Japanese)';
} else if (containsKorean && sourceCode != 'ko') {
return '韩语 (Korean)';
} else if (containsCyrillic && sourceCode != 'ru') {
return '俄语 (Russian)';
} else if (containsArabic && sourceCode != 'ar') {
return '阿拉伯语 (Arabic)';
} else if (containsThai && sourceCode != 'th') {
return '泰语 (Thai)';
} else if (containsLatin && sourceCode == 'zh') {
return '英语 (English)';
}
// 默认返回当前设置的源语言
return sourceLanguage.value;
}
// Translate the recognized text
Future<String?> translateText(String sourceText) async {
if (sourceText.isEmpty) return null;
isTranslating.value = true;
String? translationResult;
try {
// 检测源文本语言
final detectedLanguage = detectLanguage(sourceText);
final bool shouldSwapLanguages = detectedLanguage != sourceLanguage.value;
// 调用翻译服务
final result = await _translationService.translateText(
text: sourceText,
sourceLanguage:
shouldSwapLanguages ? detectedLanguage : sourceLanguage.value,
targetLanguage:
shouldSwapLanguages ? sourceLanguage.value : targetLanguage.value,
);
if (result != null) {
translationResult = result;
// 更新翻译结果
if (currentItem.value != null &&
currentItem.value!.sourceText.contains(sourceText)) {
// 直接更新并刷新
currentItem.value!.translatedText = result;
currentItem.refresh();
} else {
// 在历史记录中查找匹配的项
final index = translationHistory.indexWhere(
(historyItem) => historyItem.sourceText == sourceText);
if (index != -1) {
// 直接更新历史记录项
translationHistory[index].translatedText = result;
// 通知 GetX 更新
translationHistory.refresh();
saveTranslationHistory();
}
// 滚动到底部显示最新内容
_scrollToBottom();
}
}
return translationResult;
} catch (e) {
Get.snackbar('Error', '翻译失败: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM);
return null;
} finally {
isTranslating.value = false;
}
}
// 播放翻译文本
Future<void> playTranslatedText(String text) async {
if (text.isEmpty) return;
try {
// 获取目标语言代码
final targetCode = supportedLanguages[targetLanguage.value] ?? 'en';
// 设置合适的语音
final voiceName = _getTtsVoiceName(targetCode);
await _ttsService.setVoice(voiceName);
// 播放文本
await _ttsService.speak(text);
} catch (e) {
Get.snackbar('Error', '播放翻译失败: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM);
}
}
// 获取TTS语音名称
String _getTtsVoiceName(String languageCode) {
// 语言代码到TTS语音名称的映射
final Map<String, String> languageToVoiceName = {
'zh': 'zh-CN-XiaoxiaoNeural',
'en': 'en-US-AriaNeural',
'ja': 'ja-JP-NanamiNeural',
'ko': 'ko-KR-SunHiNeural',
'fr': 'fr-FR-DeniseNeural',
'de': 'de-DE-KatjaNeural',
'es': 'es-ES-ElviraNeural',
'it': 'it-IT-ElsaNeural',
'ru': 'ru-RU-SvetlanaNeural',
'pt': 'pt-BR-FranciscaNeural',
'ar': 'ar-EG-SalmaNeural',
'th': 'th-TH-PremwadeeNeural',
};
return languageToVoiceName[languageCode] ?? 'en-US-AriaNeural';
}
// 停止所有操作(语音识别和TTS)
Future<void> stopAll() async {
try {
// 停止语音识别
if (isRecognizing.value) {
await stopRecognition();
}
// 停止TTS播放
await _ttsService.stop();
} catch (e) {
Get.snackbar('Error', '停止操作失败: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM);
}
}
// 播放特定翻译项
Future<void> playTranslationItem(TranslationItem item) async {
if (item.translatedText.isEmpty) return;
await playTranslatedText(item.translatedText);
}
// 播放文本
Future<void> playText(String text) async {
if (text.isEmpty) return;
await playTranslatedText(text);
}
// 滚动到列表底部
void _scrollToBottom() {
if (!scrollController.hasClients) {
Future.delayed(const Duration(milliseconds: 100), _scrollToBottom);
return;
}
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} catch (e) {
// 如果滚动失败,尝试延迟后再次滚动
Future.delayed(const Duration(milliseconds: 100), () {
if (scrollController.hasClients) {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
});
}
}
// 切换源语言和目标语言
void swapLanguages() {
final temp = sourceLanguage.value;
sourceLanguage.value = targetLanguage.value;
targetLanguage.value = temp;
// 如果正在识别,需要停止并重新开始
if (isRecognizing.value) {
stopRecognition();
startRecognition();
}
}
// 设置源语言
void setSourceLanguage(String language) {
if (sourceLanguage.value != language) {
sourceLanguage.value = language;
// 如果正在识别,需要停止并重新开始
if (isRecognizing.value) {
stopRecognition();
startRecognition();
}
}
}
// 设置目标语言
void setTargetLanguage(String language) {
if (targetLanguage.value != language) {
targetLanguage.value = language;
}
}
// Load translation history from storage
void loadTranslationHistory() {
final historyJson = _storage.read(_historyKey);
if (historyJson != null) {
try {
final List<dynamic> historyList = jsonDecode(historyJson);
translationHistory.value =
historyList.map((item) => TranslationItem.fromJson(item)).toList();
} catch (e) {
// 如果解析失败,清除历史记录
_storage.remove(_historyKey);
}
}
}
// Save translation history to storage
void saveTranslationHistory() {
// 限制历史记录数量,保留最新的记录
if (translationHistory.length > _maxHistoryItems) {
translationHistory.removeRange(
0, translationHistory.length - _maxHistoryItems);
}
final List<Map<String, dynamic>> historyJson =
translationHistory.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(historyJson));
}
// Clear translation history
void clearTranslationHistory() {
translationHistory.clear();
_storage.remove(_historyKey);
}
// 设置监听器
void _setupListeners() {
// 在新版本中,我们通过recognitionStream直接监听事件
// 这部分逻辑已经在startRecognition方法中实现
Logger.info('语音识别事件监听已设置');
}
}
// Model class for translation items
class TranslationItem {
String sourceText;
String translatedText;
final String sourceLanguage;
final String targetLanguage;
final DateTime timestamp;
String sessionId;
final bool isFirstInSession;
TranslationItem({
required this.sourceText,
required this.translatedText,
required this.sourceLanguage,
required this.targetLanguage,
required this.timestamp,
required this.sessionId,
this.isFirstInSession = 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'] ?? '',
sourceLanguage: json['sourceLanguage'] ?? '中文 (Chinese)',
targetLanguage: json['targetLanguage'] ?? '英语 (English)',
timestamp: timestamp,
sessionId:
json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: json['isFirstInSession'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'sourceText': sourceText,
'translatedText': translatedText,
'sourceLanguage': sourceLanguage,
'targetLanguage': targetLanguage,
'timestamp': timestamp.toIso8601String(),
'sessionId': sessionId,
'isFirstInSession': isFirstInSession,
};
}
}