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.
246 lines
6.7 KiB
246 lines
6.7 KiB
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:get/get.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import '../../../data/services/asr_service.dart';
|
|
import '../../../data/services/speech_impl/xunfei_asr_service.dart';
|
|
import '../../../data/services/tts_service.dart';
|
|
import '../../../data/services/language_manager.dart';
|
|
import '../../../data/services/volcano_translation_service.dart';
|
|
|
|
class FTFTranslationController extends GetxController
|
|
with GetSingleTickerProviderStateMixin {
|
|
final messages = <FTFTranslationMessage>[].obs;
|
|
final isChineseInput = true.obs;
|
|
final leftLanguage = '中文'.obs;
|
|
final rightLanguage = 'English'.obs;
|
|
final isLeftInput = true.obs;
|
|
|
|
final supportedLanguages = const [
|
|
'中文',
|
|
'English',
|
|
'日本語',
|
|
'한국어',
|
|
'Español',
|
|
];
|
|
|
|
late final AsrService _asrService;
|
|
|
|
StreamSubscription? _recognitionSubscription;
|
|
|
|
RxString intermediateContent = ''.obs;
|
|
RxString finalContent = ''.obs;
|
|
BytesBuilder _originaBytes = BytesBuilder(); //原始音频
|
|
|
|
final VolcanoTranslationService _translationService =
|
|
Get.find<VolcanoTranslationService>();
|
|
final LanguageManager _languageManager = Get.find<LanguageManager>();
|
|
// 当前使用的TTS服务
|
|
late TtsService _ttsService;
|
|
final isTranslating = false.obs;
|
|
final sourceLanguageCode = 'zh-CN'.obs;
|
|
final targetLanguageCode = 'en-US'.obs;
|
|
final isTtsEnabled = true.obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
Get.put(XunfeiAsrService());
|
|
final TtsService _ttsService = Get.find<TtsService>();
|
|
_asrService = Get.find<XunfeiAsrService>();
|
|
_InfoRecorder();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_asrService.dispose();
|
|
_recognitionSubscription?.cancel();
|
|
super.onClose();
|
|
}
|
|
|
|
//开始录音
|
|
Future<void> _InfoRecorder() async {
|
|
final recognitionStream = await _asrService.recognizeCallback();
|
|
_recognitionSubscription = recognitionStream.listen(
|
|
_handleRecognitionEvent,
|
|
);
|
|
}
|
|
|
|
//开始录音
|
|
Future<void> _startRecorder() async {
|
|
await _asrService.startContinuousRecognition(false);
|
|
}
|
|
|
|
//结束录音
|
|
Future<void> _stopRecorder() async {
|
|
await _asrService.stopContinuousRecognition();
|
|
}
|
|
|
|
// 处理语音识别事件
|
|
void _handleRecognitionEvent(RecognitionEvent event) {
|
|
switch (event.type) {
|
|
case RecognitionEventType.intermediateResult:
|
|
intermediateContent.value = event.text;
|
|
handleFinalResult(event.text);
|
|
break;
|
|
case RecognitionEventType.finalResult:
|
|
finalContent.value += _spliceText(event);
|
|
intermediateContent.value = '';
|
|
_originaBytes.add(event.audio!);
|
|
handleIntermediateResult(event.text);
|
|
break;
|
|
default:
|
|
// 不做任何处理
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 翻译文本
|
|
Future<String?> translateText(String sourceText,
|
|
{bool isFinal = false}) async {
|
|
if (sourceText.isEmpty) return null;
|
|
|
|
isTranslating.value = true;
|
|
String? translationResult;
|
|
|
|
try {
|
|
// 根据检测到的语言确定源语言和目标语言
|
|
final bool shouldSwap =
|
|
intermediateContent.value == 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;
|
|
|
|
// 播放TTS - 只在最终结果时播放
|
|
if (isTtsEnabled.value && isFinal) {
|
|
// Logger.info('播放翻译文本: ${translationResult}');
|
|
await playTranslatedText(translationResult);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('翻译失败: ${e.toString()}');
|
|
} finally {
|
|
isTranslating.value = false;
|
|
}
|
|
|
|
return translationResult;
|
|
}
|
|
|
|
// 处理中间识别结果
|
|
void handleIntermediateResult(String text) {
|
|
if (text.isEmpty) return;
|
|
|
|
translateText(text, isFinal: false);
|
|
}
|
|
|
|
// 处理最终识别结果
|
|
Future<void> handleFinalResult(String text) async {
|
|
if (text.isEmpty) return;
|
|
|
|
// 使用项目的时间戳进行翻译
|
|
translateText(text, isFinal: true);
|
|
}
|
|
|
|
// 播放翻译文本
|
|
Future<void> playTranslatedText(String text) async {
|
|
if (text.isEmpty) return;
|
|
|
|
try {
|
|
final bool shouldSwap =
|
|
intermediateContent.value == 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()}');
|
|
}
|
|
}
|
|
|
|
String _spliceText(RecognitionEvent event) {
|
|
String eventText = event.text;
|
|
String text = '';
|
|
if (finalContent.value.isNotEmpty) {
|
|
if (event.role != '') {
|
|
final punctuations = {
|
|
',',
|
|
'。',
|
|
'!',
|
|
'?',
|
|
';',
|
|
':',
|
|
',',
|
|
'.',
|
|
':',
|
|
';',
|
|
'!',
|
|
'?'
|
|
};
|
|
final firstChar = eventText[0];
|
|
if (punctuations.contains(firstChar)) {
|
|
text += '$firstChar\n';
|
|
eventText = eventText.substring(1);
|
|
} else {
|
|
text += '\n';
|
|
}
|
|
text += '说话人${event.role}:';
|
|
}
|
|
} else {
|
|
text += '说话人${event.role}:';
|
|
}
|
|
text += eventText;
|
|
return text;
|
|
}
|
|
|
|
void addMessage(String text) {
|
|
final fromLang =
|
|
isLeftInput.value ? leftLanguage.value : rightLanguage.value;
|
|
final toLang = isLeftInput.value ? rightLanguage.value : leftLanguage.value;
|
|
|
|
messages.add(
|
|
FTFTranslationMessage(
|
|
original: text,
|
|
translated: '[$fromLang → $toLang] $text (翻译模拟)',
|
|
isChineseSpeaker: isLeftInput.value,
|
|
),
|
|
);
|
|
}
|
|
|
|
void swapLanguages() {
|
|
final temp = leftLanguage.value;
|
|
leftLanguage.value = rightLanguage.value;
|
|
rightLanguage.value = temp;
|
|
isLeftInput.value = !isLeftInput.value;
|
|
}
|
|
|
|
void toggleInputSide(bool isLeft) => isLeftInput.value = isLeft;
|
|
}
|
|
|
|
class FTFTranslationMessage {
|
|
final String original;
|
|
final String translated;
|
|
final bool isChineseSpeaker;
|
|
|
|
FTFTranslationMessage({
|
|
required this.original,
|
|
required this.translated,
|
|
required this.isChineseSpeaker,
|
|
});
|
|
}
|
|
|