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.

340 lines
10 KiB

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:async';
import '../../../data/services/azure_asr_service.dart';
import '../../../data/services/volcano_translation_service.dart';
import '../../../data/services/volcano_tts_api_service.dart';
// import '../../../core/utils/logger.dart';
class TranslationController extends GetxController {
// Services
final AzureAsrService _asrService = Get.find<AzureAsrService>();
final VolcanoTranslationService _translationService = Get.find<VolcanoTranslationService>();
final VolcanoTtsApiService _ttsService = Get.find<VolcanoTtsApiService>();
// State variables
final isRecognizing = false.obs;
final isTranslating = false.obs;
final isTtsEnabled = true.obs;
final recognizedText = ''.obs;
final translatedText = ''.obs;
final sourceLanguage = '中文 (Chinese)'.obs;
final targetLanguage = '英语 (English)'.obs;
final translationHistory = <TranslationItem>[].obs;
// Current translation item
TranslationItem? currentItem;
// Subscription for recognition stream
StreamSubscription? _recognitionSubscription;
// Scroll controller for ListView
final ScrollController scrollController = ScrollController();
// Timer for debouncing translation requests
Timer? _translationDebounceTimer;
@override
void onInit() {
super.onInit();
// Initialize the translation service
_initTranslationService();
// Start voice recognition automatically when the page is loaded
startRecognition();
}
@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,
);
}
}
// Toggle TTS feature
void toggleTts() {
isTtsEnabled.toggle();
}
// Start voice recognition
Future<void> startRecognition() async {
if (isRecognizing.value) return;
try {
// Initialize a new translation item
currentItem = TranslationItem(
sourceText: '',
translatedText: '',
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
timestamp: DateTime.now(),
);
// Start the voice recognition service
final success = await _asrService.startContinuousRecognition();
if (success) {
isRecognizing.value = true;
// Reset the recognized text
recognizedText.value = '';
translatedText.value = '';
// Listen for recognition events
_recognitionSubscription = _asrService.recognitionStream?.listen((event) {
switch (event.type) {
case RecognitionEventType.finalResult:
if (event.text.isNotEmpty) {
handleFinalResult(event.text);
}
break;
case RecognitionEventType.recognizing:
// Update the UI with intermediate results
if (event.text.isNotEmpty) {
handleIntermediateResult(event.text);
}
break;
case RecognitionEventType.error:
Get.snackbar(
'Error',
'Recognition error: ${event.error}',
snackPosition: SnackPosition.BOTTOM,
);
break;
default:
break;
}
});
} else {
throw Exception('Failed to start continuous recognition');
}
} catch (e) {
isRecognizing.value = false;
Get.snackbar(
'Error',
'Failed to start voice recognition: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// Stop voice recognition
Future<void> stopRecognition() async {
if (!isRecognizing.value) return;
try {
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isRecognizing.value = false;
// 添加当前项到历史(如果有内容)
finalizeCurrentItem();
// 重置当前显示的文本
recognizedText.value = '';
translatedText.value = '';
} catch (e) {
Get.snackbar(
'Error',
'Failed to stop voice recognition: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// Handle intermediate recognition result
void handleIntermediateResult(String text) {
if (text.isEmpty) return;
// 如果当前没有活跃的翻译项,创建一个新的
if (currentItem == null) {
currentItem = TranslationItem(
sourceText: text,
translatedText: '',
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
timestamp: DateTime.now(),
);
} else {
// 更新当前翻译项
currentItem!.sourceText = text;
}
// 实时更新识别文本
recognizedText.value = text;
// Debounce translation requests to avoid too many API calls
_translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
translateText(text, isFinal: false);
});
// Scroll to the bottom to show the latest content
_scrollToBottom();
}
// Handle final recognition result
void handleFinalResult(String text) {
if (text.isEmpty) return;
// 更新当前翻译项(如果不存在则创建一个)
if (currentItem == null) {
currentItem = TranslationItem(
sourceText: text,
translatedText: '',
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
timestamp: DateTime.now(),
);
} else {
currentItem!.sourceText = text;
}
// 更新识别文本
recognizedText.value = text;
// 翻译最终结果 (添加到历史的逻辑已移至translateText方法)
translateText(text, isFinal: true).then((_) {
// 作为安全措施,确保当前项被添加到历史中
// 这是为了处理translateText中可能因为条件不满足而没有添加到历史的情况
finalizeCurrentItem();
});
// Scroll to the bottom to show the latest content
_scrollToBottom();
}
// Finalize the current translation item and add it to history
void finalizeCurrentItem() {
if (currentItem != null &&
currentItem!.sourceText.isNotEmpty &&
currentItem!.translatedText.isNotEmpty &&
!translationHistory.contains(currentItem)) {
translationHistory.add(currentItem!);
currentItem = null;
}
}
// Handle voice recognition result (legacy method, kept for compatibility)
void handleVoiceResult(String text) {
handleFinalResult(text);
}
// Translate the recognized text
Future<void> translateText(String text, {bool isFinal = true}) async {
if (text.isEmpty) return;
isTranslating.value = true;
// 保存当前的翻译项引用,确保异步返回时更新正确的项
final TranslationItem? itemToUpdate = currentItem;
try {
final result = await _translationService.translateText(
text: text,
sourceLanguage: sourceLanguage.value,
targetLanguage: targetLanguage.value,
);
if (result != null) {
// 实时更新翻译文本
translatedText.value = result;
// 确保更新的是发起翻译请求时的翻译项,而不是当前可能已经变化的翻译项
if (itemToUpdate != null && itemToUpdate.sourceText == text) {
itemToUpdate.translatedText = result;
// 如果是最终结果,并且当前项仍然是发起请求时的项,则添加到历史
if (isFinal && itemToUpdate == currentItem) {
translationHistory.add(itemToUpdate);
currentItem = null; // 重要:将当前项设为null,表示已完成
// 重置当前显示的文本,避免与历史项重复
recognizedText.value = '';
translatedText.value = '';
}
// Play TTS if enabled and this is a final result
if (isTtsEnabled.value && isFinal) {
playTranslatedText(result);
}
}
}
} catch (e) {
Get.snackbar(
'Error',
'Translation failed: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
} finally {
isTranslating.value = false;
}
}
// Play the translated text using TTS
Future<void> playTranslatedText(String text) async {
if (text.isEmpty) return;
try {
await _ttsService.speakSingle(text);
} catch (e) {
Get.snackbar(
'Error',
'Failed to play translation: ${e.toString()}',
snackPosition: SnackPosition.BOTTOM,
);
}
}
// Play a specific translation item
Future<void> playTranslationItem(TranslationItem item) async {
await playTranslatedText(item.translatedText);
}
// Scroll to the bottom of the list
void _scrollToBottom() {
if (scrollController.hasClients) {
Future.delayed(const Duration(milliseconds: 100), () {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
}
}
// Model class for translation items
class TranslationItem {
String sourceText;
String translatedText;
final String sourceLanguage;
final String targetLanguage;
final DateTime timestamp;
TranslationItem({
required this.sourceText,
required this.translatedText,
required this.sourceLanguage,
required this.targetLanguage,
required this.timestamp,
});
}