Browse Source

接通翻译

weicu
fdp 1 year ago
parent
commit
804255cf24
  1. 5
      lib/data/services/asr_service.dart
  2. 18
      lib/data/services/speech_impl/azure_asr_service.dart
  3. 239
      lib/data/services/speech_impl/flutter_asr_service.dart
  4. 6
      lib/data/services/speech_impl/xunfei_asr_service.dart
  5. 310
      lib/modules/translation/controllers/translation_controller.dart
  6. 32
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  7. 16
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  8. 2
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt
  9. 2
      local_plugins/jieli/lib/jieli.dart
  10. 2
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

5
lib/data/services/asr_service.dart

@ -16,6 +16,11 @@ abstract class AsrService {
/// 返回识别的最终结果 /// 返回识别的最终结果
Future<RecognitionEvent> recognizeOnce(); Future<RecognitionEvent> recognizeOnce();
/// 开始连续语音识别
///
/// 推送音频数据到连续识别中
Future<void> pushAudioData(Uint8List data);
/// 开始连续语音识别 /// 开始连续语音识别
/// ///
/// 返回一个包含识别事件的流 /// 返回一个包含识别事件的流

18
lib/data/services/speech_impl/azure_asr_service.dart

@ -288,4 +288,22 @@ class AzureAsrService extends GetxService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
@override
Future<void> pushAudioData(Uint8List data) async {
try {
// 开始连续识别
final bool result = await _channel.invokeMethod('pushAudioData', {
'AudioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer
});
if (!result) {
_cleanupEventStream();
throw Exception('开始pushAudioData失败');
}
} catch (e) {
Logger.error('开始pushAudioData失败: $e');
rethrow;
}
}
} }

239
lib/data/services/speech_impl/flutter_asr_service.dart

@ -11,26 +11,26 @@ import '../asr_service.dart';
enum AsrState { enum AsrState {
/// 未初始化 /// 未初始化
notInitialized, notInitialized,
/// 已初始化但未开始监听 /// 已初始化但未开始监听
initialized, initialized,
/// 正在监听 /// 正在监听
listening, listening,
/// 正在处理识别结果 /// 正在处理识别结果
processing, processing,
/// 识别完成 /// 识别完成
done, done,
/// 发生错误 /// 发生错误
error, error,
} }
/// Flutter ASR服务,负责本地语音转文本功能 /// Flutter ASR服务,负责本地语音转文本功能
/// 使用speech_to_text插件实现语音识别功能 /// 使用speech_to_text插件实现语音识别功能
class FlutterAsrService extends GetxService implements AsrService { class FlutterAsrService extends GetxService implements AsrService {
static final FlutterAsrService to = Get.put(FlutterAsrService()); static final FlutterAsrService to = Get.put(FlutterAsrService());
// speech_to_text 实例 // speech_to_text 实例
@ -40,34 +40,34 @@ class FlutterAsrService extends GetxService implements AsrService {
final List<String> _supportedLanguages = []; final List<String> _supportedLanguages = [];
@override @override
List<String> get supportedLanguages => _supportedLanguages; List<String> get supportedLanguages => _supportedLanguages;
// 当前选择的语言 // 当前选择的语言
final RxString _currentLocale = ''.obs; final RxString _currentLocale = ''.obs;
String get currentLocale => _currentLocale.value; String get currentLocale => _currentLocale.value;
// 是否可用 // 是否可用
final RxBool _isAvailable = false.obs; final RxBool _isAvailable = false.obs;
bool get isAvailable => _isAvailable.value; bool get isAvailable => _isAvailable.value;
// 是否正在监听 // 是否正在监听
bool _isListening = false; bool _isListening = false;
// 当前ASR状态 // 当前ASR状态
final Rx<AsrState> _asrState = AsrState.notInitialized.obs; final Rx<AsrState> _asrState = AsrState.notInitialized.obs;
AsrState get asrState => _asrState.value; AsrState get asrState => _asrState.value;
// 连续识别模式的结果流控制器 // 连续识别模式的结果流控制器
StreamController<RecognitionEvent>? _continuousRecognitionController; StreamController<RecognitionEvent>? _continuousRecognitionController;
FlutterAsrService() { FlutterAsrService() {
_initSpeech(); _initSpeech();
} }
/// 初始化语音识别 /// 初始化语音识别
Future<void> _initSpeech() async { Future<void> _initSpeech() async {
try { try {
_asrState.value = AsrState.notInitialized; _asrState.value = AsrState.notInitialized;
// 初始化语音识别,使用较短的最终超时时间,提高响应速度 // 初始化语音识别,使用较短的最终超时时间,提高响应速度
_isAvailable.value = await _speech.initialize( _isAvailable.value = await _speech.initialize(
onStatus: _onSpeechStatus, onStatus: _onSpeechStatus,
@ -75,11 +75,11 @@ class FlutterAsrService extends GetxService implements AsrService {
debugLogging: false, debugLogging: false,
finalTimeout: const Duration(milliseconds: 800), // 更短的最终超时时间,加速结果返回 finalTimeout: const Duration(milliseconds: 800), // 更短的最终超时时间,加速结果返回
); );
if (_isAvailable.value) { if (_isAvailable.value) {
// 获取支持的语言列表 // 获取支持的语言列表
final systemLocales = await _speech.locales(); final systemLocales = await _speech.locales();
// 清空并重新填充支持的语言列表 // 清空并重新填充支持的语言列表
_supportedLanguages.clear(); _supportedLanguages.clear();
for (var locale in systemLocales) { for (var locale in systemLocales) {
@ -87,7 +87,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_supportedLanguages.add(locale.localeId); _supportedLanguages.add(locale.localeId);
} }
} }
// 设置默认语言(优先使用系统语言或中文) // 设置默认语言(优先使用系统语言或中文)
var systemLocale = await _speech.systemLocale(); var systemLocale = await _speech.systemLocale();
if (systemLocale != null) { if (systemLocale != null) {
@ -97,7 +97,7 @@ class FlutterAsrService extends GetxService implements AsrService {
} else if (_supportedLanguages.isNotEmpty) { } else if (_supportedLanguages.isNotEmpty) {
_currentLocale.value = _supportedLanguages.first; _currentLocale.value = _supportedLanguages.first;
} }
_asrState.value = AsrState.initialized; _asrState.value = AsrState.initialized;
Logger.info('语音识别初始化成功,支持 ${_supportedLanguages.length} 种语言'); Logger.info('语音识别初始化成功,支持 ${_supportedLanguages.length} 种语言');
} else { } else {
@ -110,11 +110,11 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('语音识别初始化失败: $e'); Logger.error('语音识别初始化失败: $e');
} }
} }
/// 处理语音识别状态变化 /// 处理语音识别状态变化
void _onSpeechStatus(String status) { void _onSpeechStatus(String status) {
Logger.info('语音识别状态: $status'); Logger.info('语音识别状态: $status');
switch (status) { switch (status) {
case 'listening': case 'listening':
_asrState.value = AsrState.listening; _asrState.value = AsrState.listening;
@ -129,31 +129,33 @@ class FlutterAsrService extends GetxService implements AsrService {
case 'done': case 'done':
_asrState.value = AsrState.done; _asrState.value = AsrState.done;
_isListening = false; _isListening = false;
// 在连续识别模式下,当一次识别完成后立即开始下一次识别 // 在连续识别模式下,当一次识别完成后立即开始下一次识别
if (_continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) { if (_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition(); _restartContinuousRecognition();
} }
break; break;
} }
} }
/// 处理语音识别错误 /// 处理语音识别错误
void _onSpeechError(SpeechRecognitionError error) { void _onSpeechError(SpeechRecognitionError error) {
Logger.error('语音识别错误: ${error.errorMsg} (永久性错误: ${error.permanent})'); Logger.error('语音识别错误: ${error.errorMsg} (永久性错误: ${error.permanent})');
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
// 向连续识别控制器发送错误事件(如果存在) // 向连续识别控制器发送错误事件(如果存在)
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent.error(error.errorMsg) ?.add(RecognitionEvent.error(error.errorMsg));
);
// 在连续识别模式下,尝试从错误中恢复(非永久性错误) // 在连续识别模式下,尝试从错误中恢复(非永久性错误)
if (!error.permanent && _continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) { if (!error.permanent &&
_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition(); _restartContinuousRecognition();
} }
} }
/// 启动语音识别的统一方法 /// 启动语音识别的统一方法
Future<bool> _startListening({ Future<bool> _startListening({
required Function(SpeechRecognitionResult) onResult, required Function(SpeechRecognitionResult) onResult,
@ -168,36 +170,37 @@ class FlutterAsrService extends GetxService implements AsrService {
// 延长等待时间,确保资源完全释放 // 延长等待时间,确保资源完全释放
await Future.delayed(const Duration(milliseconds: 800)); await Future.delayed(const Duration(milliseconds: 800));
} }
// 在启动前再次检查状态 // 在启动前再次检查状态
if (_speech.isListening) { if (_speech.isListening) {
Logger.warning('停止识别后,仍处于监听状态,可能有系统冲突'); Logger.warning('停止识别后,仍处于监听状态,可能有系统冲突');
await _speech.stop(); await _speech.stop();
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
} }
// 启动语音识别前确保状态正确 // 启动语音识别前确保状态正确
_asrState.value = AsrState.listening; _asrState.value = AsrState.listening;
// 启动语音识别 // 启动语音识别
final result = await _speech.listen( final result = await _speech.listen(
onResult: onResult, onResult: onResult,
localeId: _currentLocale.value, localeId: _currentLocale.value,
listenFor: listenFor, listenFor: listenFor,
pauseFor: pauseFor, pauseFor: pauseFor,
listenOptions: stt.SpeechListenOptions( listenOptions: stt.SpeechListenOptions(
partialResults: true, partialResults: true,
autoPunctuation: autoPunctuation, autoPunctuation: autoPunctuation,
// 使用独白模式,减少与系统交互冲突 // 使用独白模式,减少与系统交互冲突
listenMode: stt.ListenMode.dictation, listenMode: stt.ListenMode.dictation,
), ),
) ?? false; ) ??
false;
if (!result) { if (!result) {
Logger.error('语音识别启动失败'); Logger.error('语音识别启动失败');
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
} }
return result; return result;
} catch (e) { } catch (e) {
Logger.error('启动语音识别失败: $e'); Logger.error('启动语音识别失败: $e');
@ -205,33 +208,35 @@ class FlutterAsrService extends GetxService implements AsrService {
return false; return false;
} }
} }
/// 重新启动连续识别 /// 重新启动连续识别
Future<void> _restartContinuousRecognition() async { Future<void> _restartContinuousRecognition() async {
try { try {
// 确保控制器有效 // 确保控制器有效
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) { if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('连续识别控制器已关闭,跳过重启'); Logger.info('连续识别控制器已关闭,跳过重启');
return; return;
} }
// 检查系统状态,避免频繁重启 // 检查系统状态,避免频繁重启
if (_speech.isListening) { if (_speech.isListening) {
Logger.info('当前已在监听中,无需重启'); Logger.info('当前已在监听中,无需重启');
return; return;
} }
// 延长等待时间,避免与系统资源冲突 // 延长等待时间,避免与系统资源冲突
await Future.delayed(const Duration(milliseconds: 1000)); await Future.delayed(const Duration(milliseconds: 1000));
// 再次检查状态 // 再次检查状态
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) { if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('延迟期间控制器已关闭,跳过重启'); Logger.info('延迟期间控制器已关闭,跳过重启');
return; return;
} }
Logger.info('尝试重新启动连续识别'); Logger.info('尝试重新启动连续识别');
// 使用统一方法启动识别 // 使用统一方法启动识别
final success = await _startListening( final success = await _startListening(
onResult: _onSpeechResult, onResult: _onSpeechResult,
@ -239,49 +244,42 @@ class FlutterAsrService extends GetxService implements AsrService {
listenFor: const Duration(seconds: 30), listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 4), pauseFor: const Duration(seconds: 4),
); );
if (success) { if (success) {
Logger.info('连续识别重启成功'); Logger.info('连续识别重启成功');
} else { } else {
Logger.error('连续识别重启失败'); Logger.error('连续识别重启失败');
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突') ?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
);
} }
} catch (e) { } catch (e) {
Logger.error('重启连续识别失败: $e'); Logger.error('重启连续识别失败: $e');
// 发送错误给控制器 // 发送错误给控制器
_continuousRecognitionController?.add( _continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
RecognitionEvent.error('重启失败: $e')
);
} }
} }
/// 处理语音识别结果 /// 处理语音识别结果
void _onSpeechResult(SpeechRecognitionResult result) { void _onSpeechResult(SpeechRecognitionResult result) {
// 如果是连续识别,发送到连续识别控制器 // 如果是连续识别,发送到连续识别控制器
if (_continuousRecognitionController != null) { if (_continuousRecognitionController != null) {
if (result.finalResult) { if (result.finalResult) {
Logger.info('收到最终识别结果: "${result.recognizedWords}"'); Logger.info('收到最终识别结果: "${result.recognizedWords}"');
_continuousRecognitionController!.add( _continuousRecognitionController!.add(RecognitionEvent.finalResult(
RecognitionEvent.finalResult( text: result.recognizedWords,
text: result.recognizedWords, detectedLanguage: currentLocale,
detectedLanguage: currentLocale, ));
)
);
} else { } else {
_continuousRecognitionController!.add( _continuousRecognitionController!.add(RecognitionEvent(
RecognitionEvent( type: RecognitionEventType.intermediateResult,
type: RecognitionEventType.intermediateResult, text: result.recognizedWords,
text: result.recognizedWords, detectedLanguage: currentLocale,
detectedLanguage: currentLocale, ));
)
);
} }
} }
} }
/// 设置当前语言 /// 设置当前语言
void setLocale(String localeId) { void setLocale(String localeId) {
if (_supportedLanguages.contains(localeId)) { if (_supportedLanguages.contains(localeId)) {
@ -291,7 +289,7 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('不支持的语言: $localeId'); Logger.error('不支持的语言: $localeId');
} }
} }
@override @override
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
@ -301,7 +299,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_supportedLanguages.clear(); _supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages); _supportedLanguages.addAll(supportedLanguages);
} }
// 初始化语音识别 // 初始化语音识别
await _initSpeech(); await _initSpeech();
return _isAvailable.value; return _isAvailable.value;
@ -310,24 +308,24 @@ class FlutterAsrService extends GetxService implements AsrService {
return false; return false;
} }
} }
@override @override
Future<RecognitionEvent> recognizeOnce() async { Future<RecognitionEvent> recognizeOnce() async {
try { try {
if (!_isAvailable.value) { if (!_isAvailable.value) {
return RecognitionEvent.error('语音识别不可用'); return RecognitionEvent.error('语音识别不可用');
} }
if (_isListening) { if (_isListening) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
// 创建Completer来等待最终结果 // 创建Completer来等待最终结果
final completer = Completer<RecognitionEvent>(); final completer = Completer<RecognitionEvent>();
// 开始监听 // 开始监听
_asrState.value = AsrState.listening; _asrState.value = AsrState.listening;
// 定义结果处理函数 // 定义结果处理函数
void resultListener(SpeechRecognitionResult result) { void resultListener(SpeechRecognitionResult result) {
if (result.finalResult) { if (result.finalResult) {
@ -338,21 +336,21 @@ class FlutterAsrService extends GetxService implements AsrService {
)); ));
} }
} }
// 使用统一方法启动识别,单次识别使用更短的时间参数 // 使用统一方法启动识别,单次识别使用更短的时间参数
final success = await _startListening( final success = await _startListening(
onResult: resultListener, onResult: resultListener,
listenFor: const Duration(seconds: 15), listenFor: const Duration(seconds: 15),
pauseFor: const Duration(seconds: 2), pauseFor: const Duration(seconds: 2),
); );
if (!success) { if (!success) {
return RecognitionEvent.error('启动语音识别失败'); return RecognitionEvent.error('启动语音识别失败');
} }
// 设置较短的超时时间 // 设置较短的超时时间
return await completer.future.timeout( return await completer.future.timeout(
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果 const Duration(seconds: 20), // 略长于listenFor以确保获取到结果
onTimeout: () { onTimeout: () {
_speech.stop(); _speech.stop();
return RecognitionEvent.error('语音识别超时'); return RecognitionEvent.error('语音识别超时');
@ -363,7 +361,7 @@ class FlutterAsrService extends GetxService implements AsrService {
return RecognitionEvent.error('$e'); return RecognitionEvent.error('$e');
} }
} }
@override @override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async { Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
try { try {
@ -373,43 +371,42 @@ class FlutterAsrService extends GetxService implements AsrService {
controller.close(); controller.close();
return controller.stream; return controller.stream;
} }
// 如果已经在监听,先停止 // 如果已经在监听,先停止
if (_isListening) { if (_isListening) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
// 创建新的流控制器 // 创建新的流控制器
_continuousRecognitionController = StreamController<RecognitionEvent>.broadcast(); _continuousRecognitionController =
StreamController<RecognitionEvent>.broadcast();
// 开始会话 // 开始会话
_continuousRecognitionController!.add( _continuousRecognitionController!
RecognitionEvent(type: RecognitionEventType.sessionStarted) .add(RecognitionEvent(type: RecognitionEventType.sessionStarted));
);
// 开始连续监听 // 开始连续监听
_asrState.value = AsrState.listening; _asrState.value = AsrState.listening;
// 使用统一方法启动识别,连续识别使用较长的参数 // 使用统一方法启动识别,连续识别使用较长的参数
final success = await _startListening( final success = await _startListening(
onResult: _onSpeechResult, onResult: _onSpeechResult,
); );
if (!success) { if (!success) {
// 添加错误事件,但保留流 // 添加错误事件,但保留流
_continuousRecognitionController!.add( _continuousRecognitionController!
RecognitionEvent.error('启动连续语音识别失败') .add(RecognitionEvent.error('启动连续语音识别失败'));
);
// 返回已有的流控制器 // 返回已有的流控制器
return _continuousRecognitionController!.stream; return _continuousRecognitionController!.stream;
} }
_isListening = true; _isListening = true;
return _continuousRecognitionController!.stream; return _continuousRecognitionController!.stream;
} catch (e) { } catch (e) {
Logger.error('启动连续语音识别失败: $e'); Logger.error('启动连续语音识别失败: $e');
// 如果已经创建了控制器,使用它 // 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) { if (_continuousRecognitionController != null) {
_continuousRecognitionController!.add(RecognitionEvent.error('$e')); _continuousRecognitionController!.add(RecognitionEvent.error('$e'));
@ -422,7 +419,7 @@ class FlutterAsrService extends GetxService implements AsrService {
} }
} }
} }
@override @override
Future<bool> stopContinuousRecognition() async { Future<bool> stopContinuousRecognition() async {
try { try {
@ -430,36 +427,35 @@ class FlutterAsrService extends GetxService implements AsrService {
if (_speech.isListening) { if (_speech.isListening) {
await _speech.stop(); await _speech.stop();
} }
_isListening = false; _isListening = false;
// 发送会话结束事件 // 发送会话结束事件
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent(type: RecognitionEventType.sessionStopped) ?.add(RecognitionEvent(type: RecognitionEventType.sessionStopped));
);
// 关闭流控制器 // 关闭流控制器
await _continuousRecognitionController?.close(); await _continuousRecognitionController?.close();
_continuousRecognitionController = null; _continuousRecognitionController = null;
_asrState.value = AsrState.done; _asrState.value = AsrState.done;
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: $e');
// 强制重置状态 // 强制重置状态
_isListening = false; _isListening = false;
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
return false; return false;
} }
} }
@override @override
bool isContinuousRecognitionActive() { bool isContinuousRecognitionActive() {
return _isListening; return _isListening;
} }
@override @override
Future<void> dispose() async { Future<void> dispose() async {
try { try {
@ -467,15 +463,18 @@ class FlutterAsrService extends GetxService implements AsrService {
if (_speech.isListening) { if (_speech.isListening) {
await _speech.stop(); await _speech.stop();
} }
// 关闭控制器 // 关闭控制器
await _continuousRecognitionController?.close(); await _continuousRecognitionController?.close();
_continuousRecognitionController = null; _continuousRecognitionController = null;
_isListening = false; _isListening = false;
_asrState.value = AsrState.notInitialized; _asrState.value = AsrState.notInitialized;
} catch (e) { } catch (e) {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: $e');
} }
} }
}
@override
Future<void> pushAudioData(Uint8List data) async {}
}

6
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -269,4 +269,10 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
@override
Future<void> pushAudioData(Uint8List data) {
// TODO: implement pushAudioData
throw UnimplementedError();
}
} }

310
lib/modules/translation/controllers/translation_controller.dart

@ -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) ??
'未知语言';
} }
} }

32
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -28,7 +28,7 @@ class AzureAsrHelper(private val context: Context) {
private var supportedLanguages = arrayOf("zh-CN") private var supportedLanguages = arrayOf("zh-CN")
// 是否使用回音消除 - 内部控制常量 // 是否使用回音消除 - 内部控制常量
private val useEchoCancellation = false private val useEchoCancellation = true
// 自定义音频处理相关 // 自定义音频处理相关
private var customAudioProcessor: CustomAudioProcessor? = null private var customAudioProcessor: CustomAudioProcessor? = null
@ -36,6 +36,7 @@ class AzureAsrHelper(private val context: Context) {
private var audioConfig: AudioConfig? = null private var audioConfig: AudioConfig? = null
// 公共方法:允许外部写入音频数据 // 公共方法:允许外部写入音频数据
fun pushAudioData(data: ByteArray) { fun pushAudioData(data: ByteArray) {
FileLogger.d(TAG, "pushAudioData,data=${data},pushStream=${pushStream}")
pushStream?.write(data) // 使用 SDK 的正确方法(如 push) pushStream?.write(data) // 使用 SDK 的正确方法(如 push)
} }
@ -85,6 +86,7 @@ class AzureAsrHelper(private val context: Context) {
// 创建识别器 // 创建识别器
try { try {
if (useEchoCancellation) { if (useEchoCancellation) {
FileLogger.d(TAG, "如果使用回音消除,创建自定义音频输入流")
// 如果使用回音消除,创建自定义音频输入流 // 如果使用回音消除,创建自定义音频输入流
setupCustomAudioProcessing() setupCustomAudioProcessing()
@ -95,13 +97,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig) recognizer = SpeechRecognizer(speechConfig, audioConfig)
} }
} else { } else {
// 使用默认麦克风输入 // // 使用默认麦克风输入
if (isAutoDetectLanguage) { // if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) // val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig) // recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else { // } else {
recognizer = SpeechRecognizer(speechConfig) // recognizer = SpeechRecognizer(speechConfig)
} // }
} }
FileLogger.d(TAG, "Azure 语音服务初始化成功") FileLogger.d(TAG, "Azure 语音服务初始化成功")
@ -140,13 +142,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig) recognizer = SpeechRecognizer(speechConfig, audioConfig)
} }
} else { } else {
// 使用默认麦克风输入 // // 使用默认麦克风输入
if (isAutoDetectLanguage) { // if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) // val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig) // recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else { // } else {
recognizer = SpeechRecognizer(speechConfig) // recognizer = SpeechRecognizer(speechConfig)
} // }
} }
return true return true
} else { } else {

16
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -4,6 +4,7 @@ import android.content.Context
import android.app.Activity import android.app.Activity
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull import androidx.annotation.NonNull
import com.yunqiinnovation.azure_speech.utils.FileLogger import com.yunqiinnovation.azure_speech.utils.FileLogger
@ -91,6 +92,21 @@ class AzureSpeechPlugin: FlutterPlugin {
} catch (e: Exception) { } catch (e: Exception) {
result.error("INITIALIZATION_ERROR", e.message, null) result.error("INITIALIZATION_ERROR", e.message, null)
} }
}
"pushAudioData" -> {
val audioData = call.argument<ByteArray>("AudioData")
try {
Log.d("","AsrMethodHandler,pushAudioData${audioData}")
if (audioData != null) {
val success = azureAsrHelper.pushAudioData(audioData)
result.success(success)
}
} catch (e: Exception) {
result.error("pushAudioData_ERROR", e.message, null)
}
} }
"recognizeOnce" -> { "recognizeOnce" -> {
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {

2
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt

@ -92,7 +92,7 @@ class JieliPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
pluginScope.launch(Dispatchers.Main) { pluginScope.launch(Dispatchers.Main) {
Log.d("","数据流onAudioDataReceived${data}") // Log.d("","数据流onAudioDataReceived${data}")
sendEvent(EVENT_TYPE_AUDIO_DATA, data) sendEvent(EVENT_TYPE_AUDIO_DATA, data)
} }
} }

2
local_plugins/jieli/lib/jieli.dart

@ -127,7 +127,7 @@ class Jieli {
break; break;
case EventType.audioData: case EventType.audioData:
if (data is Uint8List) { if (data is Uint8List) {
print('收到ble通道消息: $data'); //print('收到ble通道消息: $data');
_audioController.add(data); _audioController.add(data);
} }
break; break;

2
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -324,7 +324,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) { override fun onDecodeStream(data: ByteArray?) {
if (data != null) { if (data != null) {
Log.d(TAG, "onDecodeStream${data}") //Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data) callback.onAudioDataReceived1(data)
sendEventOnMainThread(mapOf( sendEventOnMainThread(mapOf(
"event" to "onDecodeStream", "event" to "onDecodeStream",

Loading…
Cancel
Save