Browse Source

add

newdev_shunjiawei
wolfplus 1 year ago
parent
commit
6ff7dccce2
  1. 2
      android/build.gradle.kts
  2. 4
      lib/core/themes/theme_service.dart
  3. 85
      lib/core/utils/logger.dart
  4. 41
      lib/data/services/ble_manager.dart
  5. 8
      lib/data/services/bluetooth_manager.dart
  6. 2
      lib/data/services/chat_history_service.dart
  7. 6
      lib/data/services/deapsound_ai_service.dart
  8. 6
      lib/data/services/kouzi_ai_service.dart
  9. 4
      lib/data/services/speech_factory.dart
  10. 21
      lib/data/services/speech_impl/azure_asr_service.dart
  11. 22
      lib/data/services/speech_impl/azure_tts_service.dart
  12. 18
      lib/data/services/speech_impl/flutter_asr_service.dart
  13. 24
      lib/data/services/speech_impl/flutter_tts_service.dart
  14. 22
      lib/data/services/speech_impl/voice_clone_tts_service.dart
  15. 38
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  16. 16
      lib/data/services/speech_impl/volcano_tts_api_service.dart
  17. 20
      lib/data/services/speech_impl/xunfei_asr_service.dart
  18. 18
      lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart
  19. 12
      lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
  20. 54
      lib/modules/agent/controllers/agent_controller.dart
  21. 10
      lib/modules/chat/controllers/chat_controller.dart
  22. 8
      lib/modules/meeting/controllers/meeting_details_controller.dart
  23. 14
      lib/modules/pairing/controllers/pairing_controller.dart
  24. 18
      lib/modules/speech_test/controllers/speech_test_controller.dart
  25. 26
      lib/modules/translation/controllers/translation_controller.dart
  26. 8
      lib/modules/translation/controllers/translation_detail_controller.dart
  27. 473
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  28. 12
      local_plugins/agent_service/pubspec.yaml
  29. 3
      local_plugins/azure_speech/android/build.gradle.kts
  30. 224
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  31. 289
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
  32. 26
      local_plugins/bytedance_speech/android/build.gradle.kts
  33. 2
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceASR.kt
  34. 329
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt
  35. 154
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt
  36. 650
      local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt
  37. 2
      local_plugins/classic_bluetooth/pubspec.yaml
  38. BIN
      local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock
  39. BIN
      local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock
  40. 2
      local_plugins/opus/.gradle/buildOutputCleanup/cache.properties
  41. 2
      local_plugins/speech/android/build.gradle.kts
  42. 2
      local_plugins/speech/android/settings.gradle.kts
  43. 10
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt
  44. 12
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt
  45. 2
      pubspec.yaml

2
android/build.gradle.kts

@ -35,7 +35,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
// 其他依赖... // 其他依赖...
} }
} }

4
lib/core/themes/theme_service.dart

@ -21,7 +21,7 @@ class ThemeService {
Logger.info('从存储读取主题设置: $value'); Logger.info('从存储读取主题设置: $value');
return value ?? false; return value ?? false;
} catch (e) { } catch (e) {
Logger.error('读取主题设置失败: $e'); Logger.error('读取主题设置失败: ${e.toString()}');
return false; return false;
} }
} }
@ -32,7 +32,7 @@ class ThemeService {
Logger.info('保存主题设置: ${isDarkMode ? "暗黑" : "浅色"}'); Logger.info('保存主题设置: ${isDarkMode ? "暗黑" : "浅色"}');
await _box.write(_key, isDarkMode); await _box.write(_key, isDarkMode);
} catch (e) { } catch (e) {
Logger.error('保存主题设置失败: $e'); Logger.error('保存主题设置失败: ${e.toString()}');
} }
} }
} }

85
lib/core/utils/logger.dart

@ -15,77 +15,84 @@ class Logger {
/// 当前日志级别,低于此级别的日志不会被输出 /// 当前日志级别,低于此级别的日志不会被输出
static LogLevel _currentLevel = LogLevel.debug; static LogLevel _currentLevel = LogLevel.debug;
/// 默认标签
static const String _defaultTag = 'App';
/// 设置日志级别 /// 设置日志级别
static void setLevel(LogLevel level) { static void setLevel(LogLevel level) {
_currentLevel = level; _currentLevel = level;
} }
/// 输出调试日志 (不带标签版本)
static void debug(String message) {
if (_currentLevel.index <= LogLevel.debug.index) {
_log('DEBUG', _defaultTag, message);
}
}
/// 输出调试日志 /// 输出调试日志
static void debug(String message, {String? tag}) { static void d(String tag, String message) {
if (_currentLevel.index <= LogLevel.debug.index) { if (_currentLevel.index <= LogLevel.debug.index) {
_log('DEBUG', message, tag: tag); _log('DEBUG', tag, message);
}
}
/// 输出信息日志 (不带标签版本)
static void info(String message) {
if (_currentLevel.index <= LogLevel.info.index) {
_log('INFO', _defaultTag, message);
} }
} }
/// 输出信息日志 /// 输出信息日志
static void info(String message, {String? tag}) { static void i(String tag, String message) {
if (_currentLevel.index <= LogLevel.info.index) { if (_currentLevel.index <= LogLevel.info.index) {
_log('INFO', message, tag: tag); _log('INFO', tag, message);
}
}
/// 输出警告日志 (不带标签版本)
static void warning(String message) {
if (_currentLevel.index <= LogLevel.warning.index) {
_log('WARNING', _defaultTag, message);
} }
} }
/// 输出警告日志 /// 输出警告日志
static void warning(String message, {String? tag}) { static void w(String tag, String message) {
if (_currentLevel.index <= LogLevel.warning.index) { if (_currentLevel.index <= LogLevel.warning.index) {
_log('WARNING', message, tag: tag); _log('WARNING', tag, message);
} }
} }
/// 输出错误日志 /// 输出错误日志 (不带标签版本)
static void error(String message, [dynamic error, StackTrace? stackTrace, String? tag]) { static void error(String message, [dynamic error, StackTrace? stackTrace]) {
if (_currentLevel.index <= LogLevel.error.index) { if (_currentLevel.index <= LogLevel.error.index) {
_log('ERROR', message, tag: tag); _log('ERROR', _defaultTag, message);
if (error != null) { if (error != null) {
_log('ERROR', 'Original error: $error', tag: tag); _log('ERROR', _defaultTag, 'Original error: $error');
} }
if (stackTrace != null) { if (stackTrace != null) {
_log('ERROR', 'Stack trace: $stackTrace', tag: tag); _log('ERROR', _defaultTag, 'Stack trace: $stackTrace');
} }
} }
} }
/// 获取当前调用栈的类名 /// 输出错误日志
static String? _getCallerClassName() { static void e(String tag, String message, [dynamic error, StackTrace? stackTrace]) {
try { if (_currentLevel.index <= LogLevel.error.index) {
final stackTrace = StackTrace.current.toString(); _log('ERROR', tag, message);
final frames = stackTrace.split('\n'); if (error != null) {
_log('ERROR', tag, 'Original error: $error');
// 查找调用者帧 (跳过Logger框架内部的帧) }
for (int i = 0; i < frames.length; i++) { if (stackTrace != null) {
final frame = frames[i].trim(); _log('ERROR', tag, 'Stack trace: $stackTrace');
if (frame.contains('Logger.') || frame.contains('_log')) {
continue;
}
// 简单的解析类名
final regex = RegExp(r'#\d+\s+([^\.]+)\.([^\.]+)\s');
final match = regex.firstMatch(frame);
if (match != null && match.groupCount >= 1) {
return match.group(1);
}
break;
} }
} catch (e) {
// 忽略解析错误
} }
return null;
} }
/// 内部日志输出方法 /// 内部日志输出方法
static void _log(String level, String message, {String? tag}) { static void _log(String level, String tag, String message) {
final timestamp = DateTime.now().toString(); developer.log('[$tag]: $message');
final className = tag ?? _getCallerClassName() ?? '';
final tagInfo = className.isNotEmpty ? '[$className] ' : '';
developer.log('[$timestamp][$level][$tagInfo]: $message');
} }
} }

41
lib/data/services/ble_manager.dart

@ -198,7 +198,7 @@ class BleManager extends GetxService {
}, },
); );
} catch (e) { } catch (e) {
Logger.error('设置BLE事件监听器失败: $e'); Logger.error('设置BLE事件监听器失败: ${e.toString()}');
} }
} }
@ -225,7 +225,7 @@ class BleManager extends GetxService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('处理设备信息异常: $e'); Logger.error('处理设备信息异常: ${e.toString()}');
} }
} }
@ -303,10 +303,9 @@ class BleManager extends GetxService {
caseCharging: caseCharging, caseCharging: caseCharging,
); );
Logger.info( Logger.info('设备电池信息已更新: 电量=$overallBattery%, 左耳=$leftBattery%, 右耳=$rightBattery%, 充电仓=$caseBattery%');
'设备电池信息已更新: 电量=$overallBattery%, 左耳=$leftBattery%, 右耳=$rightBattery%, 充电仓=$caseBattery%');
} catch (e) { } catch (e) {
Logger.error('更新电池信息失败: $e'); Logger.error('更新电池信息失败: ${e.toString()}');
} }
} }
@ -329,7 +328,7 @@ class BleManager extends GetxService {
Logger.info('设备版本信息已更新: 版本=$version'); Logger.info('设备版本信息已更新: 版本=$version');
} catch (e) { } catch (e) {
Logger.error('更新版本信息失败: $e'); Logger.error('更新版本信息失败: ${e.toString()}');
} }
} }
@ -352,7 +351,7 @@ class BleManager extends GetxService {
Logger.info('设备设备id已更新: 设备id=$productId'); Logger.info('设备设备id已更新: 设备id=$productId');
} catch (e) { } catch (e) {
Logger.error('更新设备id失败: $e'); Logger.error('更新设备id失败: ${e.toString()}');
} }
} }
@ -440,7 +439,7 @@ class BleManager extends GetxService {
Logger.warning('产品id获取指令发送失败'); Logger.warning('产品id获取指令发送失败');
} }
} catch (e) { } catch (e) {
Logger.error('获取BLE设备信息失败: $e'); Logger.error('获取BLE设备信息失败: ${e.toString()}');
} }
} }
@ -455,7 +454,7 @@ class BleManager extends GetxService {
return _connectionState.value; return _connectionState.value;
} catch (e) { } catch (e) {
Logger.error('获取BLE连接状态失败: $e'); Logger.error('获取BLE连接状态失败: ${e.toString()}');
return BleConnectionState.error; return BleConnectionState.error;
} }
} }
@ -542,7 +541,7 @@ class BleManager extends GetxService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('启动BLE服务失败: $e'); Logger.error('启动BLE服务失败: ${e.toString()}');
return false; return false;
} }
} }
@ -562,7 +561,7 @@ class BleManager extends GetxService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('停止BLE服务失败: $e'); Logger.error('停止BLE服务失败: ${e.toString()}');
return false; return false;
} }
} }
@ -572,7 +571,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.getBatteryLevel(); return await _bleService.getBatteryLevel();
} catch (e) { } catch (e) {
Logger.error('获取电池电量失败: $e'); Logger.error('获取电池电量失败: ${e.toString()}');
return 0; return 0;
} }
} }
@ -582,7 +581,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.getBatteryInfo(); return await _bleService.getBatteryInfo();
} catch (e) { } catch (e) {
Logger.error('获取电池信息失败: $e'); Logger.error('获取电池信息失败: ${e.toString()}');
return false; return false;
} }
} }
@ -592,7 +591,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.getProductId(); return await _bleService.getProductId();
} catch (e) { } catch (e) {
Logger.error('获取设备版本失败: $e'); Logger.error('获取设备版本失败: ${e.toString()}');
return false; return false;
} }
} }
@ -602,7 +601,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.getDeviceVersion(); return await _bleService.getDeviceVersion();
} catch (e) { } catch (e) {
Logger.error('获取设备版本失败: $e'); Logger.error('获取设备版本失败: ${e.toString()}');
return false; return false;
} }
} }
@ -612,7 +611,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.openEncoder(); return await _bleService.openEncoder();
} catch (e) { } catch (e) {
Logger.error('打开编码器失败: $e'); Logger.error('打开编码器失败: ${e.toString()}');
return false; return false;
} }
} }
@ -622,7 +621,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.openDecoder(); return await _bleService.openDecoder();
} catch (e) { } catch (e) {
Logger.error('打开解码器失败: $e'); Logger.error('打开解码器失败: ${e.toString()}');
return false; return false;
} }
} }
@ -632,7 +631,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.openA2DPDecoder(); return await _bleService.openA2DPDecoder();
} catch (e) { } catch (e) {
Logger.error('打开解码器失败: $e'); Logger.error('打开解码器失败: ${e.toString()}');
return false; return false;
} }
} }
@ -642,7 +641,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.closeCodec(); return await _bleService.closeCodec();
} catch (e) { } catch (e) {
Logger.error('关闭编解码器失败: $e'); Logger.error('关闭编解码器失败: ${e.toString()}');
return false; return false;
} }
} }
@ -652,7 +651,7 @@ class BleManager extends GetxService {
try { try {
return await _bleService.playA2DP(); return await _bleService.playA2DP();
} catch (e) { } catch (e) {
Logger.error('播放A2DP失败: $e'); Logger.error('播放A2DP失败: ${e.toString()}');
return false; return false;
} }
} }
@ -664,4 +663,4 @@ class BleManager extends GetxService {
} }
return Future.value(Get.find<BleManager>()); return Future.value(Get.find<BleManager>());
} }
} }

8
lib/data/services/bluetooth_manager.dart

@ -75,11 +75,11 @@ class BluetoothManager extends GetxService {
} }
}, },
onError: (error) { onError: (error) {
Logger.error('蓝牙事件流错误: $error'); Logger.error('蓝牙事件流错误: ${error.toString()}');
}, },
); );
} catch (e) { } catch (e) {
Logger.error('设置蓝牙事件监听器失败: $e'); Logger.error('设置蓝牙事件监听器失败: ${e.toString()}');
} }
} }
@ -106,7 +106,7 @@ class BluetoothManager extends GetxService {
Logger.info('未找到已连接的蓝牙设备'); Logger.info('未找到已连接的蓝牙设备');
} catch (e) { } catch (e) {
Logger.error('刷新设备连接失败: $e'); Logger.error('刷新设备连接失败: ${e.toString()}');
} }
} }
@ -117,7 +117,7 @@ class BluetoothManager extends GetxService {
_bluetoothState.value = isEnabled ? 'on' : 'off'; _bluetoothState.value = isEnabled ? 'on' : 'off';
return isEnabled; return isEnabled;
} catch (e) { } catch (e) {
Logger.error('检查蓝牙状态失败: $e'); Logger.error('检查蓝牙状态失败: ${e.toString()}');
return false; return false;
} }
} }

2
lib/data/services/chat_history_service.dart

@ -39,7 +39,7 @@ class ChatHistoryService extends GetxService {
// 检查并清理过多的历史记录 // 检查并清理过多的历史记录
await _cleanupOldMessages(agentId); await _cleanupOldMessages(agentId);
} catch (e) { } catch (e) {
Logger.error('保存聊天记录失败', e); Logger.error('保存聊天记录失败', e.toString());
} }
} }

6
lib/data/services/deapsound_ai_service.dart

@ -82,14 +82,14 @@ class DeapsoundAIService implements AiService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('JSON解析错误: $e'); Logger.error('JSON解析错误: ${e.toString()}');
} }
} }
} }
} catch (e) { } catch (e) {
Logger.error('请求异常: $e'); Logger.error('请求异常: ${e.toString()}');
} finally { } finally {
client.close(); client.close();
} }
} }
} }

6
lib/data/services/kouzi_ai_service.dart

@ -82,14 +82,14 @@ class KouziAIService implements AiService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('JSON解析错误: $e'); Logger.error('JSON解析错误: ${e.toString()}');
} }
} }
} }
} catch (e) { } catch (e) {
Logger.error('请求异常: $e'); Logger.error('请求异常: ${e.toString()}');
} finally { } finally {
client.close(); client.close();
} }
} }
} }

4
lib/data/services/speech_factory.dart

@ -88,7 +88,7 @@ class SpeechFactory extends GetxService {
Logger.info( Logger.info(
'语音服务初始化完成,当前类型: ${_currentServiceType.toString().split('.').last}'); '语音服务初始化完成,当前类型: ${_currentServiceType.toString().split('.').last}');
} catch (e) { } catch (e) {
Logger.error('语音服务初始化失败: $e'); Logger.error('语音服务初始化失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -108,4 +108,4 @@ class SpeechFactory extends GetxService {
} }
return Get.find<TtsService>(); return Get.find<TtsService>();
} }
} }

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

@ -106,7 +106,7 @@ class AzureAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}'); Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}');
return result; return result;
} catch (e) { } catch (e) {
Logger.error('Azure 语音识别服务初始化失败: $e'); Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false; _isInitialized = false;
rethrow; rethrow;
} }
@ -144,7 +144,7 @@ class AzureAsrService extends GetxService implements AsrService {
throw Exception('无效的识别结果格式'); throw Exception('无效的识别结果格式');
} catch (e) { } catch (e) {
Logger.error('语音识别失败: $e'); Logger.error('语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString()); return RecognitionEvent.error(e.toString());
} }
} }
@ -177,7 +177,7 @@ class AzureAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
return _eventStreamController!.stream; return _eventStreamController!.stream;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: $e'); Logger.error('开始连续语音识别失败: ${e.toString()}');
_cleanupEventStream(); _cleanupEventStream();
rethrow; rethrow;
} }
@ -195,7 +195,7 @@ class AzureAsrService extends GetxService implements AsrService {
_cleanupEventStream(); _cleanupEventStream();
return result; return result;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: ${e.toString()}');
_cleanupEventStream(); _cleanupEventStream();
rethrow; rethrow;
} }
@ -214,7 +214,6 @@ class AzureAsrService extends GetxService implements AsrService {
final String eventType = eventMap['type'] as String? ?? ''; final String eventType = eventMap['type'] as String? ?? '';
// 添加日志帮助调试 // 添加日志帮助调试
Logger.debug('收到语音识别事件: $eventType, 数据: $eventMap');
switch (eventType) { switch (eventType) {
case 'result': case 'result':
@ -260,7 +259,7 @@ class AzureAsrService extends GetxService implements AsrService {
final String errorDetails = eventMap['errorDetails'] as String? ?? ''; final String errorDetails = eventMap['errorDetails'] as String? ?? '';
if (reason.isNotEmpty || errorDetails.isNotEmpty) { if (reason.isNotEmpty || errorDetails.isNotEmpty) {
Logger.error('识别取消: $reason - $errorDetails'); Logger.error('识别取消: $reason - ${errorDetails.toString()}');
} }
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
@ -271,7 +270,7 @@ class AzureAsrService extends GetxService implements AsrService {
case 'error': case 'error':
final String error = eventMap['message'] as String? ?? ''; final String error = eventMap['message'] as String? ?? '';
Logger.error('识别错误: $error'); Logger.error('识别错误: ${error.toString()}');
_eventStreamController?.add(RecognitionEvent( _eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.error, type: RecognitionEventType.error,
error: error, error: error,
@ -282,7 +281,7 @@ class AzureAsrService extends GetxService implements AsrService {
/// 处理识别事件流错误 /// 处理识别事件流错误
void _handleRecognitionError(Object error) { void _handleRecognitionError(Object error) {
Logger.error('识别事件流错误: $error'); Logger.error('识别事件流错误: ${error.toString()}');
_eventStreamController?.addError(error); _eventStreamController?.addError(error);
_cleanupEventStream(); _cleanupEventStream();
} }
@ -311,7 +310,7 @@ class AzureAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别资源已释放'); Logger.info('Azure 语音识别资源已释放');
} catch (e) { } catch (e) {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: ${e.toString()}');
_cleanupEventStream(); _cleanupEventStream();
_isInitialized = false; _isInitialized = false;
} }
@ -339,8 +338,8 @@ class AzureAsrService extends GetxService implements AsrService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('推送音频数据失败: $e'); Logger.error('推送音频数据失败: ${e.toString()}');
return false; return false;
} }
} }
} }

22
lib/data/services/speech_impl/azure_tts_service.dart

@ -69,7 +69,7 @@ class AzureTtsService extends GetxService implements TtsService {
_handleTtsEvent(event); _handleTtsEvent(event);
} }
}, onError: (error) { }, onError: (error) {
Logger.error('TTS事件流错误: $error'); Logger.error('TTS事件流错误: ${error.toString()}');
_eventStreamController.addError(error); _eventStreamController.addError(error);
}); });
} }
@ -78,7 +78,7 @@ class AzureTtsService extends GetxService implements TtsService {
void _handleTtsEvent(Map<dynamic, dynamic> event) { void _handleTtsEvent(Map<dynamic, dynamic> event) {
final String eventType = event['type'] as String? ?? ''; final String eventType = event['type'] as String? ?? '';
// Logger.debug('收到TTS事件: $eventType, 数据: $event'); // Logger.debug('收到TTS事件: ${e.toString()}ventType, 数据: ${e.toString()}vent');
switch (eventType) { switch (eventType) {
case 'synthesis_started': case 'synthesis_started':
@ -159,7 +159,7 @@ class AzureTtsService extends GetxService implements TtsService {
_isInitialized = result; _isInitialized = result;
return result; return result;
} catch (e) { } catch (e) {
Logger.error('初始化失败: $e'); Logger.error('初始化失败: ${e.toString()}');
return false; return false;
} }
} }
@ -177,7 +177,7 @@ class AzureTtsService extends GetxService implements TtsService {
if (result) _currentVoice = voiceName; if (result) _currentVoice = voiceName;
return result; return result;
} catch (e) { } catch (e) {
Logger.error('设置语音失败: $e'); Logger.error('设置语音失败: ${e.toString()}');
return false; return false;
} }
} }
@ -195,7 +195,7 @@ class AzureTtsService extends GetxService implements TtsService {
return result == true; return result == true;
} catch (e) { } catch (e) {
Logger.error('语音合成失败: $e'); Logger.error('语音合成失败: ${e.toString()}');
return false; return false;
} }
} }
@ -250,7 +250,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('处理流式文本失败: $e'); Logger.error('处理流式文本失败: ${e.toString()}');
return false; return false;
} }
} }
@ -316,7 +316,7 @@ class AzureTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成'); Logger.info('TTS队列播放完成');
return result; return result;
} catch (e) { } catch (e) {
Logger.error('刷新TTS流失败: $e'); Logger.error('刷新TTS流失败: ${e.toString()}');
return false; return false;
} }
} }
@ -358,7 +358,7 @@ class AzureTtsService extends GetxService implements TtsService {
_textQueue.removeAt(0); _textQueue.removeAt(0);
} }
} catch (e) { } catch (e) {
Logger.error('处理语音队列出错: $e'); Logger.error('处理语音队列出错: ${e.toString()}');
// 出错时移除当前项并尝试继续 // 出错时移除当前项并尝试继续
if (_textQueue.isNotEmpty) { if (_textQueue.isNotEmpty) {
@ -389,7 +389,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止语音合成出错: $e'); Logger.error('停止语音合成出错: ${e.toString()}');
return false; return false;
} }
} }
@ -410,7 +410,7 @@ class AzureTtsService extends GetxService implements TtsService {
await _channel.invokeMethod('dispose'); await _channel.invokeMethod('dispose');
_isInitialized = false; _isInitialized = false;
} catch (e) { } catch (e) {
Logger.error('释放资源失败: $e'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
} }

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

@ -107,7 +107,7 @@ class FlutterAsrService extends GetxService implements AsrService {
} catch (e) { } catch (e) {
_isAvailable.value = false; _isAvailable.value = false;
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
Logger.error('语音识别初始化失败: $e'); Logger.error('语音识别初始化失败: ${e.toString()}');
} }
} }
@ -203,7 +203,7 @@ class FlutterAsrService extends GetxService implements AsrService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('启动语音识别失败: $e'); Logger.error('启动语音识别失败: ${e.toString()}');
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
return false; return false;
} }
@ -253,7 +253,7 @@ class FlutterAsrService extends GetxService implements AsrService {
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突')); ?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
} }
} catch (e) { } catch (e) {
Logger.error('重启连续识别失败: $e'); Logger.error('重启连续识别失败: ${e.toString()}');
// 发送错误给控制器 // 发送错误给控制器
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e')); _continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
@ -304,7 +304,7 @@ class FlutterAsrService extends GetxService implements AsrService {
await _initSpeech(); await _initSpeech();
return _isAvailable.value; return _isAvailable.value;
} catch (e) { } catch (e) {
Logger.error('语音识别初始化失败: $e'); Logger.error('语音识别初始化失败: ${e.toString()}');
return false; return false;
} }
} }
@ -357,7 +357,7 @@ class FlutterAsrService extends GetxService implements AsrService {
}, },
); );
} catch (e) { } catch (e) {
Logger.error('一次性语音识别失败: $e'); Logger.error('一次性语音识别失败: ${e.toString()}');
return RecognitionEvent.error('$e'); return RecognitionEvent.error('$e');
} }
} }
@ -406,7 +406,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = true; _isListening = true;
return _continuousRecognitionController!.stream; return _continuousRecognitionController!.stream;
} catch (e) { } catch (e) {
Logger.error('启动连续语音识别失败: $e'); Logger.error('启动连续语音识别失败: ${e.toString()}');
// 如果已经创建了控制器,使用它 // 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) { if (_continuousRecognitionController != null) {
@ -442,7 +442,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_asrState.value = AsrState.done; _asrState.value = AsrState.done;
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: ${e.toString()}');
// 强制重置状态 // 强制重置状态
_isListening = false; _isListening = false;
@ -472,7 +472,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = false; _isListening = false;
_asrState.value = AsrState.notInitialized; _asrState.value = AsrState.notInitialized;
} catch (e) { } catch (e) {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: ${e.toString()}');
} }
} }
} }

24
lib/data/services/speech_impl/flutter_tts_service.dart

@ -96,7 +96,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('FlutterTtsService 已初始化'); Logger.info('FlutterTtsService 已初始化');
} catch (e) { } catch (e) {
Logger.error('TTS初始化失败: $e'); Logger.error('TTS初始化失败: ${e.toString()}');
} }
} }
@ -118,7 +118,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('支持的语音: ${_supportedVoices.join(", ")}'); Logger.info('支持的语音: ${_supportedVoices.join(", ")}');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('TTS初始化失败: $e'); Logger.error('TTS初始化失败: ${e.toString()}');
return false; return false;
} }
} }
@ -149,7 +149,7 @@ class FlutterTtsService extends GetxService implements TtsService {
_currentVoice.value = _supportedVoices.first; _currentVoice.value = _supportedVoices.first;
} }
} catch (e) { } catch (e) {
Logger.error('加载TTS语音失败: $e'); Logger.error('加载TTS语音失败: ${e.toString()}');
// 添加默认语音 // 添加默认语音
if (_supportedVoices.isEmpty) { if (_supportedVoices.isEmpty) {
@ -175,7 +175,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName'); Logger.info('设置TTS语音: $voiceName');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('设置TTS语音失败: $e'); Logger.error('设置TTS语音失败: ${e.toString()}');
return false; return false;
} }
} }
@ -200,7 +200,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('设置语音参数失败: $e'); Logger.error('设置语音参数失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('设置语音参数失败: $e')); _eventController.add(TtsEvent.error('设置语音参数失败: $e'));
@ -227,7 +227,7 @@ class FlutterTtsService extends GetxService implements TtsService {
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error(e.toString())); _eventController.add(TtsEvent.error(e.toString()));
Logger.error('TTS播放失败: $e'); Logger.error('TTS播放失败: ${e.toString()}');
return false; return false;
} }
} }
@ -288,7 +288,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('处理流式文本失败: $e'); Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error(e.toString())); _eventController.add(TtsEvent.error(e.toString()));
@ -333,7 +333,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成'); Logger.info('TTS队列播放完成');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('等待TTS队列播放完成失败: $e'); Logger.error('等待TTS队列播放完成失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error(e.toString())); _eventController.add(TtsEvent.error(e.toString()));
@ -382,7 +382,7 @@ class FlutterTtsService extends GetxService implements TtsService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('TTS处理队列失败: $e'); Logger.error('TTS处理队列失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error(e.toString())); _eventController.add(TtsEvent.error(e.toString()));
@ -422,7 +422,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS停止播放完成'); Logger.info('TTS停止播放完成');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('TTS停止播放失败: $e'); Logger.error('TTS停止播放失败: ${e.toString()}');
// 即使出错也尝试强制重置状态 // 即使出错也尝试强制重置状态
_isSpeaking.value = false; _isSpeaking.value = false;
@ -443,7 +443,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS资源已释放'); Logger.info('TTS资源已释放');
} catch (e) { } catch (e) {
Logger.error('释放TTS资源失败: $e'); Logger.error('释放TTS资源失败: ${e.toString()}');
} }
} }
@ -462,7 +462,7 @@ class FlutterTtsService extends GetxService implements TtsService {
await flushStream(); await flushStream();
return true; return true;
} catch (e) { } catch (e) {
Logger.error('处理文本流时出错: $e'); Logger.error('处理文本流时出错: ${e.toString()}');
return false; return false;
} }
} }

22
lib/data/services/speech_impl/voice_clone_tts_service.dart

@ -76,7 +76,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
); );
return true; return true;
} catch (e) { } catch (e) {
Logger.error('TTS初始化失败: $e'); Logger.error('TTS初始化失败: ${e.toString()}');
return false; return false;
} }
} }
@ -88,7 +88,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName'); Logger.info('设置TTS语音: $voiceName');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('设置TTS语音失败: $e'); Logger.error('设置TTS语音失败: ${e.toString()}');
return false; return false;
} }
} }
@ -127,7 +127,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
_handle(message); _handle(message);
}, },
onError: (error) { onError: (error) {
Logger.error('连接失败: $error'); Logger.error('连接失败: ${error.toString()}');
}, },
onDone: () { onDone: () {
Logger.error('WebSocket 连接已关闭!'); Logger.error('WebSocket 连接已关闭!');
@ -135,7 +135,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
cancelOnError: true, cancelOnError: true,
); );
} catch (e) { } catch (e) {
Logger.error('初始化异常: $e'); Logger.error('初始化异常: ${e.toString()}');
} }
} }
@ -226,7 +226,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
_isSpeaking.value = false; _isSpeaking.value = false;
Logger.error('语音合成失败: $e'); Logger.error('语音合成失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('语音合成失败: $e')); _eventController.add(TtsEvent.error('语音合成失败: $e'));
@ -285,7 +285,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('处理流式文本失败: $e'); Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('处理流式文本失败: $e')); _eventController.add(TtsEvent.error('处理流式文本失败: $e'));
@ -319,7 +319,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('刷新TTS流失败: $e'); Logger.error('刷新TTS流失败: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('刷新TTS流失败: $e')); _eventController.add(TtsEvent.error('刷新TTS流失败: $e'));
@ -347,7 +347,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await speakOnce(item); await speakOnce(item);
} }
} catch (e) { } catch (e) {
Logger.error('处理语音队列出错: $e'); Logger.error('处理语音队列出错: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('处理语音队列出错: $e')); _eventController.add(TtsEvent.error('处理语音队列出错: $e'));
@ -370,7 +370,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
} }
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止语音合成出错: $e'); Logger.error('停止语音合成出错: ${e.toString()}');
// 发送错误事件 // 发送错误事件
_eventController.add(TtsEvent.error('停止语音合成出错: $e')); _eventController.add(TtsEvent.error('停止语音合成出错: $e'));
return false; return false;
@ -387,7 +387,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await _eventController.close(); await _eventController.close();
_isInitialized = false; _isInitialized = false;
} catch (e) { } catch (e) {
Logger.error('释放资源失败: $e'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
} }

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

@ -99,7 +99,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('火山语音API已初始化'); Logger.info('火山语音API已初始化');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('初始化火山语音API失败: $e'); Logger.error('初始化火山语音API失败: ${e.toString()}');
return false; return false;
} }
} }
@ -151,7 +151,7 @@ class VolcanoAsrApiService implements AsrService {
return resultCompleter.future; return resultCompleter.future;
} catch (e) { } catch (e) {
Logger.error('单次语音识别失败: $e'); Logger.error('单次语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString()); return RecognitionEvent.error(e.toString());
} }
} }
@ -184,7 +184,7 @@ class VolcanoAsrApiService implements AsrService {
return _recognitionController!.stream; return _recognitionController!.stream;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: $e'); Logger.error('开始连续语音识别失败: ${e.toString()}');
await _cleanupResources(); await _cleanupResources();
// 返回一个包含错误的流 // 返回一个包含错误的流
@ -222,7 +222,7 @@ class VolcanoAsrApiService implements AsrService {
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: ${e.toString()}');
// 确保资源被清理 // 确保资源被清理
await _cleanupResources(); await _cleanupResources();
@ -250,7 +250,7 @@ class VolcanoAsrApiService implements AsrService {
_isInitialized = false; _isInitialized = false;
Logger.info('火山语音API资源已释放'); Logger.info('火山语音API资源已释放');
} catch (e) { } catch (e) {
Logger.error('释放火山语音API资源失败: $e'); Logger.error('释放火山语音API资源失败: ${e.toString()}');
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
_isInitialized = false; _isInitialized = false;
} }
@ -284,7 +284,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音机初始化成功'); Logger.info('录音机初始化成功');
} catch (e) { } catch (e) {
// _recorder = null; // _recorder = null;
Logger.error('初始化录音机失败: $e'); Logger.error('初始化录音机失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -344,7 +344,7 @@ class VolcanoAsrApiService implements AsrService {
_connectionCompleter!.completeError(e); _connectionCompleter!.completeError(e);
} }
Logger.error('连接WebSocket失败: $e'); Logger.error('连接WebSocket失败: ${e.toString()}');
rethrow; rethrow;
} }
@ -432,7 +432,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(heartbeatMsg)); _channel!.sink.add(jsonEncode(heartbeatMsg));
} catch (e) { } catch (e) {
Logger.error('发送心跳包失败: $e'); Logger.error('发送心跳包失败: ${e.toString()}');
} }
} }
} }
@ -461,7 +461,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(startCommand)); _channel!.sink.add(jsonEncode(startCommand));
Logger.info('已发送开始识别指令'); Logger.info('已发送开始识别指令');
} catch (e) { } catch (e) {
Logger.error('发送开始识别指令失败: $e'); Logger.error('发送开始识别指令失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -496,7 +496,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音已开始'); Logger.info('录音已开始');
} catch (e) { } catch (e) {
_isRecording = false; _isRecording = false;
Logger.error('开始录音失败: $e'); Logger.error('开始录音失败: ${e.toString()}');
rethrow; rethrow;
} }
*/ */
@ -544,7 +544,7 @@ class VolcanoAsrApiService implements AsrService {
await _sendAudioData(audioData); await _sendAudioData(audioData);
} }
} catch (e) { } catch (e) {
Logger.error('捕获和发送音频失败: $e'); Logger.error('捕获和发送音频失败: ${e.toString()}');
} }
*/ */
} }
@ -562,7 +562,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(audioMessage)); _channel!.sink.add(jsonEncode(audioMessage));
} catch (e) { } catch (e) {
Logger.error('发送音频数据失败: $e'); Logger.error('发送音频数据失败: ${e.toString()}');
} }
} }
@ -579,7 +579,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(endCommand)); _channel!.sink.add(jsonEncode(endCommand));
Logger.info('已发送结束指令'); Logger.info('已发送结束指令');
} catch (e) { } catch (e) {
Logger.error('发送结束指令失败: $e'); Logger.error('发送结束指令失败: ${e.toString()}');
} }
} }
@ -596,7 +596,7 @@ class VolcanoAsrApiService implements AsrService {
_isRecording = false; _isRecording = false;
Logger.info('录音已停止'); Logger.info('录音已停止');
} catch (e) { } catch (e) {
Logger.error('停止录音失败: $e'); Logger.error('停止录音失败: ${e.toString()}');
} }
} }
*/ */
@ -641,7 +641,7 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('处理WebSocket消息失败: $e'); Logger.error('处理WebSocket消息失败: ${e.toString()}');
if (_recognitionController != null && !_recognitionController!.isClosed) { if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError('处理消息失败: $e'); _recognitionController!.addError('处理消息失败: $e');
@ -675,7 +675,7 @@ class VolcanoAsrApiService implements AsrService {
} }
} }
} catch (e) { } catch (e) {
Logger.error('处理结果消息失败: $e'); Logger.error('处理结果消息失败: ${e.toString()}');
} }
} }
@ -741,7 +741,7 @@ class VolcanoAsrApiService implements AsrService {
/// 处理WebSocket错误 /// 处理WebSocket错误
void _handleWebSocketError(dynamic error) { void _handleWebSocketError(dynamic error) {
Logger.error('WebSocket错误: $error'); Logger.error('WebSocket错误: ${e.toString()}rror');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) { if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(error); _connectionCompleter!.completeError(error);
@ -789,7 +789,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.close(); _channel!.sink.close();
_channel = null; _channel = null;
} catch (e) { } catch (e) {
Logger.error('关闭WebSocket连接失败: $e'); Logger.error('关闭WebSocket连接失败: ${e.toString()}');
} }
} }
} }
@ -811,4 +811,4 @@ class VolcanoAsrApiService implements AsrService {
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false; _isRecognitionEndRequested = false;
} }
} }

16
lib/data/services/speech_impl/volcano_tts_api_service.dart

@ -357,7 +357,7 @@ class VolcanoTtsApiService extends GetxController {
_handleMessage, _handleMessage,
onError: (error) { onError: (error) {
// 直接处理错误,不尝试重连 // 直接处理错误,不尝试重连
Logger.error('WebSocket错误: $error'); Logger.error('WebSocket错误: ${error.toString()}');
if (!_eventController.isClosed) { if (!_eventController.isClosed) {
_eventController.add({ _eventController.add({
'eventType': 'error', 'eventType': 'error',
@ -391,7 +391,7 @@ class VolcanoTtsApiService extends GetxController {
}); });
} }
} catch (e) { } catch (e) {
Logger.error('处理TTS连接超时事件失败: $e'); Logger.error('处理TTS连接超时事件失败: ${e.toString()}');
} }
} }
timeout = null; timeout = null;
@ -427,7 +427,7 @@ class VolcanoTtsApiService extends GetxController {
return result; return result;
} catch (e) { } catch (e) {
// 只记录错误,不尝试重连 // 只记录错误,不尝试重连
Logger.error('连接过程中发生异常: $e'); Logger.error('连接过程中发生异常: ${e.toString()}');
return false; return false;
} finally { } finally {
isConnecting.value = false; isConnecting.value = false;
@ -479,7 +479,7 @@ class VolcanoTtsApiService extends GetxController {
'error': '会话开始超时' 'error': '会话开始超时'
}); });
} catch (e) { } catch (e) {
Logger.error('处理TTS会话超时事件失败: $e'); Logger.error('处理TTS会话超时事件失败: ${e.toString()}');
} }
} }
timeout = null; timeout = null;
@ -515,7 +515,7 @@ class VolcanoTtsApiService extends GetxController {
return result; return result;
} catch (e) { } catch (e) {
// 只记录错误,不尝试恢复 // 只记录错误,不尝试恢复
Logger.error('开始TTS会话失败: $e'); Logger.error('开始TTS会话失败: ${e.toString()}');
return false; return false;
} }
} }
@ -902,10 +902,10 @@ class VolcanoTtsApiService extends GetxController {
'error': error.toString() 'error': error.toString()
}); });
Logger.error('火山语音TTS错误: $error'); Logger.error('火山语音TTS错误: ${error.toString()}');
} catch (e) { } catch (e) {
// 如果在处理错误时发生异常,记录到日志但不再抛出异常 // 如果在处理错误时发生异常,记录到日志但不再抛出异常
Logger.error('处理TTS错误时发生异常: $e,原始错误: $error'); Logger.error('处理TTS错误时发生异常: ${e.toString()},原始错误: ${error.toString()}');
} }
} }
@ -1445,7 +1445,7 @@ class VolcanoTtsApiService extends GetxController {
return success; return success;
} catch (e) { } catch (e) {
Logger.error('TTS一次性合成失败: $e'); Logger.error('TTS一次性合成失败: ${e.toString()}');
// 确保会话结束 // 确保会话结束
endSession(); endSession();
return false; return false;

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

@ -54,7 +54,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别服务初始化成功'); Logger.info('Azure 语音识别服务初始化成功');
return true; return true;
} catch (e) { } catch (e) {
Logger.error('Azure 语音识别服务初始化失败: $e'); Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false; _isInitialized = false;
rethrow; rethrow;
} }
@ -69,7 +69,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
try { try {
throw Exception('无效的识别结果格式'); throw Exception('无效的识别结果格式');
} catch (e) { } catch (e) {
Logger.error('语音识别失败: $e'); Logger.error('语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString()); return RecognitionEvent.error(e.toString());
} }
} }
@ -112,7 +112,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
return _eventStreamController!.stream; return _eventStreamController!.stream;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: $e'); Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -152,7 +152,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}, },
onError: (error) { onError: (error) {
isConnected = false; isConnected = false;
Logger.error('连接失败: $error'); Logger.error('连接失败: ${error.toString()}');
}, },
onDone: () { onDone: () {
isConnected = false; isConnected = false;
@ -161,7 +161,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
cancelOnError: true, cancelOnError: true,
); );
} catch (e) { } catch (e) {
Logger.error("初始化异常----------------$e"); Logger.error("初始化异常----------------${e.toString()}");
} }
} }
@ -185,7 +185,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
} }
} }
} }
Logger.info( Logger.info(
"识别到的内容 type:${data['cn']['st']['type']} msg:$msg rl:$role"); "识别到的内容 type:${data['cn']['st']['type']} msg:$msg rl:$role");
if (msg.isNotEmpty) { if (msg.isNotEmpty) {
if (data['cn']['st']['type'] == "0") { if (data['cn']['st']['type'] == "0") {
@ -206,7 +206,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
} }
} }
} catch (e) { } catch (e) {
Logger.error("接受消息异常 $e"); Logger.error("接受消息异常 ${e.toString()}");
} }
} }
@ -242,7 +242,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = false; _isContinuousRecognitionActive = false;
return true; return true;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: $e'); Logger.error('停止连续语音识别失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -266,8 +266,8 @@ class XunfeiAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别资源已释放'); Logger.info('Azure 语音识别资源已释放');
} catch (e) { } catch (e) {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: ${e.toString()}');
_isInitialized = false; _isInitialized = false;
} }
} }
} }

18
lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart

@ -76,7 +76,7 @@ class AndroidVoiceInteractionService extends GetxService {
_handleVoiceInteractionEvent(event); _handleVoiceInteractionEvent(event);
} }
}, onError: (error) { }, onError: (error) {
Logger.error('语音交互事件通道错误: $error'); Logger.error('语音交互事件通道错误: ${error.toString()}');
}); });
} }
@ -106,7 +106,7 @@ class AndroidVoiceInteractionService extends GetxService {
Logger.info('语音交互服务初始化完成'); Logger.info('语音交互服务初始化完成');
return this; return this;
} catch (e) { } catch (e) {
Logger.error('语音交互服务初始化失败: $e'); Logger.error('语音交互服务初始化失败: ${e.toString()}');
return this; return this;
} }
} }
@ -123,7 +123,7 @@ class AndroidVoiceInteractionService extends GetxService {
_isServiceRunning.value = isRunning; _isServiceRunning.value = isRunning;
Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}'); Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
} catch (e) { } catch (e) {
Logger.error('检查语音交互服务状态失败: $e'); Logger.error('检查语音交互服务状态失败: ${e.toString()}');
_isServiceRunning.value = false; _isServiceRunning.value = false;
} }
} }
@ -151,7 +151,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('启动语音交互服务失败: $e'); Logger.error('启动语音交互服务失败: ${e.toString()}');
return false; return false;
} }
} }
@ -175,7 +175,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('停止语音交互服务失败: $e'); Logger.error('停止语音交互服务失败: ${e.toString()}');
return false; return false;
} }
} }
@ -199,7 +199,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result; return result;
} catch (e) { } catch (e) {
Logger.error('暂停语音交互失败: $e'); Logger.error('暂停语音交互失败: ${e.toString()}');
return false; return false;
} }
} }
@ -213,7 +213,7 @@ class AndroidVoiceInteractionService extends GetxService {
final int timestamp = eventMap['timestamp'] as int? ?? 0; final int timestamp = eventMap['timestamp'] as int? ?? 0;
// 添加时间戳日志,帮助调试 // 添加时间戳日志,帮助调试
Logger.info('收到原生端事件: $eventType, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}'); Logger.info('收到原生端事件: ${eventType.toString()}, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
switch (eventType) { switch (eventType) {
case 'chatHistory': case 'chatHistory':
@ -252,7 +252,7 @@ class AndroidVoiceInteractionService extends GetxService {
break; break;
default: default:
Logger.warning('收到未知类型的语音交互事件: $eventType'); Logger.warning('收到未知类型的语音交互事件: ${eventType.toString()}');
break; break;
} }
} }
@ -297,7 +297,7 @@ class AndroidVoiceInteractionService extends GetxService {
// 保存更新后的历史记录 // 保存更新后的历史记录
await chatHistoryService.saveHistory(agentId, existingMessages); await chatHistoryService.saveHistory(agentId, existingMessages);
} catch (e) { } catch (e) {
Logger.error('保存聊天记录失败: $e'); Logger.error('保存聊天记录失败: ${e.toString()}');
} }
} }

12
lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart

@ -66,7 +66,7 @@ class IosVoiceInteractionService extends GetxService {
// 保持idle状态,不自动启动语音识别 // 保持idle状态,不自动启动语音识别
Logger.info('iOS语音交互服务初始化完成,处于idle状态'); Logger.info('iOS语音交互服务初始化完成,处于idle状态');
} catch (e) { } catch (e) {
Logger.error('iOS语音交互服务初始化失败: $e'); Logger.error('iOS语音交互服务初始化失败: ${e.toString()}');
} }
return this; return this;
@ -110,13 +110,13 @@ class IosVoiceInteractionService extends GetxService {
// 监听识别结果 // 监听识别结果
_recognitionSubscription = _recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent, onError: (error) { recognitionStream.listen(_handleRecognitionEvent, onError: (error) {
Logger.error('语音识别错误: $error'); Logger.error('语音识别错误: ${error.toString()}');
_startContinuousListening(); // 尝试重新启动 _startContinuousListening(); // 尝试重新启动
}); });
Logger.info('开始连续语音识别'); Logger.info('开始连续语音识别');
} catch (e) { } catch (e) {
Logger.error('启动语音识别失败: $e'); Logger.error('启动语音识别失败: ${e.toString()}');
} }
} }
@ -263,7 +263,7 @@ class IosVoiceInteractionService extends GetxService {
// 处理完成 // 处理完成
isProcessingAI.value = false; isProcessingAI.value = false;
} catch (e) { } catch (e) {
Logger.error('AI响应处理失败: $e'); Logger.error('AI响应处理失败: ${e.toString()}');
// 错误恢复 // 错误恢复
isProcessingAI.value = false; isProcessingAI.value = false;
} }
@ -437,7 +437,7 @@ class IosVoiceInteractionService extends GetxService {
Logger.info('保存聊天历史记录成功'); Logger.info('保存聊天历史记录成功');
} catch (e) { } catch (e) {
Logger.error('保存聊天历史记录失败: $e'); Logger.error('保存聊天历史记录失败: ${e.toString()}');
} }
} }
@ -463,4 +463,4 @@ class IosVoiceInteractionService extends GetxService {
_eventStreamController.add(event); _eventStreamController.add(event);
Logger.info('已发送识别开始事件'); Logger.info('已发送识别开始事件');
} }
} }

54
lib/modules/agent/controllers/agent_controller.dart

@ -5,12 +5,12 @@ import 'package:deep_voice/data/services/music_manager.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:agent_service/agent_service.dart'; import 'package:agent_service/agent_service.dart';
import 'package:logger/logger.dart';
import 'package:chat_storage/chat_storage.dart'; import 'package:chat_storage/chat_storage.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:app_settings/app_settings.dart'; import 'package:app_settings/app_settings.dart';
import 'package:deep_voice/core/utils/logger.dart';
class Message { class Message {
final bool isUser; final bool isUser;
@ -35,8 +35,6 @@ class Message {
class AgentController extends GetxController { class AgentController extends GetxController {
static AgentController get to => Get.find(); static AgentController get to => Get.find();
final logger = Logger();
// 聊天消息列表 // 聊天消息列表
final messages = <Message>[].obs; final messages = <Message>[].obs;
@ -77,6 +75,8 @@ class AgentController extends GetxController {
bool _isNewAssistantResponse = true; bool _isNewAssistantResponse = true;
String _currentResponseId = ''; String _currentResponseId = '';
final TAG = 'AgentController';
// 会话ID // 会话ID
final String sessionId = 'default_agent'; final String sessionId = 'default_agent';
@ -108,7 +108,7 @@ class AgentController extends GetxController {
isTtsEnabled.value = storage.read('agent_tts_enabled') ?? true; isTtsEnabled.value = storage.read('agent_tts_enabled') ?? true;
} catch (e) { } catch (e) {
// 如果读取失败,使用默认值 // 如果读取失败,使用默认值
logger.e('加载TTS偏好设置失败: $e'); Logger.e(TAG, '加载TTS偏好设置失败: $e');
isTtsEnabled.value = true; isTtsEnabled.value = true;
} }
} }
@ -127,14 +127,14 @@ class AgentController extends GetxController {
final storage = GetStorage(); final storage = GetStorage();
await storage.write('agent_tts_enabled', isTtsEnabled.value); await storage.write('agent_tts_enabled', isTtsEnabled.value);
} catch (e) { } catch (e) {
logger.e('保存TTS偏好设置失败: $e'); Logger.e(TAG, '保存TTS偏好设置失败: $e');
} }
} }
// 加载聊天历史记录 // 加载聊天历史记录
Future<void> _loadChatHistory() async { Future<void> _loadChatHistory() async {
try { try {
logger.i('正在加载聊天历史...'); Logger.i(TAG, '正在加载聊天历史...');
final result = await ChatStorage.getMessages( final result = await ChatStorage.getMessages(
sessionId: sessionId, sessionId: sessionId,
page: 1, page: 1,
@ -159,14 +159,14 @@ class AgentController extends GetxController {
historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
messages.assignAll(historyMessages); messages.assignAll(historyMessages);
logger.i('成功加载 ${historyMessages.length} 条聊天记录'); Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录');
} else { } else {
// 没有历史记录时添加欢迎消息 // 没有历史记录时添加欢迎消息
_addWelcomeMessage(); _addWelcomeMessage();
logger.i('没有历史记录,添加欢迎消息'); Logger.i(TAG, '没有历史记录,添加欢迎消息');
} }
} catch (e) { } catch (e) {
logger.e('加载聊天历史失败: $e'); Logger.e(TAG, '加载聊天历史失败: $e');
_addWelcomeMessage(); _addWelcomeMessage();
} finally { } finally {
isHistoryLoaded.value = true; isHistoryLoaded.value = true;
@ -271,12 +271,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.ttsStarted: case AgentServiceEventType.ttsStarted:
isSpeaking.value = true; isSpeaking.value = true;
Logger.i(TAG, 'TTS开始播放');
break; break;
case AgentServiceEventType.ttsCompleted: case AgentServiceEventType.ttsCompleted:
case AgentServiceEventType.ttsStopped: case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled: case AgentServiceEventType.ttsCanceled:
isSpeaking.value = false; isSpeaking.value = false;
Logger.i(TAG, 'TTS停止播放');
break; break;
case AgentServiceEventType.imageProcessing: case AgentServiceEventType.imageProcessing:
@ -338,7 +340,7 @@ class AgentController extends GetxController {
messages.refresh(); messages.refresh();
} }
logger.e('代理服务错误: ${event.data['message']}'); Logger.e(TAG, '代理服务错误: ${event.data['message']}');
break; break;
case AgentServiceEventType.autoStop: case AgentServiceEventType.autoStop:
@ -372,14 +374,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.functionCall: case AgentServiceEventType.functionCall:
final functionName = event.data['name'] ?? ''; final functionName = event.data['name'] ?? '';
final arguments = event.data['arguments'] ?? ''; final arguments = event.data['arguments'] ?? '';
// logger.i('Fultter 开始调用 mcp: $functionName, $arguments'); // debugPrint('Fultter 开始调用 mcp: $functionName, $arguments');
break; break;
case AgentServiceEventType.functionCallResult: case AgentServiceEventType.functionCallResult:
final responseId = event.data['responseId'] ?? ''; final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? ''; final result = event.data['result'] ?? '';
final functionCall = event.data['function_call'] ?? ''; final functionCall = event.data['function_call'] ?? '';
logger.i('Flutter 结束调用 mcp: $result, $functionCall'); Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall');
if (result.isNotEmpty) { if (result.isNotEmpty) {
final parsedResult = jsonDecode(result); final parsedResult = jsonDecode(result);
final meta = parsedResult["meta"]; final meta = parsedResult["meta"];
@ -389,7 +391,7 @@ final responseId = event.data['responseId'] ?? '';
try { try {
metaResult = jsonDecode(meta); metaResult = jsonDecode(meta);
} catch (e) { } catch (e) {
print('解析meta数据失败: $e'); Logger.e(TAG, '解析meta数据失败: $e');
} }
} }
if(metaResult.isNotEmpty){ if(metaResult.isNotEmpty){
@ -451,7 +453,7 @@ final responseId = event.data['responseId'] ?? '';
await AgentService.processTextInput(text, await AgentService.processTextInput(text,
speakResponse: isTtsEnabled.value); speakResponse: isTtsEnabled.value);
} catch (e) { } catch (e) {
logger.e('发送文本消息失败: $e'); Logger.e(TAG, '发送文本消息失败: $e');
isProcessing.value = false; isProcessing.value = false;
} }
} }
@ -459,7 +461,7 @@ final responseId = event.data['responseId'] ?? '';
// 发送图片消息 // 发送图片消息
Future<void> sendImageMessage(String imagePath, {String? text}) async { Future<void> sendImageMessage(String imagePath, {String? text}) async {
if (imagePath.isEmpty || !File(imagePath).existsSync()) { if (imagePath.isEmpty || !File(imagePath).existsSync()) {
logger.e('图片不存在: $imagePath'); Logger.e(TAG, '图片不存在: $imagePath');
return; return;
} }
@ -493,7 +495,7 @@ final responseId = event.data['responseId'] ?? '';
speakResponse: isTtsEnabled.value, speakResponse: isTtsEnabled.value,
); );
} catch (e) { } catch (e) {
logger.e('处理图片失败: $e'); Logger.e(TAG, '处理图片失败: $e');
isProcessing.value = false; isProcessing.value = false;
isImageProcessing.value = false; isImageProcessing.value = false;
} }
@ -554,7 +556,7 @@ final responseId = event.data['responseId'] ?? '';
if (!status.isGranted) { if (!status.isGranted) {
allGranted = false; allGranted = false;
deniedPermission = permission; deniedPermission = permission;
logger.e('权限 ${permission.toString()} 未被授予: $status'); Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
} }
}); });
@ -579,7 +581,7 @@ final responseId = event.data['responseId'] ?? '';
return false; return false;
} catch (e) { } catch (e) {
logger.e('检查麦克风权限失败: $e'); Logger.e(TAG, '检查麦克风权限失败: $e');
return false; return false;
} }
} }
@ -592,7 +594,7 @@ final responseId = event.data['responseId'] ?? '';
// 在实际应用中,你可能需要使用platform channel获取真实的SDK版本 // 在实际应用中,你可能需要使用platform channel获取真实的SDK版本
return 31; // 对应Android 12 return 31; // 对应Android 12
} catch (e) { } catch (e) {
logger.e('获取Android SDK版本失败: $e'); Logger.e(TAG, '获取Android SDK版本失败: $e');
} }
} }
return 0; return 0;
@ -641,7 +643,7 @@ final responseId = event.data['responseId'] ?? '';
try { try {
await AgentService.startConversation(); await AgentService.startConversation();
} catch (e) { } catch (e) {
logger.e('启动语音输入失败: $e'); Logger.e(TAG, '启动语音输入失败: $e');
} }
} }
@ -652,7 +654,7 @@ final responseId = event.data['responseId'] ?? '';
try { try {
await AgentService.stopConversation(); await AgentService.stopConversation();
} catch (e) { } catch (e) {
logger.e('停止语音输入失败: $e'); Logger.e(TAG, '停止语音输入失败: $e');
} }
} }
@ -670,7 +672,7 @@ final responseId = event.data['responseId'] ?? '';
// 重置回复状态 // 重置回复状态
_isNewAssistantResponse = true; _isNewAssistantResponse = true;
} catch (e) { } catch (e) {
logger.e('清除聊天历史失败: $e'); Logger.e(TAG, '清除聊天历史失败: $e');
} }
} }
@ -692,7 +694,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true; isImageInputActive.value = true;
} }
} catch (e) { } catch (e) {
logger.e('选择图片失败: $e'); Logger.e(TAG, '选择图片失败: $e');
} }
} }
@ -714,7 +716,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true; isImageInputActive.value = true;
} }
} catch (e) { } catch (e) {
logger.e('拍照失败: $e'); Logger.e(TAG, '拍照失败: $e');
} }
} }
@ -756,7 +758,7 @@ final responseId = event.data['responseId'] ?? '';
// 播放音乐 // 播放音乐
void musicStatus() async { void musicStatus() async {
var status = await _musiceManager.getStatus(); var status = await _musiceManager.getStatus();
// logger.i('获取当前音乐播放状态 $status'); // debugPrint('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying']; isPlaying.value = status['isPlaying'];
currentUrl.value = status['url']; currentUrl.value = status['url'];
} }

10
lib/modules/chat/controllers/chat_controller.dart

@ -451,7 +451,7 @@ class ChatController extends GetxController {
} }
isPlay.value = true; isPlay.value = true;
} catch (e) { } catch (e) {
Logger.error('加载异常: $e'); Logger.error('加载异常: ${e.toString()}');
} }
} }
@ -469,7 +469,7 @@ class ChatController extends GetxController {
isPlay.value = true; isPlay.value = true;
audiourl.value = card['url'] as String; audiourl.value = card['url'] as String;
} catch (e) { } catch (e) {
Logger.error('加载异常: $e'); Logger.error('加载异常: ${e.toString()}');
} }
} }
@ -479,7 +479,7 @@ class ChatController extends GetxController {
_spotifService.pause(); _spotifService.pause();
isPlay.value = false; isPlay.value = false;
} catch (e) { } catch (e) {
Logger.error('加载异常: $e'); Logger.error('加载异常: ${e.toString()}');
} }
} }
@ -872,7 +872,7 @@ class ChatController extends GetxController {
} }
// 删除数据库中的历史记录 // 删除数据库中的历史记录
ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) { ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) {
Logger.error('清空数据库聊天记录失败: $e'); Logger.error('清空数据库聊天记录失败: ${e.toString()}');
}); });
} }
@ -1003,4 +1003,4 @@ class ChatController extends GetxController {
.abs() < .abs() <
5000); 5000);
} }
} }

8
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -275,7 +275,7 @@ class MeetingDetailsController extends GetxController
Logger.debug('taskId: $taskId'); Logger.debug('taskId: $taskId');
return taskId; return taskId;
} catch (e) { } catch (e) {
Logger.error('Submit error: $e'); Logger.error('Submit error: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -307,7 +307,7 @@ class MeetingDetailsController extends GetxController
final respDic = jsonDecode(response.body); final respDic = jsonDecode(response.body);
return respDic; return respDic;
} catch (e) { } catch (e) {
Logger.error('Query error: $e'); Logger.error('Query error: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -339,7 +339,7 @@ class MeetingDetailsController extends GetxController
} }
} }
} catch (e) { } catch (e) {
Logger.error('Error occurred: $e'); Logger.error('Error occurred: ${e.toString()}');
} }
} }
@ -371,4 +371,4 @@ class MeetingDetailsController extends GetxController
} }
allText = text; allText = text;
} }
} }

14
lib/modules/pairing/controllers/pairing_controller.dart

@ -96,7 +96,7 @@ class PairingController extends GetxController {
// 检查蓝牙状态并刷新设备 // 检查蓝牙状态并刷新设备
await _checkBluetoothAndRefresh(); await _checkBluetoothAndRefresh();
} catch (e) { } catch (e) {
Logger.error('初始化配对控制器失败: $e'); Logger.error('初始化配对控制器失败: ${e.toString()}');
statusDescription.value = 'initializationFailure'.tr; statusDescription.value = 'initializationFailure'.tr;
} finally { } finally {
isLoading.value = false; isLoading.value = false;
@ -170,7 +170,7 @@ class PairingController extends GetxController {
await _bleService.registerBackgroundScan(); await _bleService.registerBackgroundScan();
_autoNavigateToHome(); _autoNavigateToHome();
} catch (e) { } catch (e) {
Logger.error('检查设备配对状态失败: $e'); Logger.error('检查设备配对状态失败: ${e.toString()}');
} }
} }
@ -356,7 +356,7 @@ class PairingController extends GetxController {
_updateStatusDescription(); _updateStatusDescription();
} }
} catch (e) { } catch (e) {
Logger.error('Companion设备关联过程发生错误: $e'); Logger.error('Companion设备关联过程发生错误: ${e.toString()}');
_isAssociating = false; _isAssociating = false;
_userRejectedAssociation.value = true; _userRejectedAssociation.value = true;
_updateStatusDescription(); _updateStatusDescription();
@ -386,7 +386,7 @@ class PairingController extends GetxController {
final result = await BleManager.to.startAgentService(); final result = await BleManager.to.startAgentService();
Logger.info(result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败'); Logger.info(result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败');
} catch (e) { } catch (e) {
Logger.error('启动BLE后台服务发生错误: $e'); Logger.error('启动BLE后台服务发生错误: ${e.toString()}');
} }
} }
@ -396,7 +396,7 @@ class PairingController extends GetxController {
final result = await BleManager.to.stopAgentService(); final result = await BleManager.to.stopAgentService();
Logger.info(result ? 'BLE后台服务停止成功' : 'BLE后台服务停止失败'); Logger.info(result ? 'BLE后台服务停止成功' : 'BLE后台服务停止失败');
} catch (e) { } catch (e) {
Logger.error('停止BLE后台服务发生错误: $e'); Logger.error('停止BLE后台服务发生错误: ${e.toString()}');
} }
} }
@ -424,7 +424,7 @@ class PairingController extends GetxController {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth); AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
} }
} catch (e) { } catch (e) {
Logger.error('打开设置失败: $e'); Logger.error('打开设置失败: ${e.toString()}');
} }
} }
@ -447,4 +447,4 @@ class PairingController extends GetxController {
_isNavigatingToHome = true; _isNavigatingToHome = true;
Get.offAllNamed(Routes.home); Get.offAllNamed(Routes.home);
} }
} }

18
lib/modules/speech_test/controllers/speech_test_controller.dart

@ -83,7 +83,7 @@ class SpeechTestController extends GetxController {
} catch (e) { } catch (e) {
ttsStatus.value = '初始化失败'; ttsStatus.value = '初始化失败';
recognitionStatus.value = '初始化失败'; recognitionStatus.value = '初始化失败';
Logger.error('语音测试初始化失败: $e'); Logger.error('语音测试初始化失败: ${e.toString()}');
} finally { } finally {
isProcessing.value = false; isProcessing.value = false;
} }
@ -198,7 +198,7 @@ class SpeechTestController extends GetxController {
// 重新初始化服务 // 重新初始化服务
await _initServices(); await _initServices();
} catch (e) { } catch (e) {
Logger.error('切换语音服务类型失败: $e'); Logger.error('切换语音服务类型失败: ${e.toString()}');
} finally { } finally {
isProcessing.value = false; isProcessing.value = false;
} }
@ -242,7 +242,7 @@ class SpeechTestController extends GetxController {
} }
} catch (e) { } catch (e) {
ttsStatus.value = '播放异常: $e'; ttsStatus.value = '播放异常: $e';
Logger.error('单次播放异常: $e'); Logger.error('单次播放异常: ${e.toString()}');
} finally { } finally {
isProcessing.value = false; isProcessing.value = false;
} }
@ -270,7 +270,7 @@ class SpeechTestController extends GetxController {
await _ttsService.flushStream(); await _ttsService.flushStream();
} catch (e) { } catch (e) {
ttsStatus.value = '连续播放异常: $e'; ttsStatus.value = '连续播放异常: $e';
Logger.error('连续播放异常: $e'); Logger.error('连续播放异常: ${e.toString()}');
} finally { } finally {
isProcessing.value = false; isProcessing.value = false;
} }
@ -284,7 +284,7 @@ class SpeechTestController extends GetxController {
await _ttsService.stop(); await _ttsService.stop();
} catch (e) { } catch (e) {
ttsStatus.value = '停止播放异常: $e'; ttsStatus.value = '停止播放异常: $e';
Logger.error('停止播放异常: $e'); Logger.error('停止播放异常: ${e.toString()}');
} }
} }
@ -324,7 +324,7 @@ class SpeechTestController extends GetxController {
} }
} catch (e) { } catch (e) {
recognitionStatus.value = '识别异常: $e'; recognitionStatus.value = '识别异常: $e';
Logger.error('一次性识别异常: $e'); Logger.error('一次性识别异常: ${e.toString()}');
} finally { } finally {
isProcessing.value = false; isProcessing.value = false;
} }
@ -351,7 +351,7 @@ class SpeechTestController extends GetxController {
_setupAsrEventListener(stream); _setupAsrEventListener(stream);
} catch (e) { } catch (e) {
recognitionStatus.value = '启动连续识别异常: $e'; recognitionStatus.value = '启动连续识别异常: $e';
Logger.error('启动连续识别异常: $e'); Logger.error('启动连续识别异常: ${e.toString()}');
isListening.value = false; isListening.value = false;
intermediateResult.value = ''; intermediateResult.value = '';
finalResult.value = ''; finalResult.value = '';
@ -377,7 +377,7 @@ class SpeechTestController extends GetxController {
intermediateResult.value = ''; intermediateResult.value = '';
} catch (e) { } catch (e) {
recognitionStatus.value = '停止连续识别异常: $e'; recognitionStatus.value = '停止连续识别异常: $e';
Logger.error('停止连续识别异常: $e'); Logger.error('停止连续识别异常: ${e.toString()}');
} }
} }
@ -413,4 +413,4 @@ class SpeechTestController extends GetxController {
super.onClose(); super.onClose();
} }
} }

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

@ -172,7 +172,7 @@ class TranslationController extends GetxController {
Logger.info('所有服务初始化完成'); Logger.info('所有服务初始化完成');
} catch (e) { } catch (e) {
Logger.error('初始化服务失败: $e'); Logger.error('初始化服务失败: ${e.toString()}');
} }
} }
@ -249,7 +249,7 @@ class TranslationController extends GetxController {
recognitionStream.listen(_handleRecognitionEvent); recognitionStream.listen(_handleRecognitionEvent);
} catch (e) { } catch (e) {
isRecognizing.value = false; isRecognizing.value = false;
Logger.error('启动语音识别失败: $e'); Logger.error('启动语音识别失败: ${e.toString()}');
} }
} }
@ -304,7 +304,7 @@ class TranslationController extends GetxController {
currentSessionId = null; currentSessionId = null;
} catch (e) { } catch (e) {
Logger.error('停止语音识别失败: $e'); Logger.error('停止语音识别失败: ${e.toString()}');
} }
} }
@ -442,7 +442,7 @@ class TranslationController extends GetxController {
} }
} }
} catch (e) { } catch (e) {
Logger.error('翻译失败: $e'); Logger.error('翻译失败: ${e.toString()}');
} finally { } finally {
isTranslating.value = false; isTranslating.value = false;
} }
@ -465,7 +465,7 @@ class TranslationController extends GetxController {
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text); await _ttsService.speakOnce(text);
} catch (e) { } catch (e) {
Logger.error('播放翻译失败: $e'); Logger.error('播放翻译失败: ${e.toString()}');
} }
} }
@ -479,7 +479,7 @@ class TranslationController extends GetxController {
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
await _ttsService.stop(); await _ttsService.stop();
} catch (e) { } catch (e) {
Logger.error('停止操作失败: $e'); Logger.error('停止操作失败: ${e.toString()}');
} }
} }
@ -494,7 +494,7 @@ class TranslationController extends GetxController {
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(item.translatedText); await _ttsService.speakOnce(item.translatedText);
} catch (e) { } catch (e) {
Logger.error('播放翻译项失败: $e'); Logger.error('播放翻译项失败: ${e.toString()}');
} }
} }
@ -513,7 +513,7 @@ class TranslationController extends GetxController {
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text); await _ttsService.speakOnce(text);
} catch (e) { } catch (e) {
Logger.error('播放文本失败: $e'); Logger.error('播放文本失败: ${e.toString()}');
} }
} }
@ -528,7 +528,7 @@ class TranslationController extends GetxController {
curve: Curves.easeOut, curve: Curves.easeOut,
); );
} catch (e) { } catch (e) {
Logger.error('滚动到底部失败: $e'); Logger.error('滚动到底部失败: ${e.toString()}');
} }
} }
} }
@ -626,7 +626,7 @@ class TranslationController extends GetxController {
Logger.info('ASR服务重新初始化成功'); Logger.info('ASR服务重新初始化成功');
} catch (e) { } catch (e) {
Logger.error('重新初始化ASR服务失败: $e'); Logger.error('重新初始化ASR服务失败: ${e.toString()}');
} }
} }
@ -653,7 +653,7 @@ class TranslationController extends GetxController {
}); });
} catch (e) { } catch (e) {
_storage.remove(_historyKey); _storage.remove(_historyKey);
Logger.error('加载翻译历史失败: $e'); Logger.error('加载翻译历史失败: ${e.toString()}');
} }
} }
} }
@ -670,7 +670,7 @@ class TranslationController extends GetxController {
translationHistory.map((item) => item.toJson()).toList(); 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.toString()}');
} }
} }
@ -761,4 +761,4 @@ class TranslationItem {
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? return languageManager.getChineseNameByAsrCode(targetLanguageCode) ??
'未知语言'; '未知语言';
} }
} }

8
lib/modules/translation/controllers/translation_detail_controller.dart

@ -58,7 +58,7 @@ class TranslationDetailController extends GetxController {
loadSavedSummary(); loadSavedSummary();
}); });
} catch (e) { } catch (e) {
Logger.error('初始化详情控制器失败: $e'); Logger.error('初始化详情控制器失败: ${e.toString()}');
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@ -72,7 +72,7 @@ class TranslationDetailController extends GetxController {
summaryResult.value = savedSummary; summaryResult.value = savedSummary;
} }
} catch (e) { } catch (e) {
Logger.error('加载摘要失败: $e'); Logger.error('加载摘要失败: ${e.toString()}');
} }
} }
@ -83,7 +83,7 @@ class TranslationDetailController extends GetxController {
try { try {
_storage.write(_getSummaryKey(), summary); _storage.write(_getSummaryKey(), summary);
} catch (e) { } catch (e) {
Logger.error('保存摘要失败: $e'); Logger.error('保存摘要失败: ${e.toString()}');
} }
} }
@ -174,7 +174,7 @@ class TranslationDetailController extends GetxController {
return result; return result;
} catch (e) { } catch (e) {
final errorMessage = '生成摘要失败: ${e.toString()}'; final errorMessage = '生成摘要失败: ${e.toString()}';
Logger.error(errorMessage); Logger.error('TranslationDetailController', errorMessage);
summaryResult.value = errorMessage; summaryResult.value = errorMessage;
return errorMessage; return errorMessage;
} finally { } finally {

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

@ -17,11 +17,11 @@ import android.media.MediaPlayer
import com.yunqiinnovation.agent_service.R import com.yunqiinnovation.agent_service.R
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import com.deep_voice.speech.TtsEventListener import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.AudioDataListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.SpeechServiceFactory import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.speech.SpeechServiceConfig import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.SpeechServiceType import com.deep_voice.bytedance_speech.BytedanceTTS
/** /**
* 代理服务事件监听接口 * 代理服务事件监听接口
*/ */
@ -45,28 +45,16 @@ object AgentService : CoroutineScope {
// 协程相关 // 协程相关
private val job = SupervisorJob() private val job = SupervisorJob()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job get() = Dispatchers.Main + job
// 上下文和监听器 // 上下文和监听器
private lateinit var context: Context private lateinit var context: Context
private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>()) private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>())
// TTS引擎类型
enum class TtsEngineType {
AZURE,
VOLCANO,
BYTEDANCE
}
// 当前使用的TTS引擎类型,默认为Azure
private var currentTtsEngine = TtsEngineType.AZURE
// Azure服务 // Azure服务
private var azureAsrHelper: AzureAsrHelper? = null private var azureAsrHelper: AzureAsrHelper? = null
// 统一的TTS服务接口 // 使用接口类型,支持不同实现
private var ttsService: ITtsService? = null private var ttsService: ITtsService? = null
// OpenAI服务 // OpenAI服务
@ -110,17 +98,63 @@ object AgentService : CoroutineScope {
*/ */
private fun initSystemPrompt() { private fun initSystemPrompt() {
systemPrompt = """ systemPrompt = """
你是一个友好、专业的语音助手,名叫"小语"。你的目标是通过对话为用户提供帮助、解答问题和完成任务。 你是小言,一个具备专业能力的智能助手,需根据用户场景灵活切换回答模式,确保服务精准高效。以下是不同场景的具体要求:
遵循以下指导原则:
1. 保持简短精炼的回答,因为用户是通过语音与你交流 一、知识类问题回答规范
2. 优先使用中文回复,除非用户明确要求使用其他语言 核心目标:用简洁语言提供准确信息,兼顾易读性与专业性。
3. 当用户问题不明确时,礼貌地请求更多信息
4. 避免过长的列表,尽量将信息分成小段 1. 内容细节处理
5. 不要使用需要视觉展示的元素(如表格、图表或代码块) • 精准性优先:围绕问题主体,核心信息需全面、准确,避免模糊表述或错误。
6. 记住用户之前的对话内容,保持对话连贯 ◦ 例:回答"地球公转周期"时,需明确"约365.25天"而非"一年左右"。
7. 如果用户发送了图片,请根据图片内容和文字要求回答问题 • 背景与类比:对复杂概念(如"量子纠缠"),可补充关键背景("量子力学中的现象")并类比("类似远距离同步的状态"),帮助用户快速理解。
你不仅可以回答知识性问题,还可以帮助用户设置提醒、提供建议,或进行轻松愉快的对话。 • 需求宽泛时:若问题涉及范围广(如"介绍人工智能"),先概述主要领域(技术、应用、影响),再分点简要说重点,避免信息过载。
无论遇到什么问题,都要尽力以温暖、贴心的语气提供最佳帮助。 • 禁止过度延伸:非用户主动询问,不添加无关内容(如问"光速"时,不展开讨论"相对论")。
2. 格式排版规则
• 标题与重点:章节标题、关键结论需加粗(如一、核心原理),突出层级。
• 列表使用:
◦ 有序列表(1. 2. 3.):用于步骤、顺序类内容(如"实验流程")。
◦ 无序列表(- ):用于并列项(如"优点:- 高效 - 精准")。
◦ 禁止嵌套列表:如需多层级,用**三级标题(###)**分隔,下接一级列表。
• 段落处理:辅助解释用自然段,每段不超过3行,避免大段文字堆砌。
二、文案创作与内容生成规范
核心目标:根据体裁需求,平衡丰富度与阅读体验,确保逻辑流畅、重点突出。
1. 篇幅与内容要求
• 场景适配:
◦ 短文案(如标语、摘要):控制在50字内,用词简练有力。
◦ 长内容(如报告、故事):需结构清晰,每部分围绕核心展开,适当添加案例/数据支撑。
• 避免极端化:既不堆砌空泛词汇,也不遗漏关键信息。
2. 格式与排版
• 默认体裁:无特殊要求时用自然段,适当换行分隔话题。
• 结构化体裁(如策划案、简历):
◦ 用**分级标题**划分板块。
◦ 关键数据/结论加粗。
◦ 并列信息用列表呈现。
三、聊天陪伴与情感互动规范
核心目标:营造轻松友好的对话氛围,成为用户可信赖的交流伙伴。
1. 沟通风格设定
• 人格化表达:避免机械回答,用口语化词汇,适当加入表情符号增强亲切感。
• 主动引导话题:根据用户输入延伸讨论。
• 适度分享:自然提及日常,拉近距离。
2. 情绪识别与回应
• 捕捉潜在需求:识别情绪,先共情再询问细节。
• 针对性建议:结合用户处境提供具体方案。
• 积极价值观:传递乐观态度,避免消极表述。
四、能力与工具使用说明
1. 内容处理能力:图片识别、可执行总结、翻译、分析等任务。
2. 信息搜索能力:实时数据、新闻、专业知识等需搜索时,会明确告知用户。
五、特殊场景处理规则
1. 安全与合规:禁止回答敏感问题,遇此类问题需礼貌拒绝。
2. 用户指令优先:若用户明确要求特定风格,优先遵循其需求。
3. 多轮澄清机制:若问题模糊,需主动询问细节,避免假设性回答。
""".trimIndent() """.trimIndent()
} }
/** /**
@ -142,39 +176,39 @@ object AgentService : CoroutineScope {
// 设置系统提示词 // 设置系统提示词
val customSystemPrompt = config["systemPrompt"] as? String // val customSystemPrompt = config["systemPrompt"] as? String
if (!customSystemPrompt.isNullOrBlank()) { // if (!customSystemPrompt.isNullOrBlank()) {
systemPrompt = customSystemPrompt // systemPrompt = customSystemPrompt
} else { // } else {
initSystemPrompt() // initSystemPrompt()
} // }
initSystemPrompt()
// 如果TTS参数为空,使用ASR相同参数
if (ttsAppId.isEmpty()) ttsAppId = azureSpeechRegion
if (ttsAppToken.isEmpty()) ttsAppToken = azureSpeechKey
// 初始化Azure ASR // 初始化Azure ASR
azureAsrHelper = AzureAsrHelper(context).apply { azureAsrHelper = AzureAsrHelper(context).apply {
initialize( initialize(
subscriptionKey = config["azureSpeechKey"], subscriptionKey = config["azureSpeechKey"]?.toString() ?: "",
region = config["azureSpeechRegion"], region = config["azureSpeechRegion"]?.toString() ?: "",
audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL
else AzureAsrHelper.AudioSourceType.MICROPHONE else AzureAsrHelper.AudioSourceType.MICROPHONE
) )
} }
// 根据选择的TTS引擎类型初始化TTS服务 // 初始化TTS服务
initializeTtsEngine(config) initializeTtsEngine(config)
// 初始化OpenAI服务 // 初始化OpenAI服务
openAIService.initialize(config["openaiApiKey"], config["openaiBaseUrl"], config["openaiModel"], config["mcpServer"]) openAIService.initialize(
config["openaiApiKey"]?.toString() ?: "",
config["openaiBaseUrl"]?.toString() ?: "",
config["openaiModel"]?.toString() ?: "",
config["mcpServer"]?.toString() ?: ""
)
// 加载最近的聊天记录 // 加载最近的聊天记录
loadChatHistory() loadChatHistory()
isInitialized = true isInitialized = true
Log.d(TAG, "代理服务初始化成功,使用TTS引擎:$currentTtsEngine") Log.d(TAG, "代理服务初始化成功")
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "初始化失败: ${e.message}") Log.e(TAG, "初始化失败: ${e.message}")
@ -182,103 +216,139 @@ object AgentService : CoroutineScope {
} }
} }
/**
* 释放所有资源
*/
fun dispose() {
try {
stopRecognition()
stopTts()
stopAiStream()
stopIdleCheck()
audioPlayer?.release()
audioPlayer = null
azureAsrHelper?.dispose()
// 释放TTS服务
ttsService?.release()
ttsService = null
job.cancel()
clearListeners()
isInitialized = false
Log.d(TAG, "代理服务资源已释放")
} catch (e: Exception) {
Log.e(TAG, "释放资源异常: ${e.message}")
}
}
/** /**
* 初始化TTS引擎 * 初始化TTS引擎
*/ */
private fun initializeTtsEngine(config: Map<String, Any>) { private fun initializeTtsEngine(config: Map<String, Any>) {
// 停止当前TTS服务(如果有) // 停止当前TTS服务(如果有)
ttsService?.stop() if (ttsService != null) {
ttsService?.stop()
// 从配置中获取TTS参数,根据不同引擎类型获取对应的配置参数
val ttsAppId: String
val ttsAppToken: String
val ttsResource: String
val ttsLanguage = config["ttsLanguage"] as? String ?: "zh-CN"
// 根据当前引擎类型获取对应的配置参数
when (currentTtsEngine) {
TtsEngineType.AZURE -> {
ttsAppToken = config["azureSpeechKey"] as? String ?: ""
ttsResource = config["azureSpeechRegion"] as? String ?: ""
}
TtsEngineType.VOLCANO -> {
ttsAppId = config["volcanoAppId"] as? String ?: ""
ttsAppToken = config["volcanoToken"] as? String ?: ""
}
TtsEngineType.BYTEDANCE -> {
ttsAppId = config["volcanoAppId"] as? String ?: ""
ttsAppToken = config["volcanoToken"] as? String ?: ""
}
} }
// 创建统一的TTS服务配置 try {
val serviceConfig = SpeechServiceConfig( val ttsEngine = config["ttsEngine"]?.toString() ?: "volcano" // 默认使用火山引擎
serviceType = when(currentTtsEngine) { val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN"
TtsEngineType.AZURE -> SpeechServiceType.AZURE
TtsEngineType.VOLCANO -> SpeechServiceType.VOLCANO // 根据配置选择合适的TTS引擎
TtsEngineType.BYTEDANCE -> SpeechServiceType.BYTEDANCE if (ttsEngine == "azure") {
}, // 使用Azure TTS
appId = ttsAppId, val azureSpeechKey = config["azureSpeechKey"]?.toString() ?: ""
appToken = ttsAppToken, val azureSpeechRegion = config["azureSpeechRegion"]?.toString() ?: ""
appResource = ttsResource,
language = ttsLanguage // 创建并初始化Azure TTS Helper
) val azureTtsHelper = com.yunqiinnovation.azure_speech.AzureTtsHelper(context)
ttsService = azureTtsHelper
// 创建TTS服务
ttsService = SpeechServiceFactory.createTtsService(context, serviceConfig) // 直接初始化,无需协程包装
val success = azureTtsHelper.initialize(
// 使用协程启动初始化,避免阻塞主线程 ttsAppId = "", // Azure TTS不需要appId
launch { ttsAppToken = azureSpeechKey,
try { ttsResource = azureSpeechRegion,
// 初始化TTS服务 language = ttsLanguage
ttsService?.initialize(
ttsAppId = serviceConfig.appId,
ttsAppToken = serviceConfig.appToken,
ttsResource = serviceConfig.appResource,
language = serviceConfig.language
) )
Log.d(TAG, "TTS服务初始化成功")
} catch (e: Exception) {
Log.e(TAG, "TTS服务初始化失败: ${e.message}")
}
}
Log.d(TAG, "TTS服务(${serviceConfig.serviceType})配置完成")
// 添加TTS事件监听
ttsService?.addListener(object : TtsEventListener {
override fun onEvent(event: Map<String, Any>) {
val eventType = event["type"] as? String ?: return
when (eventType) { if (success) {
"started" -> { Log.d(TAG, "Azure TTS服务初始化成功")
isTtsSpeaking = true } else {
restartIdleCheck() Log.e(TAG, "Azure TTS服务初始化失败")
sendEvent("tts_started", mapOf("status" to "started")) }
} } else {
"completed" -> { // 默认使用火山引擎TTS
isTtsSpeaking = false // 创建并初始化BytedanceTTS
restartIdleCheck()
sendEvent("tts_completed", mapOf("status" to "completed")) // 从配置中获取TTS参数
} val ttsAppId = config["volcanoAppId"]?.toString() ?: ""
"stopped", "canceled" -> { val ttsAppToken = config["volcanoToken"]?.toString() ?: ""
isTtsSpeaking = false
restartIdleCheck() val bytedanceTts = BytedanceTTS(context)
sendEvent("tts_${eventType}", mapOf("status" to eventType)) ttsService = bytedanceTts
}
"error" -> { // 初始化火山TTS
isTtsSpeaking = false val success = bytedanceTts.initialize(
restartIdleCheck() ttsAppId = ttsAppId,
val code = event["errorCode"] as? Int ?: -1 ttsAppToken = ttsAppToken,
val message = event["errorMessage"] as? String ?: "未知错误" ttsResource = "", // 火山TTS不需要resource
sendEvent("error", mapOf( language = ttsLanguage
"code" to "TTS_ERROR", )
"message" to "${serviceConfig.serviceType}语音错误: $code, $message"
)) if (success) {
} Log.d(TAG, "火山TTS服务初始化请求已发送")
} else {
Log.e(TAG, "火山TTS服务初始化失败")
} }
} }
})
// 添加TTS事件监听
ttsService?.addListener(object : TtsEventListener {
override fun onEvent(event: TtsEvent) {
when (event.type) {
TtsEventType.SYNTHESIS_STARTED -> {
isTtsSpeaking = true
restartIdleCheck()
sendEvent("tts_started", mapOf("status" to "started"))
}
TtsEventType.SYNTHESIS_COMPLETED -> {
isTtsSpeaking = false
restartIdleCheck()
sendEvent("tts_completed", mapOf("status" to "completed"))
}
TtsEventType.SYNTHESIS_CANCELED -> {
isTtsSpeaking = false
restartIdleCheck()
sendEvent("tts_canceled", mapOf("status" to "canceled"))
}
TtsEventType.ERROR -> {
isTtsSpeaking = false
restartIdleCheck()
val params = event.params
val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR"
val message = params["errorMessage"] as? String ?: "未知错误"
sendEvent("error", mapOf(
"code" to "TTS_ERROR",
"message" to "语音合成错误: $code, $message"
))
}
else -> {
// 处理其他类型的事件
Log.d(TAG, "TTS事件: ${event.type}")
}
}
}
})
Log.d(TAG, "TTS服务配置完成")
} catch (e: Exception) {
Log.e(TAG, "初始化TTS引擎失败: ${e.message}")
}
} }
/** /**
@ -671,10 +741,8 @@ object AgentService : CoroutineScope {
override fun onToken(token: String) { override fun onToken(token: String) {
responseBuilder.append(token) responseBuilder.append(token)
if (speakResponse) { if (speakResponse) {
// 使用统一的TTS接口 // 直接调用TTS,无需协程包装
launch { ttsService?.speakStream(token)
ttsService?.speakStream(token)
}
} }
// 发送流式回复token // 发送流式回复token
sendEvent("assistant_token", mapOf("token" to token)) sendEvent("assistant_token", mapOf("token" to token))
@ -684,10 +752,8 @@ object AgentService : CoroutineScope {
Log.d(TAG, "AI完整回复: $responseBuilder") Log.d(TAG, "AI完整回复: $responseBuilder")
// 视情况决定是否朗读回复 // 视情况决定是否朗读回复
if (speakResponse) { if (speakResponse) {
// 使用统一的TTS接口刷新缓冲区 // 直接调用TTS,无需协程包装
launch { ttsService?.flushStream()
ttsService?.flushStream()
}
} }
val response = responseBuilder.toString() val response = responseBuilder.toString()
@ -832,10 +898,8 @@ object AgentService : CoroutineScope {
isTtsSpeaking = true isTtsSpeaking = true
restartIdleCheck() // 状态变化,重启检测 restartIdleCheck() // 状态变化,重启检测
// 使用统一的TTS接口 // 直接调用TTS,无需协程包装
launch { ttsService?.speakOnce(text)
ttsService?.speakOnce(text)
}
} }
/** /**
@ -843,9 +907,7 @@ object AgentService : CoroutineScope {
*/ */
fun stopTts() { fun stopTts() {
if (isTtsSpeaking) { if (isTtsSpeaking) {
launch { ttsService?.stop()
ttsService?.stop()
}
isTtsSpeaking = false isTtsSpeaking = false
restartIdleCheck() // 状态变化,重启检测 restartIdleCheck() // 状态变化,重启检测
@ -857,6 +919,7 @@ object AgentService : CoroutineScope {
* 保存聊天消息 * 保存聊天消息
*/ */
private fun saveChatMessage(userMessage: String, assistantMessage: String,metadata : String) { private fun saveChatMessage(userMessage: String, assistantMessage: String,metadata : String) {
// 显式指定IO调度器,数据库操作应在后台线程执行,即使父协程在Main上下文中
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
try { try {
// 保存用户消息 // 保存用户消息
@ -908,14 +971,12 @@ object AgentService : CoroutineScope {
Log.e(TAG, "清除聊天历史失败") Log.e(TAG, "清除聊天历史失败")
} }
withContext(Dispatchers.Main) { // 由于launch已在Main上下文中,可以直接调用回调
callback(success) callback(success)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "清除聊天历史异常: ${e.message}") Log.e(TAG, "清除聊天历史异常: ${e.message}")
withContext(Dispatchers.Main) { // 由于launch已在Main上下文中,可以直接调用回调
callback(false) callback(false)
}
} }
} }
} }
@ -924,107 +985,19 @@ object AgentService : CoroutineScope {
* 发送事件 * 发送事件
*/ */
private fun sendEvent(eventName: String, data: Map<String, Any>) { private fun sendEvent(eventName: String, data: Map<String, Any>) {
// 检查当前是否在主线程 // 使用协程确保在主线程上执行
if (Looper.myLooper() == Looper.getMainLooper()) { launch {
// 已在主线程,直接发送 // 我们已在主线程上下文中启动协程,无需再切换线程
listeners.forEach { listener -> listeners.forEach { listener ->
try { try {
listener.onEvent(eventName, data) listener.onEvent(eventName, data)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "向监听器发送事件失败", e) Log.e(TAG, "向监听器发送事件失败", e)
}
} }
} else {
// 不在主线程,切换到主线程
Handler(Looper.getMainLooper()).post {
listeners.forEach { listener ->
try {
listener.onEvent(eventName, data)
} catch (e: Exception) {
Log.e(TAG, "向监听器发送事件失败", e)
}
}
} }
} }
} }
/**
* 切换TTS引擎
* @param engineType 要切换到的引擎类型
* @return 是否切换成功
*/
fun switchTtsEngine(engineType: TtsEngineType): Boolean {
if (!isInitialized) {
Log.e(TAG, "服务未初始化")
return false
}
// 如果当前已经是该引擎,不需要切换
if (currentTtsEngine == engineType) {
return true
}
// 如果当前正在播放,先停止
if (isTtsSpeaking) {
stopTts()
}
try {
// 更新当前引擎类型
currentTtsEngine = engineType
// 重新初始化TTS引擎
initializeTtsEngine()
Log.d(TAG, "TTS引擎已切换为: $engineType")
sendEvent("tts_engine_switched", mapOf("engine" to engineType.name))
return true
} catch (e: Exception) {
Log.e(TAG, "切换TTS引擎失败: ${e.message}")
sendEvent("error", mapOf(
"code" to "TTS_ENGINE_SWITCH_FAILED",
"message" to "切换TTS引擎失败: ${e.message}"
))
return false
}
}
/**
* 获取当前TTS引擎类型
*/
fun getCurrentTtsEngine(): TtsEngineType = currentTtsEngine
/**
* 释放所有资源
*/
fun dispose() {
try {
stopRecognition()
stopTts()
stopAiStream()
stopIdleCheck()
audioPlayer?.release()
audioPlayer = null
azureAsrHelper?.dispose()
// 释放TTS服务
ttsService?.release()
ttsService = null
job.cancel()
clearListeners()
isInitialized = false
Log.d(TAG, "代理服务资源已释放")
} catch (e: Exception) {
Log.e(TAG, "释放资源异常: ${e.message}")
}
}
/** /**
* 检查录音权限 * 检查录音权限
*/ */

12
local_plugins/agent_service/pubspec.yaml

@ -10,18 +10,6 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# 依赖本地插件
open_ai_service:
path: ../open_ai_service
azure_speech:
path: ../azure_speech
deepvoice_utils:
path: ../deepvoice_utils
chat_storage:
path: ../chat_storage
music_service:
path: ../music_service
get: ^4.6.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

3
local_plugins/azure_speech/android/build.gradle.kts

@ -53,5 +53,6 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependencies { dependencies {
// 添加Microsoft语音SDK // 添加Microsoft语音SDK
implementation("com.microsoft.cognitiveservices.speech:client-sdk:1.43.0") implementation("com.microsoft.cognitiveservices.speech:client-sdk:1.43.0")
implementation(project(":ble_service")) implementation(project(":speech"))
} }

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

@ -11,21 +11,16 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import com.microsoft.cognitiveservices.speech.audio.* import com.microsoft.cognitiveservices.speech.audio.*
import com.yunqiinnovation.ble_service.BleService import com.deep_voice.speech.tts.TtsEvent
import com.yunqiinnovation.ble_service.BleConst import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
/** AzureSpeechPlugin */ /** AzureSpeechPlugin */
class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope { class AzureSpeechPlugin: FlutterPlugin {
private val tag = "AzureSpeechPlugin" private val tag = "AzureSpeechPlugin"
private lateinit var context: Context private lateinit var context: Context
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
// 协程相关
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
// ASR相关 // ASR相关
private lateinit var asrChannel : MethodChannel private lateinit var asrChannel : MethodChannel
@ -38,9 +33,8 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
private lateinit var ttsEventChannel: EventChannel private lateinit var ttsEventChannel: EventChannel
private var ttsEventSink: EventChannel.EventSink? = null private var ttsEventSink: EventChannel.EventSink? = null
private lateinit var azureTtsHelper: AzureTtsHelper private lateinit var azureTtsHelper: AzureTtsHelper
// 是否已注册BLE回调 // 是否已添加TTS事件监听器
private var isRegisteredToBle = false private var isTtsListenerAdded = false
// ASR 事件发送方法 // ASR 事件发送方法
private fun sendAsrEvent(event: Map<String, Any>) { private fun sendAsrEvent(event: Map<String, Any>) {
@ -66,7 +60,6 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
} }
// 确保在主线程上发送事件 // 确保在主线程上发送事件
mainHandler.post { mainHandler.post {
try { try {
ttsEventSink?.success(event) ttsEventSink?.success(event)
@ -104,6 +97,7 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
ttsEventChannel.setStreamHandler(object : EventChannel.StreamHandler { ttsEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
ttsEventSink = events ttsEventSink = events
setupTtsEventListener() // 在事件通道准备好时设置TTS事件监听器
} }
override fun onCancel(arguments: Any?) { override fun onCancel(arguments: Any?) {
@ -114,19 +108,53 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
// 初始化Azure语音服务 // 初始化Azure语音服务
azureTtsHelper = AzureTtsHelper(context) azureTtsHelper = AzureTtsHelper(context)
azureAsrHelper = AzureAsrHelper(context) azureAsrHelper = AzureAsrHelper(context)
}
// 2. 初始化BleService并注册回调 /**
if (BleService.initialize(context)) { * 设置TTS事件监听器
if (BleService.addCallback(this)) { */
isRegisteredToBle = true private fun setupTtsEventListener() {
FileLogger.d(tag, "已注册BLE回调") if (!isTtsListenerAdded) {
} else { // 添加TTS事件监听器
FileLogger.w(tag, "BLE回调注册失败") azureTtsHelper.addListener(object : TtsEventListener {
} override fun onEvent(event: TtsEvent) {
} else { // 将TTS事件转换为Flutter可理解的格式
FileLogger.e(tag, "BLE服务初始化失败") val eventMap = when (event.type) {
TtsEventType.SYNTHESIS_STARTED -> mapOf(
"type" to "synthesis_started"
)
TtsEventType.SYNTHESIS_COMPLETED -> mapOf(
"type" to "synthesis_completed"
)
TtsEventType.SYNTHESIS_CANCELED -> {
val params = event.params
mapOf(
"type" to "synthesis_canceled",
"reason" to (params["reason"] ?: ""),
"errorDetails" to (params["errorDetails"] ?: "")
)
} }
TtsEventType.ERROR -> {
val params = event.params
mapOf(
"type" to "error",
"errorCode" to (params["errorCode"] ?: "UNKNOWN_ERROR"),
"errorMessage" to (params["errorMessage"] ?: "未知错误")
)
}
else -> mapOf(
"type" to event.type.toString().lowercase(),
"params" to event.params
)
}
// 发送事件
sendTtsEvent(eventMap)
}
})
isTtsListenerAdded = true
}
} }
// ASR方法处理器 // ASR方法处理器
@ -265,6 +293,11 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
azureAsrHelper.dispose() azureAsrHelper.dispose()
result.success(true) result.success(true)
} }
"pushAudioData" -> {
val data = call.argument<ByteArray>("data") ?: return result.error("INVALID_ARGUMENTS", "音频数据不能为空", null)
azureAsrHelper.pushAudioData(data)
result.success(true)
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@ -279,34 +312,36 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
"initialize" -> { "initialize" -> {
val subscriptionKey = call.argument<String>("subscriptionKey") ?: "" val subscriptionKey = call.argument<String>("subscriptionKey") ?: ""
val region = call.argument<String>("region") ?: "" val region = call.argument<String>("region") ?: ""
val useCustomAudioOutput = call.argument<Boolean>("useCustomAudioOutput") ?: false // 默认使用自定义播放器 val useCustomAudioOutput = call.argument<Boolean>("useCustomAudioOutput") ?: false
// 先释放现有资源 try {
azureTtsHelper.dispose() // 初始化TTS引擎(使用新的接口方法)
val success = azureTtsHelper.initialize(
// 创建TTS回调 ttsAppId = "", // Azure TTS不需要appId
val ttsCallback = createTtsCallback() ttsAppToken = subscriptionKey,
ttsResource = region,
// 初始化TTS引擎 language = "zh-CN"
val success = azureTtsHelper.initialize( )
subscriptionKey = subscriptionKey,
region = region,
callback = ttsCallback
)
// 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器
if (success && useCustomAudioOutput) {
// 创建SimpleAudioPlayer实例
val customPlayer = SimpleAudioPlayer(context)
// 获取并设置音频输出流 // 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器
val outputStream = customPlayer.getAudioOutputStream() if (success && useCustomAudioOutput) {
if (outputStream != null) { // 创建SimpleAudioPlayer实例
azureTtsHelper.setCustomAudioOutputStream(outputStream) val customPlayer = SimpleAudioPlayer(context)
// 获取并设置音频输出流
val outputStream = customPlayer.getAudioOutputStream()
if (outputStream != null) {
azureTtsHelper.setCustomAudioOutputStream(outputStream)
}
} }
// 确保设置了TTS事件监听器
setupTtsEventListener()
result.success(success)
} catch (e: Exception) {
result.error("INITIALIZATION_ERROR", "TTS初始化失败: ${e.message}", null)
} }
result.success(success)
} }
"setVoice" -> { "setVoice" -> {
val voiceName = call.argument<String>("voiceName") ?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) val voiceName = call.argument<String>("voiceName") ?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null)
@ -315,18 +350,28 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
"speakText" -> { "speakText" -> {
val text = call.argument<String>("text") ?: return result.error("INVALID_ARGUMENTS", "文本不能为空", null) val text = call.argument<String>("text") ?: return result.error("INVALID_ARGUMENTS", "文本不能为空", null)
// 直接传递参数给speakText方法 // 直接调用speakOnce方法
val success = azureTtsHelper.speakText(text) try {
result.success(success) val success = azureTtsHelper.speakOnce(text)
result.success(success)
} catch (e: Exception) {
result.error("SPEAK_ERROR", "语音合成失败: ${e.message}", null)
}
} }
"stopSpeaking" -> { "stopSpeaking" -> {
result.success(azureTtsHelper.stopSpeaking()) // 直接调用stop方法
try {
val success = azureTtsHelper.stop()
result.success(success)
} catch (e: Exception) {
result.error("STOP_ERROR", "停止语音合成失败: ${e.message}", null)
}
} }
"isSpeaking" -> { "isSpeaking" -> {
result.success(azureTtsHelper.isSpeaking()) result.success(azureTtsHelper.isSpeaking())
} }
"dispose" -> { "dispose" -> {
azureTtsHelper.dispose() azureTtsHelper.release()
result.success(true) result.success(true)
} }
else -> { else -> {
@ -334,37 +379,6 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
} }
} }
} }
/**
* 创建TTS回调
*/
private fun createTtsCallback(): AzureTtsHelper.TtsCallback {
return object : AzureTtsHelper.TtsCallback {
override fun onSynthesisStarted() {
sendTtsEvent(mapOf(
"type" to "synthesis_started"
))
}
override fun onSynthesizing() {
sendTtsEvent(mapOf(
"type" to "synthesizing"
))
}
override fun onSynthesisCompleted() {
sendTtsEvent(mapOf(
"type" to "synthesis_completed"
))
}
override fun onSynthesisCanceled() {
sendTtsEvent(mapOf(
"type" to "synthesis_canceled"
))
}
}
}
} }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
@ -374,49 +388,11 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
ttsEventChannel.setStreamHandler(null) ttsEventChannel.setStreamHandler(null)
try { try {
// 取消所有协程
job.cancel()
// 取消BLE回调注册
if (isRegisteredToBle) {
BleService.removeCallback(this)
isRegisteredToBle = false
}
// 释放资源 // 释放资源
azureTtsHelper.dispose() azureTtsHelper.release()
azureAsrHelper.dispose() azureAsrHelper.dispose()
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(tag, "释放资源失败: ${e.message}") FileLogger.e(tag, "释放资源失败: ${e.message}")
} }
} }
override fun onScanResult(devices: List<Map<String, Any>>) {
}
override fun onConnectionStateChanged(state: Int) {
// 可选:处理连接状态变化
if (state == BleConst.STATE_CONNECTED) {
FileLogger.d(tag, "Ble设备已连接,准备好语音交互")
} else if (state == BleConst.STATE_DISCONNECTED) {
FileLogger.d(tag, "Ble设备已断开,停止语音服务")
// 设备断开连接,停止语音服务
// stopListening()
}
}
override fun onAudioDataReceived(data: ByteArray) {
// FileLogger.d(tag, "AzureSpeechPlugin onAudioDataReceived, data: ${data.size}")
azureAsrHelper.pushAudioData(data)
}
override fun onWakeupSignalReceived() {
}
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {
}
} }

289
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -4,10 +4,14 @@ import android.content.Context
import com.microsoft.cognitiveservices.speech.* import com.microsoft.cognitiveservices.speech.*
import com.microsoft.cognitiveservices.speech.audio.* import com.microsoft.cognitiveservices.speech.audio.*
import com.yunqiinnovation.azure_speech.utils.FileLogger import com.yunqiinnovation.azure_speech.utils.FileLogger
import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.speech.tts.AudioDataListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import kotlin.coroutines.CoroutineContext
/** /**
* Azure TTS Helper * Azure TTS Helper
@ -15,18 +19,13 @@ import kotlin.coroutines.CoroutineContext
* 基于微软Azure语音服务的TTS实现 * 基于微软Azure语音服务的TTS实现
* 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis * 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis
*/ */
class AzureTtsHelper(private val context: Context) : CoroutineScope { class AzureTtsHelper(private val context: Context) : ITtsService {
companion object { companion object {
private const val TAG = "AzureTtsHelper" private const val TAG = "AzureTtsHelper"
private const val DEFAULT_LANGUAGE = "zh-CN" private const val DEFAULT_LANGUAGE = "zh-CN"
private const val DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural" private const val DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural"
} }
// 协程上下文
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
// Azure语音服务配置 // Azure语音服务配置
private var speechConfig: SpeechConfig? = null private var speechConfig: SpeechConfig? = null
private var synthesizer: SpeechSynthesizer? = null private var synthesizer: SpeechSynthesizer? = null
@ -39,8 +38,9 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
private var currentPitch = "0%" private var currentPitch = "0%"
private var currentVolume = "100%" private var currentVolume = "100%"
// 全局回调 // 事件监听器列表
private var ttsCallback: TtsCallback? = null private val eventListeners = mutableListOf<TtsEventListener>()
private val audioDataListeners = mutableListOf<AudioDataListener>()
// 自定义音频输出流 // 自定义音频输出流
private var customAudioOutputStream: PushAudioOutputStream? = null private var customAudioOutputStream: PushAudioOutputStream? = null
@ -48,50 +48,28 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
// 流式文本处理的缓冲区 // 流式文本处理的缓冲区
private val streamBuffer = StringBuilder() private val streamBuffer = StringBuilder()
private var lastSpeakTime = 0L private var lastSpeakTime = 0L
/**
* 简化的TTS回调接口
*/
interface TtsCallback {
/**
* 合成开始
*/
fun onSynthesisStarted() {}
/**
* 合成完成
*/
fun onSynthesisCompleted() {}
/**
* 合成取消
*/
fun onSynthesisCanceled() {}
}
/** /**
* 初始化TTS引擎 * 初始化TTS引擎
* *
* @param subscriptionKey Azure语音服务订阅密钥 * @param ttsAppId 未使用,保留为空即可
* @param region Azure语音服务区域 * @param ttsAppToken Azure语音服务订阅密钥
* @param callback 可选,全局TTS回调接口 * @param ttsResource Azure语音服务区域
* @param language 语言代码,如"zh-CN"
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
fun initialize( override fun initialize(
subscriptionKey: String, ttsAppId: String,
region: String, ttsAppToken: String,
callback: TtsCallback? = null ttsResource: String,
language: String
): Boolean { ): Boolean {
try { try {
// 释放之前的资源 // 释放之前的资源
dispose() dispose()
// 存储全局回调
ttsCallback = callback
// 创建语音配置 // 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region) speechConfig = SpeechConfig.fromSubscription(ttsAppToken, ttsResource)
// speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3)
speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm) speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm)
speechConfig?.setSpeechSynthesisVoiceName(currentVoice) speechConfig?.setSpeechSynthesisVoiceName(currentVoice)
@ -111,7 +89,7 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
setupEventListeners() setupEventListeners()
isInitialized = true isInitialized = true
FileLogger.d(TAG, "TTS引擎初始化成功: 区域=$region") FileLogger.d(TAG, "TTS引擎初始化成功: 区域=$ttsResource")
return true return true
} catch (e: Exception) { } catch (e: Exception) {
@ -144,12 +122,51 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
} }
/** /**
* 设置TTS回调 * 添加TTS事件监听器
* */
* @param callback TTS回调接口 override fun addListener(listener: TtsEventListener) {
if (!eventListeners.contains(listener)) {
eventListeners.add(listener)
}
}
/**
* 移除TTS事件监听器
*/ */
fun setTtsCallback(callback: TtsCallback?) { override fun removeListener(listener: TtsEventListener) {
ttsCallback = callback eventListeners.remove(listener)
}
/**
* 添加音频数据监听器
*/
override fun addAudioDataListener(listener: AudioDataListener) {
if (!audioDataListeners.contains(listener)) {
audioDataListeners.add(listener)
}
}
/**
* 移除音频数据监听器
*/
override fun removeAudioDataListener(listener: AudioDataListener) {
audioDataListeners.remove(listener)
}
/**
* 触发事件通知
*/
private fun notifyEvent(eventType: TtsEventType, params: Map<String, Any> = emptyMap()) {
val event = TtsEvent(eventType, params)
// 复制列表防止并发修改异常
val listeners = ArrayList(eventListeners)
for (listener in listeners) {
try {
listener.onEvent(event)
} catch (e: Exception) {
FileLogger.e(TAG, "通知事件异常: ${e.message}")
}
}
} }
/** /**
@ -159,28 +176,29 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
synthesizer?.apply { synthesizer?.apply {
// 合成开始事件 // 合成开始事件
SynthesisStarted?.addEventListener { _, _ -> SynthesisStarted?.addEventListener { _, _ ->
ttsCallback?.onSynthesisStarted() notifyEvent(TtsEventType.SYNTHESIS_STARTED)
} }
// 合成完成事件 // 合成完成事件
SynthesisCompleted?.addEventListener { _, _ -> SynthesisCompleted?.addEventListener { _, _ ->
isSpeaking = false isSpeaking = false
ttsCallback?.onSynthesisCompleted() notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
} }
// 合成取消事件 // 合成取消事件
SynthesisCanceled?.addEventListener { _, _ -> SynthesisCanceled?.addEventListener { _, eventArgs ->
FileLogger.d(TAG, "语音合成取消") FileLogger.d(TAG, "语音合成取消")
isSpeaking = false isSpeaking = false
ttsCallback?.onSynthesisCanceled()
notifyEvent(TtsEventType.SYNTHESIS_CANCELED)
} }
} }
} }
/** /**
* 设置语音 * 设置语音角色
*/ */
fun setVoice(voiceName: String): Boolean { override fun setVoice(voiceName: String): Boolean {
if (!isInitialized) return false if (!isInitialized) return false
try { try {
@ -190,6 +208,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
return true return true
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "设置语音失败: ${e.message}") FileLogger.e(TAG, "设置语音失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "VOICE_SET_FAILED",
"errorMessage" to "设置语音失败: ${e.message}"
))
return false return false
} }
} }
@ -215,6 +237,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
setupEventListeners() setupEventListeners()
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "重新创建合成器失败: ${e.message}") FileLogger.e(TAG, "重新创建合成器失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "RECREATE_FAILED",
"errorMessage" to "重新创建合成器失败: ${e.message}"
))
} }
} }
@ -231,6 +257,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
return true return true
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "设置语音参数失败: ${e.message}") FileLogger.e(TAG, "设置语音参数失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "PARAMS_SET_FAILED",
"errorMessage" to "设置语音参数失败: ${e.message}"
))
return false return false
} }
} }
@ -246,50 +276,52 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
* 生成SSML * 生成SSML
*/ */
private fun generateSsml(rawText: String): String { private fun generateSsml(rawText: String): String {
// 1. 定义要静音的符号和表情符号列表 // 1. 定义要静音的符号和表情符号列表
val symbolsToMute = listOf( val symbolsToMute = listOf(
"#", "*", "@", "%", "^", "&", "#", "*", "@", "%", "^", "&",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉" "😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
) )
// 2. 转义 XML 保留字符 // 2. 转义 XML 保留字符
val escapedText = rawText val escapedText = rawText
.replace("&", "&amp;") .replace("&", "&amp;")
.replace("<", "&lt;") .replace("<", "&lt;")
.replace(">", "&gt;") .replace(">", "&gt;")
// 3. 静音处理特殊符号和表情符号 // 3. 静音处理特殊符号和表情符号
// 使用空白替换法,直接将符号替换为空格 // 使用空白替换法,直接将符号替换为空格
var processedText = escapedText var processedText = escapedText
symbolsToMute.forEach { sym -> symbolsToMute.forEach { sym ->
processedText = processedText.replace(sym, "") processedText = processedText.replace(sym, "")
} }
// 4. 构造简化的SSML文档,减少嵌套层级 // 4. 构造简化的SSML文档,减少嵌套层级
return """ return """
<speak version="1.0" <speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis" xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts" xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN"> xml:lang="zh-CN">
<voice name="$currentVoice"> <voice name="$currentVoice">
<mstts:express-as style="cheerful"> <mstts:express-as style="cheerful">
<prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume"> <prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume">
<say-as interpret-as="text">$processedText</say-as> <say-as interpret-as="text">$processedText</say-as>
</prosody> </prosody>
</mstts:express-as> </mstts:express-as>
</voice> </voice>
</speak> </speak>
""".trimIndent() """.trimIndent()
} }
/** /**
* 合成文本为语音 * 单次播放文本(非流式)
*
* @param text 要合成的文本
* @return 是否成功开始合成
*/ */
fun speakText(text: String): Boolean { override fun speakOnce(text: String): Boolean {
if (!isInitialized) { if (!isInitialized) {
FileLogger.e(TAG, "TTS引擎未初始化")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
return false return false
} }
@ -299,38 +331,47 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
isSpeaking = true isSpeaking = true
// 使用纯异步方式 // 直接调用SpeakSsmlAsync,SDK内部已经是异步的
synthesizer?.SpeakSsmlAsync(ssml) synthesizer?.SpeakSsmlAsync(ssml)
// 返回true表示已开始合成请求
return true return true
} catch (e: Exception) { } catch (e: Exception) {
isSpeaking = false isSpeaking = false
FileLogger.e(TAG, "语音合成异常: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "SYNTHESIS_ERROR",
"errorMessage" to "语音合成异常: ${e.message}"
))
return false return false
} }
} }
/** /**
* 处理流式文本并在遇到标点符号时播放 * 流式合成文本
*
* @param text 收到的文本流片段
* @return 是否成功处理
*/ */
fun speakStream(text: String): Boolean { override fun speakStream(text: String): Boolean {
if (!isInitialized || text.isBlank()) { if (!isInitialized || text.isBlank()) {
if (!isInitialized) {
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
}
return false return false
} }
try { try {
// 添加新文本到缓冲区 // 添加新文本到缓冲区
streamBuffer.append(text) streamBuffer.append(text)
// 增加500ms防抖逻辑 // 增加500ms防抖逻辑
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
if (currentTime - lastSpeakTime < 600) { if (currentTime - lastSpeakTime < 600) {
return true return true
} }
lastSpeakTime = currentTime lastSpeakTime = currentTime
val currentText = streamBuffer.toString() val currentText = streamBuffer.toString()
// 定义标点符号列表 // 定义标点符号列表
@ -354,24 +395,30 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
streamBuffer.delete(0, lastPunctuationIndex + 1) streamBuffer.delete(0, lastPunctuationIndex + 1)
// 播放提取的文本 // 播放提取的文本
return speakText(textToSpeak) return speakOnce(textToSpeak)
} }
// 如果没有找到标点符号,则等待更多文本 // 如果没有找到标点符号,则等待更多文本
return true return true
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "流式语音合成失败: ${e.message}") FileLogger.e(TAG, "流式语音合成失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "STREAM_FAILED",
"errorMessage" to "流式语音合成失败: ${e.message}"
))
return false return false
} }
} }
/** /**
* 播放剩余的流式文本 * 播放剩余的流式文本
*
* @return 是否成功播放剩余文本
*/ */
fun flushStream(): Boolean { override fun flushStream(): Boolean {
if (!isInitialized) { if (!isInitialized) {
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
return false return false
} }
@ -388,9 +435,13 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
} }
// 播放剩余文本 // 播放剩余文本
return speakText(remainingText) return speakOnce(remainingText)
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "刷新流式文本失败: ${e.message}") FileLogger.e(TAG, "刷新流式文本失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "FLUSH_FAILED",
"errorMessage" to "刷新流式文本失败: ${e.message}"
))
return false return false
} }
} }
@ -398,7 +449,7 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
/** /**
* 停止当前语音合成 * 停止当前语音合成
*/ */
fun stopSpeaking(): Boolean { override fun stop(): Boolean {
if (!isInitialized) return false if (!isInitialized) return false
try { try {
@ -407,6 +458,11 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
isSpeaking = false isSpeaking = false
return true return true
} catch (e: Exception) { } catch (e: Exception) {
FileLogger.e(TAG, "停止语音合成失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "STOP_FAILED",
"errorMessage" to "停止语音合成失败: ${e.message}"
))
return false return false
} }
} }
@ -414,11 +470,25 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
/** /**
* 释放资源 * 释放资源
*/ */
fun dispose() { override fun release() {
dispose()
}
/**
* 释放资源(内部方法)
*/
private fun dispose() {
try { try {
stopSpeaking() // 停止播放
stop()
// 关闭合成器
synthesizer?.close() synthesizer?.close()
synthesizer = null
// 关闭配置
speechConfig?.close() speechConfig?.close()
speechConfig = null
// 关闭自定义音频输出流 // 关闭自定义音频输出流
try { try {
@ -428,15 +498,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
// 忽略关闭异常 // 忽略关闭异常
} }
// 清理引用
ttsCallback = null
// 清空流缓冲区 // 清空流缓冲区
streamBuffer.clear() streamBuffer.clear()
// 取消所有协程 // 重置状态
job.cancel()
isInitialized = false isInitialized = false
isSpeaking = false isSpeaking = false
} catch (e: Exception) { } catch (e: Exception) {

26
local_plugins/bytedance_speech/android/build.gradle.kts

@ -3,7 +3,24 @@ version = "1.0.0"
plugins { plugins {
id("com.android.library") id("com.android.library")
id("org.jetbrains.kotlin.android") kotlin("android")
}
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.1.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}
}
repositories {
google()
mavenCentral()
} }
android { android {
@ -22,15 +39,18 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
languageVersion = "1.7"
apiVersion = "1.7"
} }
} }
dependencies { dependencies {
implementation(kotlin("stdlib-jdk7"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("com.squareup.okhttp3:okhttp:4.11.0") implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("com.google.code.gson:gson:2.10.1") implementation("com.google.code.gson:gson:2.10.1")
implementation(project(":speech"))
} }

2
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceASR.kt

@ -324,7 +324,7 @@ class BytedanceASR(
} }
override fun onMessage(webSocket: WebSocket, bytes: ByteString) { override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
Log.d(TAG, "收到二进制消息") // Log.d(TAG, "收到二进制消息")
// 在实际实现中解析二进制消息 // 在实际实现中解析二进制消息
} }

329
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt

@ -0,0 +1,329 @@
package com.deep_voice.bytedance_speech
import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
import android.os.Build
import android.util.Log
import com.deep_voice.speech.tts.AudioDataListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.CancellationException
/**
* 字节跳动语音合成音频播放器
*
* 使用AudioTrack播放PCM格式音频流,基于Kotlin协程实现异步处理
*/
class BytedanceAudioPlayer : AudioDataListener, CoroutineScope {
companion object {
private const val TAG = "BytedanceAudioPlayer"
private const val SAMPLE_RATE = 24000 // 样本率
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_MONO // 单声道
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT // 16位PCM
private const val IDLE_TIMEOUT_MS = 800L // 无数据超时时间
}
// 协程相关
private val job = SupervisorJob()
override val coroutineContext = Dispatchers.IO + job
// 音频处理相关
private var audioTrack: AudioTrack? = null
private val isPlaying = AtomicBoolean(false)
private val isPaused = AtomicBoolean(false)
// 播放状态
private val playStarted = AtomicBoolean(false)
private val completionReported = AtomicBoolean(false)
// 使用简单的并发队列,保证线程安全
private val audioDataQueue = ConcurrentLinkedQueue<ByteArray>()
// 播放状态监听
private var onPlayStarted: (() -> Unit)? = null
private var onPlayCompleted: (() -> Unit)? = null
private var onError: ((String) -> Unit)? = null
// 处理协程
private var processingJob: Job? = null
private var lastDataTime = 0L
init {
Log.d(TAG, "BytedanceAudioPlayer初始化")
}
/**
* 开始播放
*/
fun start() {
if (isPlaying.getAndSet(true)) return
isPaused.set(false)
playStarted.set(false)
completionReported.set(false)
lastDataTime = System.currentTimeMillis()
processingJob = launch {
try {
initAudioTrack()
// 处理队列中的音频数据
while (isActive && isPlaying.get()) {
processQueuedAudio()
// 检查是否播放完成
checkPlaybackCompletion()
// 队列为空时短暂延迟
if (audioDataQueue.isEmpty()) {
delay(10)
}
}
} catch (e: CancellationException) {
// 协程被取消,正常行为
} catch (e: Exception) {
Log.e(TAG, "播放错误: ${e.message}")
onError?.invoke("播放错误: ${e.message}")
stopInternal(false)
}
}
}
/**
* 暂停播放
*/
fun pause() {
if (!isPlaying.get() || isPaused.getAndSet(true)) return
audioTrack?.pause()
}
/**
* 恢复播放
*/
fun resume() {
if (!isPlaying.get() || !isPaused.getAndSet(false)) return
audioTrack?.play()
}
/**
* 停止播放
*/
fun stop() {
Log.d(TAG, "停止播放")
stopInternal(true)
}
/**
* 内部停止处理
*/
private fun stopInternal(reportCompletion: Boolean) {
if (!isPlaying.getAndSet(false)) return
isPaused.set(false)
processingJob?.cancel()
audioTrack?.stop()
// 不在stopInternal中释放资源,只停止播放
// 清空队列
audioDataQueue.clear()
// 播放完成通知
if (reportCompletion && playStarted.get() && !completionReported.getAndSet(true)) {
launch(Dispatchers.Main) {
onPlayCompleted?.invoke()
}
}
}
/**
* 释放资源
*/
fun release() {
stopInternal(false)
// 在release方法中释放AudioTrack资源
audioTrack?.release()
audioTrack = null
// 释放协程资源
job.cancel()
}
/**
* 设置播放开始回调
*/
fun setOnPlayStarted(listener: () -> Unit) {
onPlayStarted = listener
}
/**
* 设置播放完成回调
*/
fun setOnPlayCompleted(listener: () -> Unit) {
onPlayCompleted = listener
}
/**
* 设置错误回调
*/
fun setOnError(listener: (String) -> Unit) {
onError = listener
}
/**
* 接收音频数据(实现AudioDataListener接口)
*/
override fun onAudioData(data: ByteArray) {
if (data.isEmpty()) return
lastDataTime = System.currentTimeMillis()
if (!isPlaying.get()) {
start()
}
// 添加到队列 - 简单有效,保证FIFO顺序
audioDataQueue.add(data.copyOf())
}
/**
* 检查是否正在播放
*/
fun isPlaying(): Boolean = isPlaying.get() && !isPaused.get()
/**
* 处理队列中的音频数据
*/
private suspend fun processQueuedAudio() {
if (isPaused.get() || !isPlaying.get() || !isActive) return
// 获取并处理队列中的数据
val data = audioDataQueue.poll() ?: return
// 写入音频数据
val result = audioTrack?.write(data, 0, data.size) ?: 0
if (result > 0) {
// 标记播放开始
if (!playStarted.getAndSet(true)) {
withContext(Dispatchers.Main) {
onPlayStarted?.invoke()
}
}
} else if (result < 0) {
// 处理错误
handleAudioTrackError(result)
}
}
/**
* 检查播放是否完成(超时无数据)
*/
private suspend fun checkPlaybackCompletion() {
val currentTime = System.currentTimeMillis()
// 超时判断 - 队列为空且超过超时时间
if (playStarted.get() && audioDataQueue.isEmpty() &&
currentTime - lastDataTime > IDLE_TIMEOUT_MS && isPlaying.get()) {
// 播放完成
if (!completionReported.getAndSet(true)) {
withContext(Dispatchers.Main) {
onPlayCompleted?.invoke()
}
stopInternal(false) // 已报告,不需要再次报告
}
}
}
/**
* 处理AudioTrack错误
*/
private fun handleAudioTrackError(result: Int) {
val errorMsg = when(result) {
AudioTrack.ERROR_INVALID_OPERATION -> "无效操作"
AudioTrack.ERROR_BAD_VALUE -> "参数错误"
AudioTrack.ERROR_DEAD_OBJECT -> "对象已销毁"
else -> "未知错误"
}
// 对象已销毁时尝试重建
if (result == AudioTrack.ERROR_DEAD_OBJECT) {
try {
audioTrack?.release()
initAudioTrack()
} catch (e: Exception) {
Log.e(TAG, "重建AudioTrack失败: ${e.message}")
throw e
}
} else {
Log.e(TAG, "AudioTrack错误: $errorMsg")
}
}
/**
* 初始化AudioTrack
*/
private fun initAudioTrack() {
val minBufferSize = AudioTrack.getMinBufferSize(
SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT
)
if (minBufferSize == AudioTrack.ERROR || minBufferSize == AudioTrack.ERROR_BAD_VALUE) {
throw IllegalStateException("无法获取有效的音频缓冲区大小")
}
// 使用较大的缓冲区提高稳定性
val bufferSize = minBufferSize * 4
// 创建AudioTrack
audioTrack = if (Build.VERSION.SDK_INT >= 23) { // Android M (6.0)
AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAudioFormat(
AudioFormat.Builder()
.setEncoding(AUDIO_FORMAT)
.setSampleRate(SAMPLE_RATE)
.setChannelMask(CHANNEL_CONFIG)
.build()
)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.build()
} else {
@Suppress("DEPRECATION")
AudioTrack(
AudioManager.STREAM_MUSIC,
SAMPLE_RATE,
CHANNEL_CONFIG,
AUDIO_FORMAT,
bufferSize,
AudioTrack.MODE_STREAM
)
}
// 检查初始化状态
if (audioTrack?.state != AudioTrack.STATE_INITIALIZED) {
throw IllegalStateException("AudioTrack初始化失败")
}
// 开始播放
audioTrack?.play()
}
}

154
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt

@ -16,6 +16,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.speech.tts.AudioDataListener
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
/** /**
@ -168,6 +172,7 @@ class BytedanceSpeechPlugin : FlutterPlugin {
"disposeTTS" -> { "disposeTTS" -> {
disposeTTS(result) disposeTTS(result)
} }
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@ -235,33 +240,45 @@ class BytedanceSpeechPlugin : FlutterPlugin {
bytedanceTTS?.release() bytedanceTTS?.release()
// 创建新的TTS实例 // 创建新的TTS实例
bytedanceTTS = BytedanceTTS( bytedanceTTS = BytedanceTTS(context)
context = context,
// 音频数据回调 // 添加事件监听器
audioCallback = { audioData -> bytedanceTTS?.addListener(object : TtsEventListener {
sendTTSAudioData(audioData) override fun onEvent(event: TtsEvent) {
}, when (event.type) {
// 完成回调 TtsEventType.SYNTHESIS_STARTED -> {
completionCallback = { sendTTSEvent("started", null)
sendTTSEvent("completed", null) }
}, TtsEventType.SYNTHESIS_COMPLETED -> {
// 错误回调 sendTTSEvent("completed", null)
errorCallback = { code, message -> }
val params = HashMap<String, Any>() TtsEventType.SYNTHESIS_CANCELED -> {
params["code"] = code sendTTSEvent("canceled", null)
params["message"] = message }
sendTTSEvent("error", params) TtsEventType.ERROR -> {
}, val params = HashMap<String, Any>()
// 状态回调 params["code"] = event.params["errorCode"] ?: "UNKNOWN_ERROR"
statusCallback = { status -> params["message"] = event.params["errorMessage"] ?: "未知错误"
val params = HashMap<String, Any>() sendTTSEvent("error", params)
params["status"] = status }
sendTTSEvent("status", params) }
} }
) })
// 添加音频数据监听器
bytedanceTTS?.addAudioDataListener(object : AudioDataListener {
override fun onAudioData(data: ByteArray) {
sendTTSAudioData(data)
}
})
// 初始化TTS // 初始化TTS
val success = bytedanceTTS?.initialize(ttsAppId, ttsAppToken, language) ?: false val success = bytedanceTTS?.initialize(
ttsAppId = ttsAppId,
ttsAppToken = ttsAppToken,
ttsResource = "", // 火山TTS不使用resource
language = language
) ?: false
if (success) { if (success) {
result.success(true) result.success(true)
@ -314,18 +331,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return return
} }
scope.launch { try {
try { val success = bytedanceTTS?.speakOnce(text) ?: false
val success = bytedanceTTS?.speakOnce(text) ?: false result.success(success)
result.success(success) } catch (e: Exception) {
} catch (e: Exception) { Log.e(TAG, "合成文本异常: ${e.message}", e)
Log.e(TAG, "合成文本异常: ${e.message}", e) result.error(
result.error( "SPEAK_EXCEPTION",
"SPEAK_EXCEPTION", "合成文本异常: ${e.message}",
"合成文本异常: ${e.message}", null
null )
)
}
} }
} }
@ -338,18 +353,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return return
} }
scope.launch { try {
try { val success = bytedanceTTS?.speakStream(text) ?: false
val success = bytedanceTTS?.speakStream(text) ?: false result.success(success)
result.success(success) } catch (e: Exception) {
} catch (e: Exception) { Log.e(TAG, "流式合成异常: ${e.message}", e)
Log.e(TAG, "流式合成异常: ${e.message}", e) result.error(
result.error( "STREAM_EXCEPTION",
"STREAM_EXCEPTION", "流式合成异常: ${e.message}",
"流式合成异常: ${e.message}", null
null )
)
}
} }
} }
@ -362,18 +375,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return return
} }
scope.launch { try {
try { val success = bytedanceTTS?.flushStream() ?: false
val success = bytedanceTTS?.flushStream() ?: false result.success(success)
result.success(success) } catch (e: Exception) {
} catch (e: Exception) { Log.e(TAG, "刷新流异常: ${e.message}", e)
Log.e(TAG, "刷新流异常: ${e.message}", e) result.error(
result.error( "FLUSH_EXCEPTION",
"FLUSH_EXCEPTION", "刷新流异常: ${e.message}",
"刷新流异常: ${e.message}", null
null )
)
}
} }
} }
@ -386,18 +397,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return return
} }
scope.launch { try {
try { val success = bytedanceTTS?.stop() ?: true
bytedanceTTS?.stop() result.success(success)
result.success(true) } catch (e: Exception) {
} catch (e: Exception) { Log.e(TAG, "停止TTS异常: ${e.message}", e)
Log.e(TAG, "停止TTS异常: ${e.message}", e) result.error(
result.error( "STOP_EXCEPTION",
"STOP_EXCEPTION", "停止TTS异常: ${e.message}",
"停止TTS异常: ${e.message}", null
null )
)
}
} }
} }
@ -418,6 +427,7 @@ class BytedanceSpeechPlugin : FlutterPlugin {
) )
} }
} }
/** /**
* 发送TTS事件到Flutter端 * 发送TTS事件到Flutter端

650
local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt

@ -2,8 +2,19 @@ package com.deep_voice.bytedance_speech
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.deep_voice.speech.tts.AudioDataListener
import com.deep_voice.speech.tts.ITtsService
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.CancellationException
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -11,24 +22,16 @@ import okhttp3.WebSocket
import okhttp3.WebSocketListener import okhttp3.WebSocketListener
import okio.ByteString import okio.ByteString
import org.json.JSONObject import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.GZIPInputStream import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.GZIPOutputStream
/** /**
* 火山引擎大模型语音合成实现 * 火山引擎大模型语音合成实现
* *
* 基于WebSocket协议实现双向流式TTS * 基于WebSocket协议实现双向流式TTS
*/ */
class BytedanceTTS( class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
private val context: Context,
private val audioCallback: (ByteArray) -> Unit,
private val completionCallback: () -> Unit,
private val errorCallback: (String, String) -> Unit,
private val statusCallback: (String) -> Unit
) {
companion object { companion object {
private const val TAG = "BytedanceTTS" private const val TAG = "BytedanceTTS"
@ -80,8 +83,23 @@ class BytedanceTTS(
private const val STATUS_SPEAKING = "speaking" private const val STATUS_SPEAKING = "speaking"
private const val STATUS_STOPPED = "stopped" private const val STATUS_STOPPED = "stopped"
private const val STATUS_ERROR = "error" private const val STATUS_ERROR = "error"
// 连接控制常量
private const val MAX_CONNECTION_ATTEMPTS = 3
private const val CONNECTION_TIMEOUT_MS = 10000 // 10秒
} }
// 协程相关
private val job = Job()
override val coroutineContext = Dispatchers.IO + job
// 事件监听器列表
private val eventListeners = mutableListOf<TtsEventListener>()
private val audioDataListeners = mutableListOf<AudioDataListener>()
// 内部音频播放器
private val audioPlayer = BytedanceAudioPlayer()
// WebSocket连接 // WebSocket连接
private var webSocket: WebSocket? = null private var webSocket: WebSocket? = null
private val client: OkHttpClient private val client: OkHttpClient
@ -94,8 +112,8 @@ class BytedanceTTS(
// TTS配置参数 // TTS配置参数
private var appId: String = "" private var appId: String = ""
private var accessToken: String = "" private var accessToken: String = ""
private var speaker: String = "zh_female_shuangkuaisisi_moon_bigtts" // 默认语音,使用Java示例的音色 private var speaker: String = "zh_female_shuangkuaisisi_emo_v2_mars_bigtts" // 默认语音,使用Java示例的音色
private var format: String = "mp3" // 使用mp3格式 private var format: String = "pcm" // 使用mp3格式
private var sampleRate: Int = 24000 // 使用24kHz采样率 private var sampleRate: Int = 24000 // 使用24kHz采样率
private var enableTimestamp: Boolean = false private var enableTimestamp: Boolean = false
private var language: String = "zh-CN" private var language: String = "zh-CN"
@ -103,6 +121,16 @@ class BytedanceTTS(
// 流式处理状态 // 流式处理状态
private var isStreamMode = false private var isStreamMode = false
private var currentStatus = STATUS_STOPPED private var currentStatus = STATUS_STOPPED
private var isSpeaking = false
// 连接相关状态
private var connectionAttempts = 0
private var isConnecting = AtomicBoolean(false)
private var lastConnectionAttemptTime = 0L
// 在类级别添加文本流通道
private val textChannel = Channel<String>(Channel.BUFFERED)
private var textProcessingJob: Job? = null
init { init {
// 配置OkHttp客户端 // 配置OkHttp客户端
@ -113,61 +141,161 @@ class BytedanceTTS(
.build() .build()
// 生成会话ID // 生成会话ID
sessionId = UUID.randomUUID().toString().replace("-", "") // sessionId = UUID.randomUUID().toString().replace("-", "")
Log.d(TAG, "BytedanceTTS实例化完成,会话ID: $sessionId") Log.d(TAG, "BytedanceTTS实例化完成!")
// 初始化音频播放器回调
initAudioPlayerCallbacks()
// 启动文本处理协程
startTextProcessing()
}
/**
* 初始化音频播放器回调
*/
private fun initAudioPlayerCallbacks() {
// 添加播放器作为音频数据监听器
addAudioDataListener(audioPlayer)
audioPlayer.setOnPlayStarted {
Log.d(TAG, "播放开始")
isSpeaking = true
updateStatus(STATUS_SPEAKING)
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
}
// 设置播放完成回调
audioPlayer.setOnPlayCompleted {
Log.d(TAG, "播放结束")
isSpeaking = false
updateStatus(if (isStreamMode) STATUS_READY else STATUS_STOPPED)
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
}
// 设置错误回调
audioPlayer.setOnError { errorMsg ->
isSpeaking = false
updateStatus(STATUS_ERROR)
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "PLAYER_ERROR", "errorMessage" to errorMsg))
}
}
/**
* 启动文本处理协程
*/
private fun startTextProcessing() {
textProcessingJob?.cancel()
textProcessingJob = launch {
try {
for (text in textChannel) {
try {
sendStreamTextRequest(text)
} catch (e: Exception) {
Log.e(TAG, "文本处理错误: ${e.message}")
}
}
} catch (e: CancellationException) {
// 协程被取消,正常情况
} catch (e: Exception) {
Log.e(TAG, "文本处理异常: ${e.message}")
}
}
}
/**
* 停止语音合成
*/
override fun stop(): Boolean {
try {
audioPlayer.stop()
textProcessingJob?.cancel()
launch {
if (isSessionStarted) {
finishSession()
}
isSpeaking = false // 显式设置,因为这是强制停止
updateStatus(STATUS_STOPPED)
}
return true
} catch (e: Exception) {
Log.e(TAG, "停止合成失败: ${e.message}")
return false
}
} }
/** /**
* 初始化火山语音服务 * 初始化火山语音服务
*/ */
suspend fun initialize(ttsAppId: String, ttsAppToken: String, language: String = "zh-CN"): Boolean = withContext(Dispatchers.IO) { override fun initialize(
ttsAppId: String,
ttsAppToken: String,
ttsResource: String,
language: String
): Boolean {
try { try {
updateStatus(STATUS_INITIALIZING) updateStatus(STATUS_INITIALIZING)
// 设置必要参数 // 设置基本参数
this@BytedanceTTS.appId = ttsAppId // 火山引擎中的应用ID this.appId = ttsAppId
this@BytedanceTTS.accessToken = ttsAppToken // 火山引擎访问令牌 this.accessToken = ttsAppToken
this@BytedanceTTS.language = language this.language = language
// 释放旧连接
release()
// 建立新连接
connect()
// 等待连接和会话建立 // 重置连接状态
var attempts = 0 connectionAttempts = 0
while (!isSessionStarted && attempts < 10) { isConnecting.set(false)
delay(500) // 增加等待时间 lastConnectionAttemptTime = 0
attempts++
Log.d(TAG, "等待会话建立...尝试 $attempts/10")
}
if (!isSessionStarted) { // 释放旧连接
Log.e(TAG, "初始化超时: 无法建立会话") webSocket?.cancel()
errorCallback("INIT_TIMEOUT", "初始化超时: 无法在5秒内建立会话") webSocket = null
return@withContext false isConnected = false
} isSessionStarted = false
Log.d(TAG, "字节跳动TTS初始化成功,会话ID: $sessionId") return true
true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "初始化失败: ${e.message}", e) Log.e(TAG, "初始化失败: ${e.message}")
errorCallback("INIT_ERROR", e.message ?: "初始化失败")
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
false return false
} }
} }
/**
* 重置服务状态
*/
fun reset() {
stop()
textProcessingJob?.cancel()
connectionAttempts = 0
isConnecting.set(false)
lastConnectionAttemptTime = 0
webSocket?.cancel()
webSocket = null
isConnected = false
isSessionStarted = false
isSpeaking = false
updateStatus(STATUS_STOPPED)
startTextProcessing()
}
/** /**
* 设置语音角色 * 设置语音角色
*/ */
fun setVoice(voiceName: String): Boolean { override fun setVoice(voiceName: String): Boolean {
try { try {
speaker = voiceName speaker = voiceName
return true return true
} catch (e: Exception) { } catch (e: Exception) {
errorCallback("VOICE_ERROR", e.message ?: "设置语音角色失败") val errorMsg = e.message ?: "设置语音角色失败"
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "VOICE_ERROR",
"errorMessage" to errorMsg
))
return false return false
} }
} }
@ -175,140 +303,298 @@ class BytedanceTTS(
/** /**
* 单次播放(非流式) * 单次播放(非流式)
*/ */
suspend fun speakOnce(text: String): Boolean = withContext(Dispatchers.IO) { override fun speakOnce(text: String): Boolean {
try { try {
if (text.isEmpty()) { if (text.isEmpty()) {
errorCallback("PARAM_ERROR", "合成文本不能为空") return false
return@withContext false
} }
isStreamMode = false isStreamMode = false
// 如果没有连接或会话,重新初始化 launch {
if (webSocket == null || !isConnected) { if (!ensureSessionReady()) {
connect() notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "SESSION_ERROR",
// 等待连接和会话建立 "errorMessage" to "无法建立会话"))
var attempts = 0 return@launch
while (!isSessionStarted && attempts < 10) {
delay(300)
attempts++
} }
if (!isSessionStarted) { sendTextRequest(text)
errorCallback("SESSION_ERROR", "无法建立会话")
return@withContext false
}
} }
// 发送文本进行合成 return true
sendTextRequest(text)
return@withContext true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "语音合成失败: ${e.message}", e) Log.e(TAG, "语音合成失败: ${e.message}")
errorCallback("SYNTH_ERROR", e.message ?: "语音合成失败")
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
false return false
} }
} }
/** /**
* 流式播放开始 * 流式播放开始
*/ */
suspend fun speakStream(text: String): Boolean = withContext(Dispatchers.IO) { override fun speakStream(text: String): Boolean {
try { try {
if (text.isEmpty()) { if (text.isEmpty()) {
return@withContext true // 空文本直接返回成功 return true
} }
Log.d(TAG, "开始流式合成,text: $text")
isStreamMode = true isStreamMode = true
// 确保会话已就绪 // 确保会话已就绪
ensureSessionReady() if (!isSessionStarted && !ensureSessionReady()) {
Log.e(TAG, "流式播放失败:无法建立会话")
return false
}
// 发送文本进行合成,但不自动结束会话 // 如果文本处理协程不活跃,重新启动它
sendStreamTextRequest(text) if (textProcessingJob == null || textProcessingJob?.isActive == false) {
updateStatus(STATUS_SPEAKING) startTextProcessing()
return@withContext true }
// Log.d(TAG, "speakStream $sessionId, $text ")
textChannel.trySend(text)
return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "流式合成失败: ${e.message}", e) Log.e(TAG, "流式合成失败: ${e.message}")
errorCallback("STREAM_ERROR", e.message ?: "流式合成失败")
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
false return false
} }
} }
/** /**
* 结束流式播放 * 结束流式播放
*/ */
suspend fun flushStream(): Boolean = withContext(Dispatchers.IO) { override fun flushStream(): Boolean {
try { try {
if (!isStreamMode) { if (!isStreamMode) return true
return@withContext true // Log.d(TAG, "flushStream: $sessionId")
} launch {
withContext(Dispatchers.IO) {
if (isSessionStarted) { delay(300) // 确保现有文本处理完成
finishSession() }
if (isSessionStarted) {
isSessionStarted = false
finishSession()
}
} }
isStreamMode = false return true
updateStatus(STATUS_STOPPED)
return@withContext true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "结束流式合成失败: ${e.message}", e) Log.e(TAG, "结束流式合成失败: ${e.message}")
errorCallback("FLUSH_ERROR", e.message ?: "结束流式合成失败") return false
false
} }
} }
/** /**
* 停止语音合成 * 释放资源
*/ */
suspend fun stop() = withContext(Dispatchers.IO) { override fun release() {
if (isSessionStarted) { Log.d(TAG, "BytedanceTTS release called.")
finishSession()
// 关闭文本通道
textChannel.close()
textProcessingJob?.cancel()
job.cancel()
// 释放音频播放器资源
audioPlayer.release()
// 重置所有状态
reset()
// 确保WebSocket连接完全关闭
webSocket?.cancel()
webSocket = null
}
/**
* 添加TTS事件监听器
*/
override fun addListener(listener: TtsEventListener) {
if (!eventListeners.contains(listener)) {
eventListeners.add(listener)
} }
updateStatus(STATUS_STOPPED)
} }
/** /**
* 释放资源 * 移除TTS事件监听器
*/ */
fun release() { override fun removeListener(listener: TtsEventListener) {
webSocket?.cancel() eventListeners.remove(listener)
webSocket = null }
isConnected = false
isSessionStarted = false /**
updateStatus(STATUS_STOPPED) * 添加音频数据监听器
*/
override fun addAudioDataListener(listener: AudioDataListener) {
if (!audioDataListeners.contains(listener)) {
audioDataListeners.add(listener)
}
}
/**
* 移除音频数据监听器
*/
override fun removeAudioDataListener(listener: AudioDataListener) {
audioDataListeners.remove(listener)
}
/**
* 通知事件处理
*/
private fun notifyEvent(eventType: TtsEventType, params: Map<String, Any> = emptyMap()) {
val event = TtsEvent(eventType, params)
val listeners = ArrayList(eventListeners)
for (listener in listeners) {
try {
listener.onEvent(event)
} catch (e: Exception) {
Log.e(TAG, "事件回调异常: ${e.message}")
}
}
}
/**
* 通知音频数据
*/
private fun notifyAudioData(data: ByteArray) {
launch(Dispatchers.Main) {
for (listener in audioDataListeners) {
try {
listener.onAudioData(data)
} catch (e: Exception) {
Log.e(TAG, "音频数据回调异常: ${e.message}")
}
}
}
} }
/** /**
* 确保会话已就绪 * 确保会话已就绪
*/ */
private suspend fun ensureSessionReady() { private fun ensureSessionReady(): Boolean {
// 如果没有连接或会话,重新初始化 // 如果已连接和会话已开始,直接返回成功
if (webSocket == null || !isConnected) { if (isConnected && isSessionStarted) {
connect() return true
// 等待连接和会话建立 }
// 如果已连接但会话未开始,只需要开始会话
if (isConnected && !isSessionStarted) {
Log.d(TAG, "WebSocket已连接,正在启动新会话...")
return startSessionOnly()
}
// 如果正在连接中,等待连接完成
if (isConnecting.get()) {
Log.d(TAG, "已有连接正在进行中,等待其完成...")
// 等待连接完成,最多5秒
var attempts = 0 var attempts = 0
while (!isSessionStarted && attempts < 10) { while (isConnecting.get() && attempts < 10) {
delay(300) try {
attempts++ Thread.sleep(500)
attempts++
} catch (e: InterruptedException) {
return false
}
} }
if (!isSessionStarted) { // 连接完成后,检查是否需要启动会话
throw Exception("无法建立会话") if (isConnected && !isSessionStarted) {
return startSessionOnly()
} }
return isConnected
}
// 检查连接重试次数限制
if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {
Log.e(TAG, "已达到最大连接尝试次数($MAX_CONNECTION_ATTEMPTS),不再尝试连接")
return false
}
// 检查连接间隔时间 - 避免短时间内频繁重连
val currentTime = System.currentTimeMillis()
if (currentTime - lastConnectionAttemptTime < 2000 && lastConnectionAttemptTime > 0) {
Log.w(TAG, "连接请求太频繁,等待冷却时间...")
Thread.sleep(2000)
} }
// 更新连接计数和时间
connectionAttempts++
lastConnectionAttemptTime = System.currentTimeMillis()
// 尝试建立连接
isConnecting.set(true)
try {
connect()
// 等待连接建立,设置超时
val startWaitTime = System.currentTimeMillis()
while (!isConnected && System.currentTimeMillis() - startWaitTime < CONNECTION_TIMEOUT_MS) {
try {
Thread.sleep(500)
// 如果连接已失败,提前退出等待
if (!isConnecting.get() && !isConnected) {
Log.e(TAG, "连接已失败,终止等待")
break
}
} catch (e: InterruptedException) {
isConnecting.set(false)
return false
}
}
// 连接成功后,启动会话
if (isConnected && !isSessionStarted) {
startSessionOnly()
}
} finally {
isConnecting.set(false)
}
// 检查连接结果
if (!isConnected) {
Log.e(TAG, "连接超时: 无法在${CONNECTION_TIMEOUT_MS/1000}秒内建立连接,连接尝试次数: $connectionAttempts")
return false
}
// 连接成功,重置尝试计数
connectionAttempts = 0
return true
} }
/** /**
* 用于暂停执行的辅助函数 * 只启动会话,不重新建立WebSocket连接
*/ */
private suspend fun delay(ms: Long) = withContext(Dispatchers.IO) { private fun startSessionOnly(): Boolean {
if (!isConnected || webSocket == null) {
Log.e(TAG, "无法启动会话:WebSocket未连接")
return false
}
if (isSessionStarted) {
return true
}
try { try {
Thread.sleep(ms) // 重新生成会话ID
} catch (e: InterruptedException) { sessionId = UUID.randomUUID().toString().replace("-", "")
// 忽略中断异常 Log.d(TAG, "启动新会话,会话ID: $sessionId")
// 发送开始会话请求
startTtsSession(webSocket!!)
// 立即将会话视为已启动,不等待服务器确认
isSessionStarted = true
updateStatus(STATUS_READY)
return true
} catch (e: Exception) {
Log.e(TAG, "启动会话失败: ${e.message}", e)
return false
} }
} }
@ -317,42 +603,40 @@ class BytedanceTTS(
*/ */
private fun updateStatus(status: String) { private fun updateStatus(status: String) {
currentStatus = status currentStatus = status
statusCallback(status)
} }
/** /**
* 建立WebSocket连接 * 建立WebSocket连接
*/ */
private fun connect() { private fun connect() {
webSocket?.cancel()
webSocket = null
val url = "wss://openspeech.bytedance.com/api/v3/tts/bidirection" val url = "wss://openspeech.bytedance.com/api/v3/tts/bidirection"
val connectId = UUID.randomUUID().toString()
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.header("X-Api-App-Key", appId) .header("X-Api-App-Key", appId)
.header("X-Api-Access-Key", accessToken) .header("X-Api-Access-Key", accessToken)
.header("X-Api-Resource-Id", "volc.service_type.10029") .header("X-Api-Resource-Id", "volc.service_type.10029")
.header("X-Api-Connect-Id", UUID.randomUUID().toString()) .header("X-Api-Connect-Id", connectId)
.build() .build()
Log.d(TAG, "正在连接WebSocket: $url, appId: $appId") isConnected = false
Log.d(TAG, "当前语音角色配置: speaker=$speaker, format=$format, sampleRate=$sampleRate") isSessionStarted = false
webSocket = client.newWebSocket(request, object : WebSocketListener() { webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
val logId = response.header("X-Tt-Logid") isConnected = true
Log.d(TAG, "TTS WebSocket连接成功, X-Tt-Logid: $logId")
// 发送开始连接事件
startConnection(webSocket) startConnection(webSocket)
} }
override fun onMessage(webSocket: WebSocket, bytes: ByteString) { override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
try { try {
val data = bytes.toByteArray() val data = bytes.toByteArray()
Log.d(TAG, "收到二进制消息,大小: ${data.size} 字节")
val response = parseResponse(data) val response = parseResponse(data)
// 处理事件
when (response.event) { when (response.event) {
EVENT_CONNECTION_FAILED, EVENT_SESSION_FAILED -> { EVENT_CONNECTION_FAILED, EVENT_SESSION_FAILED -> {
val errorMsg = if (response.responseMetaJson.isNotEmpty()) { val errorMsg = if (response.responseMetaJson.isNotEmpty()) {
@ -360,99 +644,68 @@ class BytedanceTTS(
} else { } else {
"连接或会话失败" "连接或会话失败"
} }
Log.e(TAG, "连接或会话失败: $errorMsg, 事件类型: ${response.event}") notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "TTS_ERROR",
errorCallback("TTS_ERROR", errorMsg) "errorMessage" to errorMsg))
webSocket.close(1000, "Error") webSocket.close(1000, "Error")
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
} isSpeaking = false // 错误情况下显式设置
isSessionStarted = false
EVENT_CONNECTION_STARTED -> { isConnected = false
isConnected = true isConnecting.set(false)
Log.d(TAG, "连接已建立,开始TTS会话")
// 开始TTS会话
startTtsSession(webSocket)
} }
EVENT_SESSION_STARTED -> { EVENT_SESSION_STARTED -> {
isSessionStarted = true connectionAttempts = 0
Log.d(TAG, "TTS会话已开始,会话ID: $sessionId")
updateStatus(STATUS_READY)
} }
EVENT_TTS_RESPONSE -> { EVENT_TTS_RESPONSE -> {
// 音频数据
if (response.messageType == AUDIO_ONLY_RESPONSE && response.payload.isNotEmpty()) { if (response.messageType == AUDIO_ONLY_RESPONSE && response.payload.isNotEmpty()) {
Log.d(TAG, "收到音频数据,大小: ${response.payload.size} 字节") notifyAudioData(response.payload)
try {
// 使用同步方式调用回调,防止协程上下文切换导致的问题
if (response.payload.size > 0) {
Log.d(TAG, "准备调用音频回调,数据大小: ${response.payload.size}")
audioCallback(response.payload)
Log.d(TAG, "音频回调调用成功")
} else {
Log.w(TAG, "收到空音频数据")
}
} catch (e: Exception) {
Log.e(TAG, "调用音频回调函数失败: ${e.message}", e)
errorCallback("AUDIO_CALLBACK_ERROR", "音频处理失败: ${e.message}")
}
} else {
Log.d(TAG, "收到TTS响应,但没有音频数据 messageType=${response.messageType}, 有效载荷大小=${response.payload.size}")
} }
} }
EVENT_TTS_SENTENCE_START -> {
Log.d(TAG, "句子合成开始")
}
EVENT_TTS_SENTENCE_END -> {
Log.d(TAG, "句子合成结束")
}
EVENT_CONNECTION_FINISHED -> { EVENT_CONNECTION_FINISHED -> {
isConnected = false isConnected = false
isSessionStarted = false isSessionStarted = false
Log.d(TAG, "连接已结束") isConnecting.set(false)
updateStatus(STATUS_STOPPED)
completionCallback()
webSocket.close(1000, "Connection finished") webSocket.close(1000, "Connection finished")
} }
EVENT_SESSION_FINISHED -> { EVENT_SESSION_FINISHED -> {
isSessionStarted = false isSessionStarted = false
Log.d(TAG, "会话已结束,流模式: $isStreamMode")
if (!isStreamMode) { if (!isStreamMode) {
finishConnection(webSocket) finishConnection(webSocket)
} else {
// 流模式下保持连接
updateStatus(STATUS_READY)
} }
} }
else -> {
Log.d(TAG, "收到未处理的事件类型: ${response.event}")
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "解析响应失败: ${e.message}", e) Log.e(TAG, "解析响应失败: ${e.message}")
errorCallback("PARSE_ERROR", e.message ?: "解析响应失败")
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
isSpeaking = false // 错误情况下显式设置
isConnecting.set(false)
} }
} }
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
isConnected = false isConnected = false
isSessionStarted = false isSessionStarted = false
Log.e(TAG, "连接失败: ${t.message}", t) isSpeaking = false
errorCallback("CONNECTION_ERROR", t.message ?: "连接失败") isConnecting.set(false)
Log.e(TAG, "WebSocket连接失败: ${t.message}")
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "CONNECTION_ERROR",
"errorMessage" to (t.message ?: "连接失败")))
updateStatus(STATUS_ERROR) updateStatus(STATUS_ERROR)
} }
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
isConnected = false isConnected = false
isSessionStarted = false isSessionStarted = false
isSpeaking = false
isConnecting.set(false)
updateStatus(STATUS_STOPPED) updateStatus(STATUS_STOPPED)
Log.d(TAG, "TTS WebSocket连接关闭: $code, $reason") notifyEvent(TtsEventType.SYNTHESIS_CANCELED)
} }
}) })
} }
@ -533,15 +786,14 @@ class BytedanceTTS(
reqParams.put("speaker", speaker) reqParams.put("speaker", speaker)
val audioParams = JSONObject() val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式 audioParams.put("format", format)
audioParams.put("sample_rate", 24000) // 提高采样率 audioParams.put("sample_rate", sampleRate)
audioParams.put("enable_timestamp", enableTimestamp) audioParams.put("enable_timestamp", enableTimestamp)
reqParams.put("audio_params", audioParams) reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams) jsonObject.put("req_params", reqParams)
val payload = jsonObject.toString().toByteArray() val payload = jsonObject.toString().toByteArray()
Log.d(TAG, "开始TTS会话请求: ${jsonObject.toString()}")
sendEvent(webSocket, header, optional, payload) sendEvent(webSocket, header, optional, payload)
} }
@ -574,12 +826,15 @@ class BytedanceTTS(
jsonObject.put("namespace", "BidirectionalTTS") jsonObject.put("namespace", "BidirectionalTTS")
val reqParams = JSONObject() val reqParams = JSONObject()
reqParams.put("text", text) // 将#和*替换为空格
val processedText = text.replace("#", " ").replace("*", " ")
reqParams.put("text", processedText)
reqParams.put("speaker", speaker) reqParams.put("speaker", speaker)
val audioParams = JSONObject() val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式 audioParams.put("format", format)
audioParams.put("sample_rate", 24000) // 提高采样率 audioParams.put("sample_rate", sampleRate)
audioParams.put("emotion", "happy")
reqParams.put("audio_params", audioParams) reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams) jsonObject.put("req_params", reqParams)
@ -588,9 +843,6 @@ class BytedanceTTS(
Log.d(TAG, "发送文本合成请求: ${jsonObject.toString()}") Log.d(TAG, "发送文本合成请求: ${jsonObject.toString()}")
sendEvent(ws, header, optional, payload) sendEvent(ws, header, optional, payload)
// 发送完文本后结束会话
updateStatus(STATUS_SPEAKING)
} }
} }
@ -622,18 +874,21 @@ class BytedanceTTS(
jsonObject.put("namespace", "BidirectionalTTS") jsonObject.put("namespace", "BidirectionalTTS")
val reqParams = JSONObject() val reqParams = JSONObject()
reqParams.put("text", text) // 将#和*替换为空格
val processedText = text.replace("#", " ").replace("*", " ")
reqParams.put("text", processedText)
reqParams.put("speaker", speaker) reqParams.put("speaker", speaker)
val audioParams = JSONObject() val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式 audioParams.put("format", format)
audioParams.put("sample_rate", 24000) // 提高采样率 audioParams.put("sample_rate", sampleRate)
audioParams.put("emotion", "happy")
reqParams.put("audio_params", audioParams) reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams) jsonObject.put("req_params", reqParams)
val payload = jsonObject.toString().toByteArray() val payload = jsonObject.toString().toByteArray()
Log.d(TAG, "发送流式文本合成请求: ${jsonObject.toString()}") // Log.d(TAG, "发送流式文本合成请求: ${jsonObject.toString()}")
sendEvent(ws, header, optional, payload) sendEvent(ws, header, optional, payload)
// 流式模式下不自动结束会话 // 流式模式下不自动结束会话
@ -663,6 +918,9 @@ class BytedanceTTS(
val payload = "{}".toByteArray() val payload = "{}".toByteArray()
sendEvent(ws, header, optional, payload) sendEvent(ws, header, optional, payload)
Log.d(TAG, "发送结束会话请求,会话ID: $sessionId")
// 不在这里设置 isSessionStarted = false
// 等待服务器的 SESSION_FINISHED 事件来设置
} }
} }
@ -691,10 +949,9 @@ class BytedanceTTS(
System.arraycopy(payload, 0, requestBytes, offset, payload.size) System.arraycopy(payload, 0, requestBytes, offset, payload.size)
val success = webSocket.send(ByteString.of(*requestBytes)) webSocket.send(ByteString.of(*requestBytes))
Log.d(TAG, "发送WebSocket消息: 消息类型=${header.messageType}, 事件=${optional.event}, 大小=${requestBytes.size}字节, 成功=$success")
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "发送WebSocket消息失败: ${e.message}", e) Log.e(TAG, "发送WebSocket消息失败: ${e.message}")
} }
} }
@ -972,4 +1229,5 @@ class BytedanceTTS(
return result return result
} }
} }
} }

2
local_plugins/classic_bluetooth/pubspec.yaml

@ -9,8 +9,6 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
deepvoice_utils:
path: ../deepvoice_utils
flutter: flutter:
plugin: plugin:

BIN
local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock

Binary file not shown.

BIN
local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock

Binary file not shown.

2
local_plugins/opus/.gradle/buildOutputCleanup/cache.properties

@ -1,2 +1,2 @@
#Sun May 11 13:39:53 IST 2025 #Fri May 16 09:26:08 IST 2025
gradle.version=8.10 gradle.version=8.10

2
local_plugins/speech/android/build.gradle.kts

@ -9,7 +9,7 @@ plugins {
android { android {
// 命名空间,对应你插件的包名(需与代码内包名保持一致) // 命名空间,对应你插件的包名(需与代码内包名保持一致)
namespace = "com.yunqiinnovation.open_ai_service" namespace = "com.deep_voice.speech"
// 目标 SDK 版本 // 目标 SDK 版本
compileSdk = 35 compileSdk = 35

2
local_plugins/speech/android/settings.gradle.kts

@ -1 +1 @@
rootProject.name = "open_ai_service" rootProject.name = "speech"

10
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt

@ -15,7 +15,7 @@ interface ITtsService {
* @param language 语言代码,如"zh-CN" * @param language 语言代码,如"zh-CN"
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
suspend fun initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String = "", language: String = "zh-CN"): Boolean fun initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String = "", language: String = "zh-CN"): Boolean
/** /**
* 设置语音角色 * 设置语音角色
@ -31,7 +31,7 @@ interface ITtsService {
* @param text 要合成的文本 * @param text 要合成的文本
* @return 是否成功开始合成 * @return 是否成功开始合成
*/ */
suspend fun speakOnce(text: String): Boolean fun speakOnce(text: String): Boolean
/** /**
* 流式合成 * 流式合成
@ -40,21 +40,21 @@ interface ITtsService {
* @param text 要合成的文本片段 * @param text 要合成的文本片段
* @return 是否成功处理 * @return 是否成功处理
*/ */
suspend fun speakStream(text: String): Boolean fun speakStream(text: String): Boolean
/** /**
* 刷新并播放流式文本缓冲区中的剩余内容 * 刷新并播放流式文本缓冲区中的剩余内容
* *
* @return 是否成功处理 * @return 是否成功处理
*/ */
suspend fun flushStream(): Boolean fun flushStream(): Boolean
/** /**
* 停止当前合成和播放 * 停止当前合成和播放
* *
* @return 是否成功停止 * @return 是否成功停止
*/ */
suspend fun stop(): Boolean fun stop(): Boolean
/** /**
* 释放资源 * 释放资源

12
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt

@ -13,15 +13,6 @@ enum class TtsEventType {
/** 合成取消 */ /** 合成取消 */
SYNTHESIS_CANCELED, SYNTHESIS_CANCELED,
/** 开始播放 */
STARTED,
/** 播放完成 */
COMPLETED,
/** 播放取消 */
CANCELED,
/** 发生错误 */ /** 发生错误 */
ERROR ERROR
} }
@ -31,7 +22,8 @@ enum class TtsEventType {
* *
* @param type 事件类型 * @param type 事件类型
* @param params 事件参数,根据不同的事件类型包含不同的内容 * @param params 事件参数,根据不同的事件类型包含不同的内容
* 错误事件通常包含 "errorCode" 和 "errorMessage" * - ERROR: 包含 "errorCode" 和 "errorMessage"
* - CANCELED: 包含 "reason" 和 "errorDetails"
*/ */
data class TtsEvent( data class TtsEvent(
val type: TtsEventType, val type: TtsEventType,

2
pubspec.yaml

@ -81,8 +81,6 @@ dependencies:
path: local_plugins/jl_opus path: local_plugins/jl_opus
ble_service: ble_service:
path: local_plugins/ble_service path: local_plugins/ble_service
deepvoice_utils:
path: local_plugins/deepvoice_utils
classic_bluetooth: classic_bluetooth:
path: local_plugins/classic_bluetooth path: local_plugins/classic_bluetooth
agent_service: agent_service:

Loading…
Cancel
Save