|
|
@ -10,14 +10,37 @@ import '../../../core/utils/logger.dart'; |
|
|
import '../../../data/models/events/voice_interaction_event.dart'; |
|
|
import '../../../data/models/events/voice_interaction_event.dart'; |
|
|
import '../../../data/services/voice_interaction_service.dart'; |
|
|
import '../../../data/services/voice_interaction_service.dart'; |
|
|
import '../../../data/services/asr_service.dart'; |
|
|
import '../../../data/services/asr_service.dart'; |
|
|
|
|
|
import 'package:jieli/jieli.dart'; |
|
|
|
|
|
|
|
|
class TranslationController extends GetxController { |
|
|
class TranslationController extends GetxController { |
|
|
|
|
|
late Jieli jieli; |
|
|
|
|
|
// 事件订阅 |
|
|
|
|
|
StreamSubscription? _scanSubscription; |
|
|
|
|
|
StreamSubscription? _connectionSubscription; |
|
|
|
|
|
StreamSubscription? _wakeupSubscription; |
|
|
|
|
|
StreamSubscription? _audioSubscription; |
|
|
|
|
|
StreamSubscription? _commandResultSubscription; |
|
|
|
|
|
// 状态变量 |
|
|
|
|
|
var isScanning = false.obs; |
|
|
|
|
|
var deviceInfo = '未获取'.obs; |
|
|
|
|
|
var connectStatus = '未连接'.obs; |
|
|
|
|
|
var currentDevice = Rx<BleDevice?>(null); |
|
|
|
|
|
var scannedDevices = RxList<BleDevice>([]); |
|
|
|
|
|
var wakeupReceived = false.obs; |
|
|
|
|
|
var audioDataSize = 0.obs; |
|
|
|
|
|
var receivedFrames = 0.obs; |
|
|
|
|
|
var audioDataReceiving = false.obs; |
|
|
|
|
|
var volumeCommandResult = ''.obs; |
|
|
|
|
|
|
|
|
// 服务 |
|
|
// 服务 |
|
|
final AsrService _asrService = Get.find<AsrService>(); |
|
|
final AsrService _asrService = Get.find<AsrService>(); |
|
|
final VolcanoTranslationService _translationService = Get.find<VolcanoTranslationService>(); |
|
|
final VolcanoTranslationService _translationService = |
|
|
|
|
|
Get.find<VolcanoTranslationService>(); |
|
|
final TtsService _ttsService = Get.find<TtsService>(); |
|
|
final TtsService _ttsService = Get.find<TtsService>(); |
|
|
final LanguageManager _languageManager = Get.find<LanguageManager>(); |
|
|
final LanguageManager _languageManager = Get.find<LanguageManager>(); |
|
|
final GetStorage _storage = GetStorage(); |
|
|
final GetStorage _storage = GetStorage(); |
|
|
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>(); |
|
|
final VoiceInteractionService _voiceInteractionService = |
|
|
|
|
|
Get.find<VoiceInteractionService>(); |
|
|
|
|
|
|
|
|
// 存储相关 |
|
|
// 存储相关 |
|
|
static const String _historyKey = 'translation_history'; |
|
|
static const String _historyKey = 'translation_history'; |
|
|
@ -29,75 +52,80 @@ class TranslationController extends GetxController { |
|
|
final isRecognizing = false.obs; |
|
|
final isRecognizing = false.obs; |
|
|
final isTranslating = false.obs; |
|
|
final isTranslating = false.obs; |
|
|
final isTtsEnabled = true.obs; |
|
|
final isTtsEnabled = true.obs; |
|
|
|
|
|
|
|
|
// 语言相关 |
|
|
// 语言相关 |
|
|
final sourceLanguage = '中文(简体)'.obs; |
|
|
final sourceLanguage = '中文(简体)'.obs; |
|
|
final targetLanguage = '英语'.obs; |
|
|
final targetLanguage = '英语'.obs; |
|
|
final sourceLanguageCode = 'zh-CN'.obs; |
|
|
final sourceLanguageCode = 'zh-CN'.obs; |
|
|
final targetLanguageCode = 'en-US'.obs; |
|
|
final targetLanguageCode = 'en-US'.obs; |
|
|
var detectedLanguageCode = ''; |
|
|
var detectedLanguageCode = ''; |
|
|
|
|
|
|
|
|
// 翻译历史和当前项 |
|
|
// 翻译历史和当前项 |
|
|
final translationHistory = <TranslationItem>[].obs; |
|
|
final translationHistory = <TranslationItem>[].obs; |
|
|
String? currentSessionId; |
|
|
String? currentSessionId; |
|
|
|
|
|
|
|
|
// 兼容旧版视图的计算属性 |
|
|
// 兼容旧版视图的计算属性 |
|
|
Rx<TranslationItem?> get currentItem { |
|
|
Rx<TranslationItem?> get currentItem { |
|
|
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { |
|
|
if (translationHistory.isNotEmpty && |
|
|
|
|
|
translationHistory.last.isIntermediate) { |
|
|
return Rx<TranslationItem?>(translationHistory.last); |
|
|
return Rx<TranslationItem?>(translationHistory.last); |
|
|
} |
|
|
} |
|
|
return Rx<TranslationItem?>(null); |
|
|
return Rx<TranslationItem?>(null); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// UI相关 |
|
|
// UI相关 |
|
|
final ScrollController scrollController = ScrollController(); |
|
|
final ScrollController scrollController = ScrollController(); |
|
|
|
|
|
|
|
|
// 其他 |
|
|
// 其他 |
|
|
StreamSubscription? _recognitionSubscription; |
|
|
StreamSubscription? _recognitionSubscription; |
|
|
StreamSubscription? _voiceInteractionSubscription; |
|
|
StreamSubscription? _voiceInteractionSubscription; |
|
|
Timer? _translationDebounceTimer; |
|
|
Timer? _translationDebounceTimer; |
|
|
|
|
|
|
|
|
// 获取支持的语言列表 |
|
|
// 获取支持的语言列表 |
|
|
Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); |
|
|
Map<String, String> get supportedLanguages => |
|
|
|
|
|
_languageManager.getChineseNameToAsrCodeMap(); |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
void onInit() { |
|
|
void onInit() { |
|
|
super.onInit(); |
|
|
super.onInit(); |
|
|
|
|
|
jieli = Jieli(); |
|
|
|
|
|
//_setupEventListeners(); |
|
|
// 加载上次保存的语言设置 |
|
|
// 加载上次保存的语言设置 |
|
|
_loadSavedLanguages(); |
|
|
_loadSavedLanguages(); |
|
|
|
|
|
|
|
|
// 初始化语言代码 |
|
|
// 初始化语言代码 |
|
|
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); |
|
|
final sourceAsrCode = |
|
|
|
|
|
_languageManager.getAsrCodeByChineseName(sourceLanguage.value); |
|
|
if (sourceAsrCode != null) { |
|
|
if (sourceAsrCode != null) { |
|
|
sourceLanguageCode.value = sourceAsrCode; |
|
|
sourceLanguageCode.value = sourceAsrCode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); |
|
|
final targetAsrCode = |
|
|
|
|
|
_languageManager.getAsrCodeByChineseName(targetLanguage.value); |
|
|
if (targetAsrCode != null) { |
|
|
if (targetAsrCode != null) { |
|
|
targetLanguageCode.value = targetAsrCode; |
|
|
targetLanguageCode.value = targetAsrCode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 订阅原生服务事件 |
|
|
// 订阅原生服务事件 |
|
|
_subscribeToVoiceInteractionService(); |
|
|
_subscribeToVoiceInteractionService(); |
|
|
|
|
|
|
|
|
// 加载历史记录 |
|
|
// 加载历史记录 |
|
|
loadTranslationHistory(); |
|
|
loadTranslationHistory(); |
|
|
|
|
|
|
|
|
// 初始化服务 |
|
|
// 初始化服务 |
|
|
_initServices(); |
|
|
_initServices(); |
|
|
|
|
|
|
|
|
// 使用微任务在布局完成后自动滚动到底部 |
|
|
// 使用微任务在布局完成后自动滚动到底部 |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
|
|
|
|
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 订阅原生服务事件 |
|
|
/// 订阅原生服务事件 |
|
|
void _subscribeToVoiceInteractionService() { |
|
|
void _subscribeToVoiceInteractionService() { |
|
|
if (_voiceInteractionService.eventStream != null) { |
|
|
if (_voiceInteractionService.eventStream != null) { |
|
|
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) { |
|
|
_voiceInteractionSubscription = |
|
|
|
|
|
_voiceInteractionService.eventStream!.listen((event) { |
|
|
if (event is RecognitionStartedEvent) { |
|
|
if (event is RecognitionStartedEvent) { |
|
|
_handleVoiceInteractionStarted(); |
|
|
_handleVoiceInteractionStarted(); |
|
|
} |
|
|
} |
|
|
@ -109,35 +137,54 @@ class TranslationController extends GetxController { |
|
|
void _loadSavedLanguages() { |
|
|
void _loadSavedLanguages() { |
|
|
final savedSourceLanguage = _storage.read(_sourceLanguageKey); |
|
|
final savedSourceLanguage = _storage.read(_sourceLanguageKey); |
|
|
final savedTargetLanguage = _storage.read(_targetLanguageKey); |
|
|
final savedTargetLanguage = _storage.read(_targetLanguageKey); |
|
|
|
|
|
|
|
|
if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) { |
|
|
if (savedSourceLanguage != null && |
|
|
|
|
|
supportedLanguages.containsKey(savedSourceLanguage)) { |
|
|
sourceLanguage.value = savedSourceLanguage; |
|
|
sourceLanguage.value = savedSourceLanguage; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) { |
|
|
if (savedTargetLanguage != null && |
|
|
|
|
|
supportedLanguages.containsKey(savedTargetLanguage)) { |
|
|
targetLanguage.value = savedTargetLanguage; |
|
|
targetLanguage.value = savedTargetLanguage; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 保存当前语言设置 |
|
|
// 保存当前语言设置 |
|
|
void _saveLanguageSettings() { |
|
|
void _saveLanguageSettings() { |
|
|
_storage.write(_sourceLanguageKey, sourceLanguage.value); |
|
|
_storage.write(_sourceLanguageKey, sourceLanguage.value); |
|
|
_storage.write(_targetLanguageKey, targetLanguage.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 { |
|
|
Future<void> _initServices() async { |
|
|
try { |
|
|
try { |
|
|
await _translationService.initialize(); |
|
|
await _translationService.initialize(); |
|
|
|
|
|
|
|
|
// 初始化 ASR 服务时,明确指定需要支持的语言 |
|
|
// 初始化 ASR 服务时,明确指定需要支持的语言 |
|
|
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; |
|
|
final List<String> asrSupportedLanguages = [ |
|
|
|
|
|
sourceLanguageCode.value, |
|
|
|
|
|
targetLanguageCode.value |
|
|
|
|
|
]; |
|
|
Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); |
|
|
Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); |
|
|
|
|
|
|
|
|
await _asrService.initialize( |
|
|
await _asrService.initialize( |
|
|
supportedLanguages: asrSupportedLanguages, |
|
|
supportedLanguages: asrSupportedLanguages, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
Logger.info('所有服务初始化完成'); |
|
|
Logger.info('所有服务初始化完成'); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('初始化服务失败: $e'); |
|
|
Logger.error('初始化服务失败: $e'); |
|
|
@ -159,20 +206,26 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
// 开始语音识别 |
|
|
// 开始语音识别 |
|
|
Future<void> startRecognition() async { |
|
|
Future<void> startRecognition() async { |
|
|
|
|
|
Logger.info('开始语音识别'); |
|
|
if (isRecognizing.value) return; |
|
|
if (isRecognizing.value) return; |
|
|
|
|
|
|
|
|
_voiceInteractionService.pauseVoiceInteraction(); |
|
|
_voiceInteractionService.pauseVoiceInteraction(); |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
|
|
|
Logger.info('开始语音识别1'); |
|
|
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对 |
|
|
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对 |
|
|
final List<String> currentLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; |
|
|
final List<String> currentLanguages = [ |
|
|
final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages); |
|
|
sourceLanguageCode.value, |
|
|
|
|
|
targetLanguageCode.value |
|
|
|
|
|
]; |
|
|
|
|
|
final bool needsReinit = |
|
|
|
|
|
!_asrService.supportedLanguages.toSet().containsAll(currentLanguages); |
|
|
|
|
|
|
|
|
if (needsReinit) { |
|
|
if (needsReinit) { |
|
|
Logger.info('识别开始前发现语言变更,重新初始化ASR服务'); |
|
|
Logger.info('识别开始前发现语言变更,重新初始化ASR服务'); |
|
|
await _reinitializeAsrService(); |
|
|
await _reinitializeAsrService(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); |
|
|
|
|
|
|
|
|
// // 初始化新的翻译项 |
|
|
// // 初始化新的翻译项 |
|
|
@ -186,17 +239,25 @@ class TranslationController extends GetxController { |
|
|
// isFirstInSession: true, |
|
|
// isFirstInSession: true, |
|
|
// isIntermediate: true, |
|
|
// isIntermediate: true, |
|
|
// ); |
|
|
// ); |
|
|
|
|
|
|
|
|
// // 添加到历史记录 |
|
|
// // 添加到历史记录 |
|
|
// translationHistory.add(newItem); |
|
|
// translationHistory.add(newItem); |
|
|
|
|
|
|
|
|
// 开始连续语音识别 |
|
|
// 开始连续语音识别 |
|
|
final recognitionStream = await _asrService.startContinuousRecognition(); |
|
|
final recognitionStream = await _asrService.startContinuousRecognition(); |
|
|
|
|
|
Logger.info('翻译开启,开始监'); |
|
|
|
|
|
// 监听音频数据 |
|
|
|
|
|
_audioSubscription = jieli.audioDataStream.listen((data) async { |
|
|
|
|
|
// Logger.info('翻译开启,开始监听解码后的数据=${data}'); |
|
|
|
|
|
final pushAudioDataStream = await _asrService.pushAudioData(data); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
isRecognizing.value = true; |
|
|
isRecognizing.value = true; |
|
|
|
|
|
|
|
|
// 监听识别结果 |
|
|
// 监听识别结果 |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); |
|
|
_recognitionSubscription = |
|
|
|
|
|
recognitionStream.listen(_handleRecognitionEvent); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
isRecognizing.value = false; |
|
|
isRecognizing.value = false; |
|
|
Logger.error('启动语音识别失败: $e'); |
|
|
Logger.error('启动语音识别失败: $e'); |
|
|
@ -205,10 +266,12 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
// 处理识别事件 |
|
|
// 处理识别事件 |
|
|
void _handleRecognitionEvent(RecognitionEvent event) { |
|
|
void _handleRecognitionEvent(RecognitionEvent event) { |
|
|
if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) { |
|
|
if (event.type == RecognitionEventType.finalResult && |
|
|
|
|
|
event.text.isNotEmpty) { |
|
|
detectedLanguageCode = event.detectedLanguage; |
|
|
detectedLanguageCode = event.detectedLanguage; |
|
|
handleFinalResult(event.text); |
|
|
handleFinalResult(event.text); |
|
|
} else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) { |
|
|
} else if (event.type == RecognitionEventType.intermediateResult && |
|
|
|
|
|
event.text.isNotEmpty) { |
|
|
if (event.detectedLanguage.isNotEmpty) { |
|
|
if (event.detectedLanguage.isNotEmpty) { |
|
|
detectedLanguageCode = event.detectedLanguage; |
|
|
detectedLanguageCode = event.detectedLanguage; |
|
|
} |
|
|
} |
|
|
@ -231,21 +294,25 @@ class TranslationController extends GetxController { |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription = null; |
|
|
_recognitionSubscription = null; |
|
|
isRecognizing.value = false; |
|
|
isRecognizing.value = false; |
|
|
|
|
|
|
|
|
// 如果当前有中间结果,将其作为最终结果处理 |
|
|
// 如果当前有中间结果,将其作为最终结果处理 |
|
|
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { |
|
|
if (translationHistory.isNotEmpty && |
|
|
|
|
|
translationHistory.last.isIntermediate && |
|
|
|
|
|
translationHistory.last.sourceText.isNotEmpty) { |
|
|
// 标记为最终结果 |
|
|
// 标记为最终结果 |
|
|
translationHistory.last.isIntermediate = false; |
|
|
translationHistory.last.isIntermediate = false; |
|
|
translationHistory.refresh(); |
|
|
translationHistory.refresh(); |
|
|
|
|
|
|
|
|
// 如果还没有翻译结果,尝试翻译 |
|
|
// 如果还没有翻译结果,尝试翻译 |
|
|
if (translationHistory.last.translatedText.isEmpty) { |
|
|
if (translationHistory.last.translatedText.isEmpty) { |
|
|
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true); |
|
|
await translateText(translationHistory.last.sourceText, |
|
|
|
|
|
translationHistory.last.timestamp, |
|
|
|
|
|
isFinal: true); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
saveTranslationHistory(); |
|
|
saveTranslationHistory(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
currentSessionId = null; |
|
|
currentSessionId = null; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('停止语音识别失败: $e'); |
|
|
Logger.error('停止语音识别失败: $e'); |
|
|
@ -265,10 +332,11 @@ class TranslationController extends GetxController { |
|
|
targetLanguageCode: targetLanguageCode.value, |
|
|
targetLanguageCode: targetLanguageCode.value, |
|
|
timestamp: DateTime.now(), |
|
|
timestamp: DateTime.now(), |
|
|
sessionId: currentSessionId!, |
|
|
sessionId: currentSessionId!, |
|
|
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, |
|
|
isFirstInSession: translationHistory.isEmpty || |
|
|
|
|
|
translationHistory.last.sessionId != currentSessionId, |
|
|
isIntermediate: true, |
|
|
isIntermediate: true, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
translationHistory.add(newItem); |
|
|
translationHistory.add(newItem); |
|
|
// 添加新项后滚动到底部 |
|
|
// 添加新项后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
@ -283,8 +351,12 @@ class TranslationController extends GetxController { |
|
|
// 防抖处理翻译请求 |
|
|
// 防抖处理翻译请求 |
|
|
_translationDebounceTimer?.cancel(); |
|
|
_translationDebounceTimer?.cancel(); |
|
|
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { |
|
|
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { |
|
|
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { |
|
|
if (translationHistory.isNotEmpty && |
|
|
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false); |
|
|
translationHistory.last.isIntermediate && |
|
|
|
|
|
translationHistory.last.sourceText.isNotEmpty) { |
|
|
|
|
|
translateText(translationHistory.last.sourceText, |
|
|
|
|
|
translationHistory.last.timestamp, |
|
|
|
|
|
isFinal: false); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
@ -301,7 +373,7 @@ class TranslationController extends GetxController { |
|
|
// 更新后滚动到底部 |
|
|
// 更新后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
saveTranslationHistory(); |
|
|
saveTranslationHistory(); |
|
|
|
|
|
|
|
|
// 使用项目的时间戳进行翻译 |
|
|
// 使用项目的时间戳进行翻译 |
|
|
translateText(text, translationHistory.last.timestamp, isFinal: true); |
|
|
translateText(text, translationHistory.last.timestamp, isFinal: true); |
|
|
} else { |
|
|
} else { |
|
|
@ -312,16 +384,17 @@ class TranslationController extends GetxController { |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
sourceLanguageCode: sourceLanguageCode.value, |
|
|
targetLanguageCode: targetLanguageCode.value, |
|
|
targetLanguageCode: targetLanguageCode.value, |
|
|
timestamp: DateTime.now(), |
|
|
timestamp: DateTime.now(), |
|
|
sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(), |
|
|
sessionId: currentSessionId ?? |
|
|
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, |
|
|
DateTime.now().millisecondsSinceEpoch.toString(), |
|
|
|
|
|
isFirstInSession: translationHistory.isEmpty || |
|
|
|
|
|
translationHistory.last.sessionId != currentSessionId, |
|
|
isIntermediate: false, |
|
|
isIntermediate: false, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
translationHistory.add(newItem); |
|
|
translationHistory.add(newItem); |
|
|
translationHistory.refresh(); |
|
|
translationHistory.refresh(); |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
saveTranslationHistory(); |
|
|
saveTranslationHistory(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新项目的时间戳进行翻译 |
|
|
// 使用新项目的时间戳进行翻译 |
|
|
translateText(text, newItem.timestamp, isFinal: true); |
|
|
translateText(text, newItem.timestamp, isFinal: true); |
|
|
@ -329,7 +402,8 @@ class TranslationController extends GetxController { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 翻译文本 |
|
|
// 翻译文本 |
|
|
Future<String?> translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async { |
|
|
Future<String?> translateText(String sourceText, DateTime timestamp, |
|
|
|
|
|
{bool isFinal = false}) async { |
|
|
if (sourceText.isEmpty) return null; |
|
|
if (sourceText.isEmpty) return null; |
|
|
|
|
|
|
|
|
isTranslating.value = true; |
|
|
isTranslating.value = true; |
|
|
@ -338,8 +412,10 @@ class TranslationController extends GetxController { |
|
|
try { |
|
|
try { |
|
|
// 根据检测到的语言确定源语言和目标语言 |
|
|
// 根据检测到的语言确定源语言和目标语言 |
|
|
final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; |
|
|
final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; |
|
|
final detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; |
|
|
final detectedSourceLanguageCode = |
|
|
final detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; |
|
|
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; |
|
|
|
|
|
final detectedTargetLanguageCode = |
|
|
|
|
|
shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; |
|
|
|
|
|
|
|
|
// 调用翻译服务 |
|
|
// 调用翻译服务 |
|
|
translationResult = await _translationService.translateText( |
|
|
translationResult = await _translationService.translateText( |
|
|
@ -352,25 +428,28 @@ class TranslationController extends GetxController { |
|
|
// 查找匹配的项目 - 优先使用timestamp查找 |
|
|
// 查找匹配的项目 - 优先使用timestamp查找 |
|
|
int index = -1; |
|
|
int index = -1; |
|
|
|
|
|
|
|
|
index = translationHistory.indexWhere((item) => |
|
|
index = translationHistory |
|
|
item.timestamp.isAtSameMomentAs(timestamp)); |
|
|
.indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp)); |
|
|
if (index != -1) { |
|
|
if (index != -1) { |
|
|
// 更新历史项 |
|
|
// 更新历史项 |
|
|
translationHistory[index].translatedText = translationResult; |
|
|
translationHistory[index].translatedText = translationResult; |
|
|
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; |
|
|
translationHistory[index].sourceLanguageCode = |
|
|
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; |
|
|
detectedSourceLanguageCode; |
|
|
|
|
|
translationHistory[index].targetLanguageCode = |
|
|
|
|
|
detectedTargetLanguageCode; |
|
|
translationHistory.refresh(); |
|
|
translationHistory.refresh(); |
|
|
saveTranslationHistory(); |
|
|
saveTranslationHistory(); |
|
|
// 更新后滚动到底部 |
|
|
// 更新后滚动到底部 |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
|
|
|
|
|
|
// 播放TTS - 只在最终结果时播放 |
|
|
// 播放TTS - 只在最终结果时播放 |
|
|
if (isTtsEnabled.value && isFinal) { |
|
|
if (isTtsEnabled.value && isFinal) { |
|
|
// Logger.info('播放翻译文本: ${translationResult}'); |
|
|
// Logger.info('播放翻译文本: ${translationResult}'); |
|
|
await playTranslatedText(translationResult); |
|
|
await playTranslatedText(translationResult); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
Logger.info('未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); |
|
|
Logger.info( |
|
|
|
|
|
'未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -388,9 +467,12 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value; |
|
|
final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value; |
|
|
final detectedTargetLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; |
|
|
final detectedTargetLanguageCode = |
|
|
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? 'en-US-AriaNeural'; |
|
|
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; |
|
|
|
|
|
final voiceName = _languageManager |
|
|
|
|
|
.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? |
|
|
|
|
|
'en-US-AriaNeural'; |
|
|
|
|
|
|
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.speakOnce(text); |
|
|
await _ttsService.speakOnce(text); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -404,7 +486,7 @@ class TranslationController extends GetxController { |
|
|
if (isRecognizing.value) { |
|
|
if (isRecognizing.value) { |
|
|
await stopRecognition(); |
|
|
await stopRecognition(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
_translationDebounceTimer?.cancel(); |
|
|
_translationDebounceTimer?.cancel(); |
|
|
await _ttsService.stop(); |
|
|
await _ttsService.stop(); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -415,9 +497,11 @@ class TranslationController extends GetxController { |
|
|
// 播放特定翻译项 |
|
|
// 播放特定翻译项 |
|
|
Future<void> playTranslationItem(TranslationItem item) async { |
|
|
Future<void> playTranslationItem(TranslationItem item) async { |
|
|
if (item.translatedText.isEmpty) return; |
|
|
if (item.translatedText.isEmpty) return; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural'; |
|
|
final voiceName = |
|
|
|
|
|
_languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? |
|
|
|
|
|
'en-US-AriaNeural'; |
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.speakOnce(item.translatedText); |
|
|
await _ttsService.speakOnce(item.translatedText); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -428,12 +512,15 @@ class TranslationController extends GetxController { |
|
|
// 播放文本 |
|
|
// 播放文本 |
|
|
Future<void> playText(String text, {String? languageCode}) async { |
|
|
Future<void> playText(String text, {String? languageCode}) async { |
|
|
if (text.isEmpty) return; |
|
|
if (text.isEmpty) return; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final voiceName = languageCode != null |
|
|
final voiceName = languageCode != null |
|
|
? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural' |
|
|
? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? |
|
|
: _languageManager.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural'; |
|
|
'en-US-AriaNeural' |
|
|
|
|
|
: _languageManager |
|
|
|
|
|
.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? |
|
|
|
|
|
'en-US-AriaNeural'; |
|
|
|
|
|
|
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.setVoice(voiceName); |
|
|
await _ttsService.speakOnce(text); |
|
|
await _ttsService.speakOnce(text); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -443,7 +530,8 @@ class TranslationController extends GetxController { |
|
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
// 滚动到底部 |
|
|
void _scrollToBottom() { |
|
|
void _scrollToBottom() { |
|
|
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { |
|
|
if (scrollController.hasClients && |
|
|
|
|
|
(translationHistory.isNotEmpty || currentSessionId != null)) { |
|
|
try { |
|
|
try { |
|
|
scrollController.animateTo( |
|
|
scrollController.animateTo( |
|
|
scrollController.position.maxScrollExtent, |
|
|
scrollController.position.maxScrollExtent, |
|
|
@ -460,33 +548,33 @@ class TranslationController extends GetxController { |
|
|
void setupScrollListener() { |
|
|
void setupScrollListener() { |
|
|
// 简化后的空方法,保留接口兼容性 |
|
|
// 简化后的空方法,保留接口兼容性 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 底部控制栏高度 |
|
|
// 底部控制栏高度 |
|
|
double _bottomBarHeight = 0.0; |
|
|
double _bottomBarHeight = 0.0; |
|
|
|
|
|
|
|
|
// 设置底部控制栏高度 |
|
|
// 设置底部控制栏高度 |
|
|
void setBottomBarHeight(double height) { |
|
|
void setBottomBarHeight(double height) { |
|
|
_bottomBarHeight = height; |
|
|
_bottomBarHeight = height; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 获取底部控制栏高度 |
|
|
// 获取底部控制栏高度 |
|
|
double get bottomBarHeight => _bottomBarHeight; |
|
|
double get bottomBarHeight => _bottomBarHeight; |
|
|
|
|
|
|
|
|
// 切换源语言和目标语言 |
|
|
// 切换源语言和目标语言 |
|
|
void swapLanguages() { |
|
|
void swapLanguages() { |
|
|
stopAll(); |
|
|
stopAll(); |
|
|
|
|
|
|
|
|
final tempName = sourceLanguage.value; |
|
|
final tempName = sourceLanguage.value; |
|
|
sourceLanguage.value = targetLanguage.value; |
|
|
sourceLanguage.value = targetLanguage.value; |
|
|
targetLanguage.value = tempName; |
|
|
targetLanguage.value = tempName; |
|
|
|
|
|
|
|
|
final tempCode = sourceLanguageCode.value; |
|
|
final tempCode = sourceLanguageCode.value; |
|
|
sourceLanguageCode.value = targetLanguageCode.value; |
|
|
sourceLanguageCode.value = targetLanguageCode.value; |
|
|
targetLanguageCode.value = tempCode; |
|
|
targetLanguageCode.value = tempCode; |
|
|
|
|
|
|
|
|
// 保存语言设置 |
|
|
// 保存语言设置 |
|
|
_saveLanguageSettings(); |
|
|
_saveLanguageSettings(); |
|
|
|
|
|
|
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
_reinitializeAsrService(); |
|
|
_reinitializeAsrService(); |
|
|
} |
|
|
} |
|
|
@ -495,17 +583,17 @@ class TranslationController extends GetxController { |
|
|
void setSourceLanguage(String language) { |
|
|
void setSourceLanguage(String language) { |
|
|
if (sourceLanguage.value != language) { |
|
|
if (sourceLanguage.value != language) { |
|
|
stopAll(); |
|
|
stopAll(); |
|
|
|
|
|
|
|
|
sourceLanguage.value = language; |
|
|
sourceLanguage.value = language; |
|
|
|
|
|
|
|
|
final asrCode = _languageManager.getAsrCodeByChineseName(language); |
|
|
final asrCode = _languageManager.getAsrCodeByChineseName(language); |
|
|
if (asrCode != null) { |
|
|
if (asrCode != null) { |
|
|
sourceLanguageCode.value = asrCode; |
|
|
sourceLanguageCode.value = asrCode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 保存语言设置 |
|
|
// 保存语言设置 |
|
|
_saveLanguageSettings(); |
|
|
_saveLanguageSettings(); |
|
|
|
|
|
|
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
_reinitializeAsrService(); |
|
|
_reinitializeAsrService(); |
|
|
} |
|
|
} |
|
|
@ -515,35 +603,38 @@ class TranslationController extends GetxController { |
|
|
void setTargetLanguage(String language) { |
|
|
void setTargetLanguage(String language) { |
|
|
if (targetLanguage.value != language) { |
|
|
if (targetLanguage.value != language) { |
|
|
stopAll(); |
|
|
stopAll(); |
|
|
|
|
|
|
|
|
targetLanguage.value = language; |
|
|
targetLanguage.value = language; |
|
|
|
|
|
|
|
|
final asrCode = _languageManager.getAsrCodeByChineseName(language); |
|
|
final asrCode = _languageManager.getAsrCodeByChineseName(language); |
|
|
if (asrCode != null) { |
|
|
if (asrCode != null) { |
|
|
targetLanguageCode.value = asrCode; |
|
|
targetLanguageCode.value = asrCode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 保存语言设置 |
|
|
// 保存语言设置 |
|
|
_saveLanguageSettings(); |
|
|
_saveLanguageSettings(); |
|
|
|
|
|
|
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
// 更新ASR支持的语言并重新初始化 |
|
|
_reinitializeAsrService(); |
|
|
_reinitializeAsrService(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重新初始化ASR服务以支持新的语言 |
|
|
// 重新初始化ASR服务以支持新的语言 |
|
|
Future<void> _reinitializeAsrService() async { |
|
|
Future<void> _reinitializeAsrService() async { |
|
|
try { |
|
|
try { |
|
|
// 更新ASR支持的语言 |
|
|
// 更新ASR支持的语言 |
|
|
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; |
|
|
final List<String> asrSupportedLanguages = [ |
|
|
|
|
|
sourceLanguageCode.value, |
|
|
|
|
|
targetLanguageCode.value |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); |
|
|
Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); |
|
|
|
|
|
|
|
|
// 重新初始化ASR服务 |
|
|
// 重新初始化ASR服务 |
|
|
await _asrService.initialize( |
|
|
await _asrService.initialize( |
|
|
supportedLanguages: asrSupportedLanguages, |
|
|
supportedLanguages: asrSupportedLanguages, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
Logger.info('ASR服务重新初始化成功'); |
|
|
Logger.info('ASR服务重新初始化成功'); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('重新初始化ASR服务失败: $e'); |
|
|
Logger.error('重新初始化ASR服务失败: $e'); |
|
|
@ -556,15 +647,17 @@ class TranslationController extends GetxController { |
|
|
if (historyJson != null) { |
|
|
if (historyJson != null) { |
|
|
try { |
|
|
try { |
|
|
final List<dynamic> historyList = jsonDecode(historyJson); |
|
|
final List<dynamic> historyList = jsonDecode(historyJson); |
|
|
final List<TranslationItem> allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); |
|
|
final List<TranslationItem> allHistory = |
|
|
|
|
|
historyList.map((item) => TranslationItem.fromJson(item)).toList(); |
|
|
|
|
|
|
|
|
// 只保留最新的10条记录 |
|
|
// 只保留最新的10条记录 |
|
|
if (allHistory.length > _maxHistoryItems) { |
|
|
if (allHistory.length > _maxHistoryItems) { |
|
|
translationHistory.value = allHistory.sublist(allHistory.length - _maxHistoryItems); |
|
|
translationHistory.value = |
|
|
|
|
|
allHistory.sublist(allHistory.length - _maxHistoryItems); |
|
|
} else { |
|
|
} else { |
|
|
translationHistory.value = allHistory; |
|
|
translationHistory.value = allHistory; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 使用微任务确保在视图完全渲染后滚动到底部 |
|
|
// 使用微任务确保在视图完全渲染后滚动到底部 |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
@ -579,11 +672,13 @@ class TranslationController extends GetxController { |
|
|
// 保存翻译历史 |
|
|
// 保存翻译历史 |
|
|
void saveTranslationHistory() { |
|
|
void saveTranslationHistory() { |
|
|
if (translationHistory.length > _maxHistoryItems) { |
|
|
if (translationHistory.length > _maxHistoryItems) { |
|
|
translationHistory.removeRange(0, translationHistory.length - _maxHistoryItems); |
|
|
translationHistory.removeRange( |
|
|
|
|
|
0, translationHistory.length - _maxHistoryItems); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final List<Map<String, dynamic>> historyJson = translationHistory.map((item) => item.toJson()).toList(); |
|
|
final List<Map<String, dynamic>> historyJson = |
|
|
|
|
|
translationHistory.map((item) => item.toJson()).toList(); |
|
|
_storage.write(_historyKey, jsonEncode(historyJson)); |
|
|
_storage.write(_historyKey, jsonEncode(historyJson)); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('保存翻译历史失败: $e'); |
|
|
Logger.error('保存翻译历史失败: $e'); |
|
|
@ -644,7 +739,8 @@ class TranslationItem { |
|
|
sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN', |
|
|
sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN', |
|
|
targetLanguageCode: json['targetLanguageCode'] ?? 'en-US', |
|
|
targetLanguageCode: json['targetLanguageCode'] ?? 'en-US', |
|
|
timestamp: timestamp, |
|
|
timestamp: timestamp, |
|
|
sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), |
|
|
sessionId: |
|
|
|
|
|
json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), |
|
|
isFirstInSession: json['isFirstInSession'] ?? false, |
|
|
isFirstInSession: json['isFirstInSession'] ?? false, |
|
|
isIntermediate: false, // 从存储加载的都是最终结果 |
|
|
isIntermediate: false, // 从存储加载的都是最终结果 |
|
|
); |
|
|
); |
|
|
@ -662,16 +758,18 @@ class TranslationItem { |
|
|
// 不保存isIntermediate,因为存储的都是最终结果 |
|
|
// 不保存isIntermediate,因为存储的都是最终结果 |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 获取源语言的中文名称 |
|
|
// 获取源语言的中文名称 |
|
|
String get sourceLanguageName { |
|
|
String get sourceLanguageName { |
|
|
final languageManager = Get.find<LanguageManager>(); |
|
|
final languageManager = Get.find<LanguageManager>(); |
|
|
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? '未知语言'; |
|
|
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? |
|
|
|
|
|
'未知语言'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 获取目标语言的中文名称 |
|
|
// 获取目标语言的中文名称 |
|
|
String get targetLanguageName { |
|
|
String get targetLanguageName { |
|
|
final languageManager = Get.find<LanguageManager>(); |
|
|
final languageManager = Get.find<LanguageManager>(); |
|
|
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? '未知语言'; |
|
|
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? |
|
|
|
|
|
'未知语言'; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|