Browse Source

加入通话翻译,音视频翻译

newdev_shunjiawei
fdp 1 year ago
parent
commit
299b665f8d
  1. 2
      lib/core/translations/language/zh_cn.dart
  2. 5
      lib/data/services/asr_service.dart
  3. 60
      lib/data/services/speech_impl/azure_asr_service.dart
  4. 3
      lib/data/services/speech_impl/flutter_asr_service.dart
  5. 5
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  6. 133
      lib/data/services/speech_impl/volcano_asr_service.dart
  7. 6
      lib/data/services/speech_impl/xunfei_asr_service.dart
  8. 38
      lib/modules/translation/controllers/translation_controller.dart
  9. 68
      lib/modules/translation/views/translation_view.dart
  10. 11
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  11. 13
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  12. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt
  13. 4
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

2
lib/core/translations/language/zh_cn.dart

@ -117,6 +117,8 @@ const Map<String, String> zhCN = {
"allRoundAssistant": "全能助理", "allRoundAssistant": "全能助理",
"allRoundAssistantInfo": "帮你解决各类问题", "allRoundAssistantInfo": "帮你解决各类问题",
"simultaneousTranslation": "同声翻译", "simultaneousTranslation": "同声翻译",
'audioVideoTranslation': '音视频翻译',
'callTranslation': '通话翻译',
"simultaneousTranslationInfo": "实时翻译80+种语言", "simultaneousTranslationInfo": "实时翻译80+种语言",
"aiGirlfriend": "亲子陪伴", "aiGirlfriend": "亲子陪伴",
"aiGirlfriendInfo": "智能陪伴,随时聊天", "aiGirlfriendInfo": "智能陪伴,随时聊天",

5
lib/data/services/asr_service.dart

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

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

@ -8,8 +8,8 @@ import '../asr_service.dart';
/// 音频源类型 /// 音频源类型
enum AudioSourceType { enum AudioSourceType {
microphone, // 使用设备麦克风 microphone, // 使用设备麦克风
external // 使用外部提供的音频数据 external // 使用外部提供的音频数据
} }
/// Azure 语音识别服务 /// Azure 语音识别服务
@ -18,7 +18,8 @@ enum AudioSourceType {
class AzureAsrService extends GetxService implements AsrService { class AzureAsrService extends GetxService implements AsrService {
static final AzureAsrService to = Get.put(AzureAsrService()); static final AzureAsrService to = Get.put(AzureAsrService());
static const MethodChannel _channel = MethodChannel('azure_speech/asr'); static const MethodChannel _channel = MethodChannel('azure_speech/asr');
static const EventChannel _eventChannel = EventChannel('azure_speech/asr_events'); static const EventChannel _eventChannel =
EventChannel('azure_speech/asr_events');
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
bool _isInitialized = false; bool _isInitialized = false;
late final String _subscriptionKey; late final String _subscriptionKey;
@ -77,7 +78,8 @@ class AzureAsrService extends GetxService implements AsrService {
bool useEchoCancellation = false, bool useEchoCancellation = false,
}) async { }) async {
try { try {
final List<String> languages = supportedLanguages ?? _defaultSupportedLanguages; final List<String> languages =
supportedLanguages ?? _defaultSupportedLanguages;
// 检查是否需要重新初始化 // 检查是否需要重新初始化
if (_isInitialized) { if (_isInitialized) {
@ -85,7 +87,9 @@ class AzureAsrService extends GetxService implements AsrService {
} }
// 设置音频源类型 // 设置音频源类型
_audioSourceType = useExternalAudio ? AudioSourceType.external : AudioSourceType.microphone; _audioSourceType = useExternalAudio
? AudioSourceType.external
: AudioSourceType.microphone;
final bool result = await _channel.invokeMethod('initialize', { final bool result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey, 'subscriptionKey': _subscriptionKey,
@ -94,7 +98,7 @@ class AzureAsrService extends GetxService implements AsrService {
'audioSourceType': _audioSourceType.toString().split('.').last, 'audioSourceType': _audioSourceType.toString().split('.').last,
'useEchoCancellation': useEchoCancellation, 'useEchoCancellation': useEchoCancellation,
}); });
_setupEventChannel(); _setupEventChannel();
_isInitialized = result; _isInitialized = result;
@ -120,8 +124,9 @@ class AzureAsrService extends GetxService implements AsrService {
if (result is Map) { if (result is Map) {
// 处理新格式返回 // 处理新格式返回
final String text = result['text'] as String? ?? ''; final String text = result['text'] as String? ?? '';
final String detectedLanguage = result['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
result['detectedLanguage'] as String? ?? '';
// 更新最新识别结果 // 更新最新识别结果
_latestRecognizedText = text; _latestRecognizedText = text;
_latestDetectedLanguage = detectedLanguage; _latestDetectedLanguage = detectedLanguage;
@ -157,7 +162,8 @@ class AzureAsrService extends GetxService implements AsrService {
_eventStreamController = StreamController<RecognitionEvent>.broadcast(); _eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别 // 开始连续识别
final bool result = await _channel.invokeMethod('startContinuousRecognition'); final bool result =
await _channel.invokeMethod('startContinuousRecognition');
if (!result) { if (!result) {
_cleanupEventStream(); _cleanupEventStream();
@ -180,7 +186,8 @@ class AzureAsrService extends GetxService implements AsrService {
} }
try { try {
final bool result = await _channel.invokeMethod('stopContinuousRecognition'); final bool result =
await _channel.invokeMethod('stopContinuousRecognition');
_cleanupEventStream(); _cleanupEventStream();
return result; return result;
} catch (e) { } catch (e) {
@ -208,7 +215,8 @@ class AzureAsrService extends GetxService implements AsrService {
switch (eventType) { switch (eventType) {
case 'result': case 'result':
final String text = eventMap['text'] as String? ?? ''; final String text = eventMap['text'] as String? ?? '';
final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
eventMap['detectedLanguage'] as String? ?? '';
_latestRecognizedText = text; _latestRecognizedText = text;
_latestDetectedLanguage = detectedLanguage; _latestDetectedLanguage = detectedLanguage;
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
@ -220,7 +228,8 @@ class AzureAsrService extends GetxService implements AsrService {
case 'recognizing': case 'recognizing':
final String text = eventMap['text'] as String? ?? ''; final String text = eventMap['text'] as String? ?? '';
final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; final String detectedLanguage =
eventMap['detectedLanguage'] as String? ?? '';
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.intermediateResult, type: RecognitionEventType.intermediateResult,
text: text, text: text,
@ -303,31 +312,4 @@ class AzureAsrService extends GetxService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
@override
Future<bool> pushAudioData(Uint8List data) async {
if (!_isInitialized) {
await initialize(useExternalAudio: true);
}
if (_audioSourceType != AudioSourceType.external) {
Logger.error('当前非外部音频模式,不能推送音频数据');
return false;
}
try {
final bool result = await _channel.invokeMethod('pushAudioData', {
'audioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer
});
if (!result) {
throw Exception('推送音频数据失败');
}
return true;
} catch (e) {
Logger.error('推送音频数据失败: $e');
return false;
}
}
} }

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

@ -474,7 +474,4 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: $e');
} }
} }
@override
Future<void> pushAudioData(Uint8List data) async {}
} }

5
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -809,9 +809,4 @@ class VolcanoAsrApiService implements AsrService {
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
} }
@override
Future<void> pushAudioData(Uint8List data) {
throw UnimplementedError();
}
} }

133
lib/data/services/speech_impl/volcano_asr_service.dart

@ -8,35 +8,36 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../asr_service.dart'; import '../asr_service.dart';
/// 火山语音识别服务实现 /// 火山语音识别服务实现
class VolcanoAsrService extends GetxService implements AsrService { class VolcanoAsrService extends GetxService implements AsrService {
/// 方法通道 /// 方法通道
static const MethodChannel _channel = MethodChannel('volcano_speech/asr'); static const MethodChannel _channel = MethodChannel('volcano_speech/asr');
/// 事件通道 /// 事件通道
static const EventChannel _eventChannel = EventChannel('volcano_speech/asr_events'); static const EventChannel _eventChannel =
EventChannel('volcano_speech/asr_events');
/// 事件流控制器 /// 事件流控制器
final StreamController<RecognitionEvent> _eventStreamController = final StreamController<RecognitionEvent> _eventStreamController =
StreamController<RecognitionEvent>.broadcast(); StreamController<RecognitionEvent>.broadcast();
/// 事件订阅 /// 事件订阅
StreamSubscription? _eventSubscription; StreamSubscription? _eventSubscription;
/// 是否初始化完成 /// 是否初始化完成
bool _isInitialized = false; bool _isInitialized = false;
/// 是否正在连续识别 /// 是否正在连续识别
bool _isContinuousRecognitionActive = false; bool _isContinuousRecognitionActive = false;
/// 当前语言 /// 当前语言
String _currentLanguage = 'zh-CN'; String _currentLanguage = 'zh-CN';
/// 支持的语言列表 /// 支持的语言列表
final List<String> _supportedLanguages = ['zh-CN', 'en-US']; final List<String> _supportedLanguages = ['zh-CN', 'en-US'];
@override @override
List<String> get supportedLanguages => _supportedLanguages; List<String> get supportedLanguages => _supportedLanguages;
/// 构造函数 /// 构造函数
VolcanoAsrService() { VolcanoAsrService() {
_setupEventListener(); _setupEventListener();
@ -48,39 +49,42 @@ class VolcanoAsrService extends GetxService implements AsrService {
List<String>? supportedLanguages, List<String>? supportedLanguages,
}) async { }) async {
if (_isInitialized) return true; if (_isInitialized) return true;
try { try {
// 如果提供了支持的语言列表,则替换默认列表 // 如果提供了支持的语言列表,则替换默认列表
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_supportedLanguages.clear(); _supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages); _supportedLanguages.addAll(supportedLanguages);
} }
// 从.env文件读取配置 // 从.env文件读取配置
final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? '';
final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? '';
final resourceId = dotenv.env['VOLCANO_ASR_RESOURCE_ID'] ?? 'volc.bigasr.sauc.duration'; final resourceId =
dotenv.env['VOLCANO_ASR_RESOURCE_ID'] ?? 'volc.bigasr.sauc.duration';
if (appId.isEmpty || appToken.isEmpty) { if (appId.isEmpty || appToken.isEmpty) {
debugPrint('火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量'); debugPrint(
'火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量');
return false; return false;
} }
// 初始化火山语音ASR // 初始化火山语音ASR
final initResult = await _channel.invokeMethod<bool>('initialize', { final initResult = await _channel.invokeMethod<bool>('initialize', {
'appId': appId, 'appId': appId,
'appToken': appToken, 'appToken': appToken,
'resourceId': resourceId, 'resourceId': resourceId,
}) ?? false; }) ??
false;
if (!initResult) { if (!initResult) {
debugPrint('火山语音ASR初始化失败'); debugPrint('火山语音ASR初始化失败');
return false; return false;
} }
// 设置默认语言、VAD参数和启用音量返回 // 设置默认语言、VAD参数和启用音量返回
await _configureAsr(); await _configureAsr();
_isInitialized = true; _isInitialized = true;
return true; return true;
} catch (e) { } catch (e) {
@ -88,14 +92,13 @@ class VolcanoAsrService extends GetxService implements AsrService {
return false; return false;
} }
} }
/// 配置ASR参数 /// 配置ASR参数
Future<void> _configureAsr() async { Future<void> _configureAsr() async {
// 设置默认语言 // 设置默认语言
await _channel.invokeMethod<bool>('setLanguage', { await _channel.invokeMethod<bool>('setLanguage', {
'language': _currentLanguage, 'language': _currentLanguage,
}); });
} }
/// 设置事件监听 /// 设置事件监听
@ -118,25 +121,25 @@ class VolcanoAsrService extends GetxService implements AsrService {
/// 处理ASR事件 /// 处理ASR事件
void _handleAsrEvent(Map<String, dynamic> event) { void _handleAsrEvent(Map<String, dynamic> event) {
final eventType = event['type'] as String?; final eventType = event['type'] as String?;
switch (eventType) { switch (eventType) {
case 'sessionStarted': case 'sessionStarted':
_eventStreamController.add(RecognitionEvent( _eventStreamController.add(RecognitionEvent(
type: RecognitionEventType.sessionStarted, type: RecognitionEventType.sessionStarted,
)); ));
break; break;
case 'sessionStopped': case 'sessionStopped':
_eventStreamController.add(RecognitionEvent( _eventStreamController.add(RecognitionEvent(
type: RecognitionEventType.sessionStopped, type: RecognitionEventType.sessionStopped,
)); ));
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
break; break;
case 'recognizing': case 'recognizing':
final text = event['text'] as String?; final text = event['text'] as String?;
final language = event['language'] as String?; final language = event['language'] as String?;
if (text != null) { if (text != null) {
debugPrint('接收到中间识别结果: $text'); debugPrint('接收到中间识别结果: $text');
_eventStreamController.add(RecognitionEvent( _eventStreamController.add(RecognitionEvent(
@ -146,11 +149,11 @@ class VolcanoAsrService extends GetxService implements AsrService {
)); ));
} }
break; break;
case 'result': case 'result':
final text = event['text'] as String?; final text = event['text'] as String?;
final language = event['language'] as String?; final language = event['language'] as String?;
if (text != null) { if (text != null) {
debugPrint('接收到最终识别结果: $text'); debugPrint('接收到最终识别结果: $text');
_eventStreamController.add(RecognitionEvent( _eventStreamController.add(RecognitionEvent(
@ -160,10 +163,10 @@ class VolcanoAsrService extends GetxService implements AsrService {
)); ));
} }
break; break;
case 'error': case 'error':
final errorMessage = event['message'] as String?; final errorMessage = event['message'] as String?;
debugPrint('接收到识别错误: ${errorMessage ?? "未知错误"}'); debugPrint('接收到识别错误: ${errorMessage ?? "未知错误"}');
_eventStreamController.add(RecognitionEvent( _eventStreamController.add(RecognitionEvent(
type: RecognitionEventType.error, type: RecognitionEventType.error,
@ -171,11 +174,12 @@ class VolcanoAsrService extends GetxService implements AsrService {
)); ));
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
break; break;
case 'volumeChanged': case 'volumeChanged':
// 音量变化事件仅在日志中输出,不发送到事件流 // 音量变化事件仅在日志中输出,不发送到事件流
final volume = event['volume'] as int?; final volume = event['volume'] as int?;
if (volume != null && volume > 5) { // 仅记录有意义的音量变化 if (volume != null && volume > 5) {
// 仅记录有意义的音量变化
debugPrint('音量变化: $volume'); debugPrint('音量变化: $volume');
} }
break; break;
@ -199,11 +203,11 @@ class VolcanoAsrService extends GetxService implements AsrService {
error: '语音识别服务未初始化', error: '语音识别服务未初始化',
); );
} }
try { try {
// 创建一个Completer用于等待最终结果 // 创建一个Completer用于等待最终结果
final completer = Completer<RecognitionEvent>(); final completer = Completer<RecognitionEvent>();
// 创建一次性事件监听 // 创建一次性事件监听
StreamSubscription? subscription; StreamSubscription? subscription;
subscription = _eventStreamController.stream.listen((event) { subscription = _eventStreamController.stream.listen((event) {
@ -218,10 +222,11 @@ class VolcanoAsrService extends GetxService implements AsrService {
subscription?.cancel(); subscription?.cancel();
} }
}); });
// 启动一次性识别 // 启动一次性识别
final started = await _channel.invokeMethod<bool>('recognizeOnce') ?? false; final started =
await _channel.invokeMethod<bool>('recognizeOnce') ?? false;
if (!started) { if (!started) {
subscription.cancel(); subscription.cancel();
return RecognitionEvent( return RecognitionEvent(
@ -229,7 +234,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
error: '启动一次性识别失败', error: '启动一次性识别失败',
); );
} }
// 设置超时,防止永久等待 // 设置超时,防止永久等待
Future.delayed(const Duration(seconds: 30), () { Future.delayed(const Duration(seconds: 30), () {
if (!completer.isCompleted) { if (!completer.isCompleted) {
@ -242,7 +247,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
_channel.invokeMethod('stopRecognize'); _channel.invokeMethod('stopRecognize');
} }
}); });
// 等待结果 // 等待结果
return await completer.future; return await completer.future;
} catch (e) { } catch (e) {
@ -250,7 +255,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
try { try {
await _channel.invokeMethod('stopRecognize'); await _channel.invokeMethod('stopRecognize');
} catch (_) {} } catch (_) {}
return RecognitionEvent( return RecognitionEvent(
type: RecognitionEventType.error, type: RecognitionEventType.error,
error: e.toString(), error: e.toString(),
@ -268,10 +273,12 @@ class VolcanoAsrService extends GetxService implements AsrService {
)); ));
return _eventStreamController.stream; return _eventStreamController.stream;
} }
try { try {
final success = await _channel.invokeMethod<bool>('startContinuousRecognition') ?? false; final success =
await _channel.invokeMethod<bool>('startContinuousRecognition') ??
false;
if (success) { if (success) {
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
} else { } else {
@ -286,7 +293,7 @@ class VolcanoAsrService extends GetxService implements AsrService {
error: '启动连续识别异常:$e', error: '启动连续识别异常:$e',
)); ));
} }
return _eventStreamController.stream; return _eventStreamController.stream;
} }
@ -296,10 +303,12 @@ class VolcanoAsrService extends GetxService implements AsrService {
if (!_isInitialized || !_isContinuousRecognitionActive) { if (!_isInitialized || !_isContinuousRecognitionActive) {
return false; return false;
} }
try { try {
final success = await _channel.invokeMethod<bool>('stopContinuousRecognition') ?? false; final success =
await _channel.invokeMethod<bool>('stopContinuousRecognition') ??
false;
if (success) { if (success) {
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
} }
@ -310,18 +319,6 @@ class VolcanoAsrService extends GetxService implements AsrService {
} }
} }
/// 推送音频数据到连续识别中
@override
Future<void> pushAudioData(Uint8List data) async {
if (!_isInitialized || !_isContinuousRecognitionActive) {
return;
}
// 注意:此功能需要在引擎初始化时设置音频输入类型为stream
// 由于当前实现默认使用麦克风输入,此方法暂时不会实际处理数据
debugPrint('警告:当前火山语音ASR使用麦克风输入,pushAudioData方法不会起作用');
}
/// 检查连续识别是否活跃 /// 检查连续识别是否活跃
@override @override
bool isContinuousRecognitionActive() { bool isContinuousRecognitionActive() {
@ -334,17 +331,17 @@ class VolcanoAsrService extends GetxService implements AsrService {
if (_isContinuousRecognitionActive) { if (_isContinuousRecognitionActive) {
await stopContinuousRecognition(); await stopContinuousRecognition();
} }
await _eventSubscription?.cancel(); await _eventSubscription?.cancel();
_eventSubscription = null; _eventSubscription = null;
if (!_eventStreamController.isClosed) { if (!_eventStreamController.isClosed) {
await _eventStreamController.close(); await _eventStreamController.close();
} }
if (_isInitialized) { if (_isInitialized) {
await _channel.invokeMethod<bool>('release'); await _channel.invokeMethod<bool>('release');
_isInitialized = false; _isInitialized = false;
} }
} }
} }

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

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

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

@ -57,10 +57,29 @@ class TranslationController extends GetxController {
final targetLanguageCode = 'en-US'.obs; final targetLanguageCode = 'en-US'.obs;
var detectedLanguageCode = ''; var detectedLanguageCode = '';
final currentModeTitle = 'simultaneousTranslation'.obs;
final currentMode = 'simultaneous'.obs;
// 翻译历史和当前项 // 翻译历史和当前项
final translationHistory = <TranslationItem>[].obs; final translationHistory = <TranslationItem>[].obs;
String? currentSessionId; String? currentSessionId;
void changeTranslationMode(String mode) {
currentMode.value = mode;
currentModeTitle.value = _getModeTitle(mode);
// 这里添加具体模式切换逻辑
}
String _getModeTitle(String mode) {
switch (mode) {
case 'audioVideo':
return 'audioVideoTranslation';
case 'call':
return 'callTranslation';
default:
return 'simultaneousTranslation';
}
}
// 兼容旧版视图的计算属性 // 兼容旧版视图的计算属性
Rx<TranslationItem?> get currentItem { Rx<TranslationItem?> get currentItem {
if (translationHistory.isNotEmpty && if (translationHistory.isNotEmpty &&
@ -103,7 +122,6 @@ class TranslationController extends GetxController {
targetLanguageCode.value = targetAsrCode; targetLanguageCode.value = targetAsrCode;
} }
// 加载历史记录 // 加载历史记录
loadTranslationHistory(); loadTranslationHistory();
@ -116,7 +134,6 @@ class TranslationController extends GetxController {
}); });
} }
// 加载保存的语言设置 // 加载保存的语言设置
void _loadSavedLanguages() { void _loadSavedLanguages() {
final savedSourceLanguage = _storage.read(_sourceLanguageKey); final savedSourceLanguage = _storage.read(_sourceLanguageKey);
@ -193,9 +210,7 @@ class TranslationController extends GetxController {
Logger.info('开始语音识别'); Logger.info('开始语音识别');
if (isRecognizing.value) return; if (isRecognizing.value) return;
try { try {
Logger.info('开始语音识别1');
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对 // 每次开始识别前检查 ASR 服务是否已配置为当前语言对
final List<String> currentLanguages = [ final List<String> currentLanguages = [
sourceLanguageCode.value, sourceLanguageCode.value,
@ -225,15 +240,18 @@ class TranslationController extends GetxController {
// // 添加到历史记录 // // 添加到历史记录
// translationHistory.add(newItem); // translationHistory.add(newItem);
if (currentMode.value == "audioVideo") {
// 发送ble音乐或者通话远端声音
Logger.info('发送ble音乐或者通话远端声音');
} else if (currentMode.value == "call") {
// 发送ble系统mic和dac(音乐或者通话远端)声音
Logger.info('发送ble系统mic和dac(音乐或者通话远端)声音');
}
// 开始连续语音识别 // 开始连续语音识别
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;

68
lib/modules/translation/views/translation_view.dart

@ -21,7 +21,20 @@ class TranslationView extends GetView<TranslationController> {
); );
} }
PreferredSizeWidget _buildAppBar(bool isDarkMode) { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
// 在同一个类中添加辅助方法
PopupMenuItem<String> _buildPopupMenuItem(String value, String text) {
return PopupMenuItem(
value: value,
child: Text(
text,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
),
),
);
}
return AppBar( return AppBar(
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
elevation: 1, elevation: 1,
@ -37,14 +50,37 @@ class TranslationView extends GetView<TranslationController> {
Get.back(); Get.back();
}, },
), ),
title: Text( title: Obx(() => PopupMenuButton<String>(
'simultaneousTranslation'.tr,// 实时翻译 onSelected: (value) {
style: TextStyle( controller.changeTranslationMode(value);
fontSize: 18.sp, },
fontWeight: FontWeight.w600, color: isDarkMode ? AppColors.cardBackground : Colors.white,
color: isDarkMode ? Colors.white : Colors.black, itemBuilder: (BuildContext context) => [
), _buildPopupMenuItem('simultaneous', 'simultaneousTranslation'.tr),
), _buildPopupMenuItem('audioVideo', 'audioVideoTranslation'.tr),
_buildPopupMenuItem('call', 'callTranslation'.tr),
],
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
controller.currentModeTitle.value.tr, // 添加 .value 访问实际值
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
Icon(
Icons.arrow_drop_down,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
],
),
),
)),
centerTitle: true, centerTitle: true,
actions: [ actions: [
// TTS toggle button // TTS toggle button
@ -93,7 +129,8 @@ class TranslationView extends GetView<TranslationController> {
child: GestureDetector( child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(true, isDarkMode), onTap: () => _showLanguageSelectionDialog(true, isDarkMode),
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
@ -101,8 +138,8 @@ class TranslationView extends GetView<TranslationController> {
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Obx(() => Text( child: Obx(() => Text(
'${controller.sourceLanguage.value '${controller.sourceLanguage.value.split(' ')[0]}'
.split(' ')[0]}'.tr, // Just show the Chinese part .tr, // Just show the Chinese part
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -140,7 +177,8 @@ class TranslationView extends GetView<TranslationController> {
child: GestureDetector( child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(false, isDarkMode), onTap: () => _showLanguageSelectionDialog(false, isDarkMode),
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), padding:
EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
@ -148,8 +186,8 @@ class TranslationView extends GetView<TranslationController> {
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Obx(() => Text( child: Obx(() => Text(
'${controller.targetLanguage.value '${controller.targetLanguage.value.split(' ')[0]}'
.split(' ')[0]}'.tr, // Just show the English part .tr, // Just show the English part
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

11
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -287,11 +287,12 @@ object AgentService : CoroutineScope {
if (isRecognitionActive) return true if (isRecognitionActive) return true
// 检查录音权限 // 检查录音权限
// if (!checkRecordAudioPermission()) {
// FileLogger.e(TAG, "需要录音权限,请在设置中授予权限") if (!isExternalActive||!checkRecordAudioPermission()) {
// sendEvent("error", mapOf("code" to "PERMISSION_DENIED", "message" to "需要录音权限")) FileLogger.e(TAG, "需要录音权限,请在设置中授予权限")
// return false sendEvent("error", mapOf("code" to "PERMISSION_DENIED", "message" to "需要录音权限"))
// } return false
}
isRecognitionActive = true isRecognitionActive = true
hasSpeechDetected = false hasSpeechDetected = false

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

@ -143,19 +143,6 @@ class AzureSpeechPlugin: FlutterPlugin, CoroutineScope {
result.error("INITIALIZATION_ERROR", e.message, null) result.error("INITIALIZATION_ERROR", e.message, null)
} }
} }
"pushAudioData" -> {
val audioData = call.argument<ByteArray>("audioData")
try {
if (audioData != null) {
azureAsrHelper.pushAudioData(audioData)
result.success(true)
} else {
result.error("INVALID_ARGUMENTS", "音频数据不能为空", null)
}
} catch (e: Exception) {
result.error("PUSH_AUDIO_ERROR", e.message, null)
}
}
"recognizeOnce" -> { "recognizeOnce" -> {
val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false val useExternalAudio = call.argument<Boolean>("useExternalAudio") ?: false
try { try {

4
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -82,9 +82,9 @@ object BleConst {
// ================== 编解码控制值 ================== // ================== 编解码控制值 ==================
/** 关闭编解码指令 */ /** 关闭编解码指令 */
const val CODEC_CONTROL_CLOSE = 0x00 const val CODEC_CONTROL_CLOSE = 0x00
/** 打开解码指令 */ /** 音乐或者通话远端声音 */
const val CODEC_CONTROL_DECODE_ON = 0xA1 const val CODEC_CONTROL_DECODE_ON = 0xA1
/** 通过A2DP播放 */ /** mic和dac(音乐或者通话远端)声音 */
const val CODEC_CONTROL_A2DP_PLAY = 0xA2 const val CODEC_CONTROL_A2DP_PLAY = 0xA2
/** 打开编码指令 */ /** 打开编码指令 */
const val CODEC_CONTROL_ENCODE_ON = 0xB1 const val CODEC_CONTROL_ENCODE_ON = 0xB1

4
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -907,7 +907,9 @@ object BleService {
FileLogger.i(TAG, "关闭编解码...") FileLogger.i(TAG, "关闭编解码...")
return sendCommand( return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte()) byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte())
) )
} }

Loading…
Cancel
Save