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()
}
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');
return value ?? false;
} catch (e) {
Logger.error('读取主题设置失败: $e');
Logger.error('读取主题设置失败: ${e.toString()}');
return false;
}
}
@ -32,7 +32,7 @@ class ThemeService {
Logger.info('保存主题设置: ${isDarkMode ? "暗黑" : "浅色"}');
await _box.write(_key, isDarkMode);
} 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 const String _defaultTag = 'App';
/// 设置日志级别
static void setLevel(LogLevel 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) {
_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) {
_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) {
_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) {
_log('ERROR', message, tag: tag);
_log('ERROR', _defaultTag, message);
if (error != null) {
_log('ERROR', 'Original error: $error', tag: tag);
_log('ERROR', _defaultTag, 'Original error: $error');
}
if (stackTrace != null) {
_log('ERROR', 'Stack trace: $stackTrace', tag: tag);
_log('ERROR', _defaultTag, 'Stack trace: $stackTrace');
}
}
}
/// 获取当前调用栈的类名
static String? _getCallerClassName() {
try {
final stackTrace = StackTrace.current.toString();
final frames = stackTrace.split('\n');
// 查找调用者帧 (跳过Logger框架内部的帧)
for (int i = 0; i < frames.length; i++) {
final frame = frames[i].trim();
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;
/// 输出错误日志
static void e(String tag, String message, [dynamic error, StackTrace? stackTrace]) {
if (_currentLevel.index <= LogLevel.error.index) {
_log('ERROR', tag, message);
if (error != null) {
_log('ERROR', tag, 'Original error: $error');
}
if (stackTrace != null) {
_log('ERROR', tag, 'Stack trace: $stackTrace');
}
} catch (e) {
// 忽略解析错误
}
return null;
}
/// 内部日志输出方法
static void _log(String level, String message, {String? tag}) {
final timestamp = DateTime.now().toString();
final className = tag ?? _getCallerClassName() ?? '';
final tagInfo = className.isNotEmpty ? '[$className] ' : '';
developer.log('[$timestamp][$level][$tagInfo]: $message');
static void _log(String level, String tag, String message) {
developer.log('[$tag]: $message');
}
}

41
lib/data/services/ble_manager.dart

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

8
lib/data/services/bluetooth_manager.dart

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

2
lib/data/services/chat_history_service.dart

@ -39,7 +39,7 @@ class ChatHistoryService extends GetxService {
// 检查并清理过多的历史记录
await _cleanupOldMessages(agentId);
} 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) {
Logger.error('JSON解析错误: $e');
Logger.error('JSON解析错误: ${e.toString()}');
}
}
}
} catch (e) {
Logger.error('请求异常: $e');
Logger.error('请求异常: ${e.toString()}');
} finally {
client.close();
}
}
}
}

6
lib/data/services/kouzi_ai_service.dart

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

4
lib/data/services/speech_factory.dart

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

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

@ -69,7 +69,7 @@ class AzureTtsService extends GetxService implements TtsService {
_handleTtsEvent(event);
}
}, onError: (error) {
Logger.error('TTS事件流错误: $error');
Logger.error('TTS事件流错误: ${error.toString()}');
_eventStreamController.addError(error);
});
}
@ -78,7 +78,7 @@ class AzureTtsService extends GetxService implements TtsService {
void _handleTtsEvent(Map<dynamic, dynamic> event) {
final String eventType = event['type'] as String? ?? '';
// Logger.debug('收到TTS事件: $eventType, 数据: $event');
// Logger.debug('收到TTS事件: ${e.toString()}ventType, 数据: ${e.toString()}vent');
switch (eventType) {
case 'synthesis_started':
@ -159,7 +159,7 @@ class AzureTtsService extends GetxService implements TtsService {
_isInitialized = result;
return result;
} catch (e) {
Logger.error('初始化失败: $e');
Logger.error('初始化失败: ${e.toString()}');
return false;
}
}
@ -177,7 +177,7 @@ class AzureTtsService extends GetxService implements TtsService {
if (result) _currentVoice = voiceName;
return result;
} catch (e) {
Logger.error('设置语音失败: $e');
Logger.error('设置语音失败: ${e.toString()}');
return false;
}
}
@ -195,7 +195,7 @@ class AzureTtsService extends GetxService implements TtsService {
return result == true;
} catch (e) {
Logger.error('语音合成失败: $e');
Logger.error('语音合成失败: ${e.toString()}');
return false;
}
}
@ -250,7 +250,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('处理流式文本失败: $e');
Logger.error('处理流式文本失败: ${e.toString()}');
return false;
}
}
@ -316,7 +316,7 @@ class AzureTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成');
return result;
} catch (e) {
Logger.error('刷新TTS流失败: $e');
Logger.error('刷新TTS流失败: ${e.toString()}');
return false;
}
}
@ -358,7 +358,7 @@ class AzureTtsService extends GetxService implements TtsService {
_textQueue.removeAt(0);
}
} catch (e) {
Logger.error('处理语音队列出错: $e');
Logger.error('处理语音队列出错: ${e.toString()}');
// 出错时移除当前项并尝试继续
if (_textQueue.isNotEmpty) {
@ -389,7 +389,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('停止语音合成出错: $e');
Logger.error('停止语音合成出错: ${e.toString()}');
return false;
}
}
@ -410,7 +410,7 @@ class AzureTtsService extends GetxService implements TtsService {
await _channel.invokeMethod('dispose');
_isInitialized = false;
} 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) {
_isAvailable.value = false;
_asrState.value = AsrState.error;
Logger.error('语音识别初始化失败: $e');
Logger.error('语音识别初始化失败: ${e.toString()}');
}
}
@ -203,7 +203,7 @@ class FlutterAsrService extends GetxService implements AsrService {
return result;
} catch (e) {
Logger.error('启动语音识别失败: $e');
Logger.error('启动语音识别失败: ${e.toString()}');
_asrState.value = AsrState.error;
return false;
}
@ -253,7 +253,7 @@ class FlutterAsrService extends GetxService implements AsrService {
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
}
} catch (e) {
Logger.error('重启连续识别失败: $e');
Logger.error('重启连续识别失败: ${e.toString()}');
// 发送错误给控制器
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
@ -304,7 +304,7 @@ class FlutterAsrService extends GetxService implements AsrService {
await _initSpeech();
return _isAvailable.value;
} catch (e) {
Logger.error('语音识别初始化失败: $e');
Logger.error('语音识别初始化失败: ${e.toString()}');
return false;
}
}
@ -357,7 +357,7 @@ class FlutterAsrService extends GetxService implements AsrService {
},
);
} catch (e) {
Logger.error('一次性语音识别失败: $e');
Logger.error('一次性语音识别失败: ${e.toString()}');
return RecognitionEvent.error('$e');
}
}
@ -406,7 +406,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = true;
return _continuousRecognitionController!.stream;
} catch (e) {
Logger.error('启动连续语音识别失败: $e');
Logger.error('启动连续语音识别失败: ${e.toString()}');
// 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) {
@ -442,7 +442,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_asrState.value = AsrState.done;
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
Logger.error('停止连续语音识别失败: ${e.toString()}');
// 强制重置状态
_isListening = false;
@ -472,7 +472,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = false;
_asrState.value = AsrState.notInitialized;
} 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 已初始化');
} 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(", ")}');
return true;
} catch (e) {
Logger.error('TTS初始化失败: $e');
Logger.error('TTS初始化失败: ${e.toString()}');
return false;
}
}
@ -149,7 +149,7 @@ class FlutterTtsService extends GetxService implements TtsService {
_currentVoice.value = _supportedVoices.first;
}
} catch (e) {
Logger.error('加载TTS语音失败: $e');
Logger.error('加载TTS语音失败: ${e.toString()}');
// 添加默认语音
if (_supportedVoices.isEmpty) {
@ -175,7 +175,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName');
return true;
} catch (e) {
Logger.error('设置TTS语音失败: $e');
Logger.error('设置TTS语音失败: ${e.toString()}');
return false;
}
}
@ -200,7 +200,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('设置语音参数失败: $e');
Logger.error('设置语音参数失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('设置语音参数失败: $e'));
@ -227,7 +227,7 @@ class FlutterTtsService extends GetxService implements TtsService {
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
Logger.error('TTS播放失败: $e');
Logger.error('TTS播放失败: ${e.toString()}');
return false;
}
}
@ -288,7 +288,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('处理流式文本失败: $e');
Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@ -333,7 +333,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成');
return true;
} catch (e) {
Logger.error('等待TTS队列播放完成失败: $e');
Logger.error('等待TTS队列播放完成失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@ -382,7 +382,7 @@ class FlutterTtsService extends GetxService implements TtsService {
}
}
} catch (e) {
Logger.error('TTS处理队列失败: $e');
Logger.error('TTS处理队列失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@ -422,7 +422,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS停止播放完成');
return true;
} catch (e) {
Logger.error('TTS停止播放失败: $e');
Logger.error('TTS停止播放失败: ${e.toString()}');
// 即使出错也尝试强制重置状态
_isSpeaking.value = false;
@ -443,7 +443,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS资源已释放');
} catch (e) {
Logger.error('释放TTS资源失败: $e');
Logger.error('释放TTS资源失败: ${e.toString()}');
}
}
@ -462,7 +462,7 @@ class FlutterTtsService extends GetxService implements TtsService {
await flushStream();
return true;
} catch (e) {
Logger.error('处理文本流时出错: $e');
Logger.error('处理文本流时出错: ${e.toString()}');
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;
} catch (e) {
Logger.error('TTS初始化失败: $e');
Logger.error('TTS初始化失败: ${e.toString()}');
return false;
}
}
@ -88,7 +88,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName');
return true;
} catch (e) {
Logger.error('设置TTS语音失败: $e');
Logger.error('设置TTS语音失败: ${e.toString()}');
return false;
}
}
@ -127,7 +127,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
_handle(message);
},
onError: (error) {
Logger.error('连接失败: $error');
Logger.error('连接失败: ${error.toString()}');
},
onDone: () {
Logger.error('WebSocket 连接已关闭!');
@ -135,7 +135,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
cancelOnError: true,
);
} catch (e) {
Logger.error('初始化异常: $e');
Logger.error('初始化异常: ${e.toString()}');
}
}
@ -226,7 +226,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
_isSpeaking.value = false;
Logger.error('语音合成失败: $e');
Logger.error('语音合成失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('语音合成失败: $e'));
@ -285,7 +285,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('处理流式文本失败: $e');
Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('处理流式文本失败: $e'));
@ -319,7 +319,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
Logger.error('刷新TTS流失败: $e');
Logger.error('刷新TTS流失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('刷新TTS流失败: $e'));
@ -347,7 +347,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await speakOnce(item);
}
} catch (e) {
Logger.error('处理语音队列出错: $e');
Logger.error('处理语音队列出错: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('处理语音队列出错: $e'));
@ -370,7 +370,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
}
return true;
} catch (e) {
Logger.error('停止语音合成出错: $e');
Logger.error('停止语音合成出错: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('停止语音合成出错: $e'));
return false;
@ -387,7 +387,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await _eventController.close();
_isInitialized = false;
} 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已初始化');
return true;
} catch (e) {
Logger.error('初始化火山语音API失败: $e');
Logger.error('初始化火山语音API失败: ${e.toString()}');
return false;
}
}
@ -151,7 +151,7 @@ class VolcanoAsrApiService implements AsrService {
return resultCompleter.future;
} catch (e) {
Logger.error('单次语音识别失败: $e');
Logger.error('单次语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString());
}
}
@ -184,7 +184,7 @@ class VolcanoAsrApiService implements AsrService {
return _recognitionController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: $e');
Logger.error('开始连续语音识别失败: ${e.toString()}');
await _cleanupResources();
// 返回一个包含错误的流
@ -222,7 +222,7 @@ class VolcanoAsrApiService implements AsrService {
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
Logger.error('停止连续语音识别失败: ${e.toString()}');
// 确保资源被清理
await _cleanupResources();
@ -250,7 +250,7 @@ class VolcanoAsrApiService implements AsrService {
_isInitialized = false;
Logger.info('火山语音API资源已释放');
} catch (e) {
Logger.error('释放火山语音API资源失败: $e');
Logger.error('释放火山语音API资源失败: ${e.toString()}');
_isContinuousRecognitionActive = false;
_isInitialized = false;
}
@ -284,7 +284,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音机初始化成功');
} catch (e) {
// _recorder = null;
Logger.error('初始化录音机失败: $e');
Logger.error('初始化录音机失败: ${e.toString()}');
rethrow;
}
}
@ -344,7 +344,7 @@ class VolcanoAsrApiService implements AsrService {
_connectionCompleter!.completeError(e);
}
Logger.error('连接WebSocket失败: $e');
Logger.error('连接WebSocket失败: ${e.toString()}');
rethrow;
}
@ -432,7 +432,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(heartbeatMsg));
} catch (e) {
Logger.error('发送心跳包失败: $e');
Logger.error('发送心跳包失败: ${e.toString()}');
}
}
}
@ -461,7 +461,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(startCommand));
Logger.info('已发送开始识别指令');
} catch (e) {
Logger.error('发送开始识别指令失败: $e');
Logger.error('发送开始识别指令失败: ${e.toString()}');
rethrow;
}
}
@ -496,7 +496,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音已开始');
} catch (e) {
_isRecording = false;
Logger.error('开始录音失败: $e');
Logger.error('开始录音失败: ${e.toString()}');
rethrow;
}
*/
@ -544,7 +544,7 @@ class VolcanoAsrApiService implements AsrService {
await _sendAudioData(audioData);
}
} catch (e) {
Logger.error('捕获和发送音频失败: $e');
Logger.error('捕获和发送音频失败: ${e.toString()}');
}
*/
}
@ -562,7 +562,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(audioMessage));
} catch (e) {
Logger.error('发送音频数据失败: $e');
Logger.error('发送音频数据失败: ${e.toString()}');
}
}
@ -579,7 +579,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(endCommand));
Logger.info('已发送结束指令');
} catch (e) {
Logger.error('发送结束指令失败: $e');
Logger.error('发送结束指令失败: ${e.toString()}');
}
}
@ -596,7 +596,7 @@ class VolcanoAsrApiService implements AsrService {
_isRecording = false;
Logger.info('录音已停止');
} catch (e) {
Logger.error('停止录音失败: $e');
Logger.error('停止录音失败: ${e.toString()}');
}
}
*/
@ -641,7 +641,7 @@ class VolcanoAsrApiService implements AsrService {
}
}
} catch (e) {
Logger.error('处理WebSocket消息失败: $e');
Logger.error('处理WebSocket消息失败: ${e.toString()}');
if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError('处理消息失败: $e');
@ -675,7 +675,7 @@ class VolcanoAsrApiService implements AsrService {
}
}
} catch (e) {
Logger.error('处理结果消息失败: $e');
Logger.error('处理结果消息失败: ${e.toString()}');
}
}
@ -741,7 +741,7 @@ class VolcanoAsrApiService implements AsrService {
/// 处理WebSocket错误
void _handleWebSocketError(dynamic error) {
Logger.error('WebSocket错误: $error');
Logger.error('WebSocket错误: ${e.toString()}rror');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(error);
@ -789,7 +789,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.close();
_channel = null;
} catch (e) {
Logger.error('关闭WebSocket连接失败: $e');
Logger.error('关闭WebSocket连接失败: ${e.toString()}');
}
}
}
@ -811,4 +811,4 @@ class VolcanoAsrApiService implements AsrService {
_isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false;
}
}
}

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

@ -357,7 +357,7 @@ class VolcanoTtsApiService extends GetxController {
_handleMessage,
onError: (error) {
// 直接处理错误,不尝试重连
Logger.error('WebSocket错误: $error');
Logger.error('WebSocket错误: ${error.toString()}');
if (!_eventController.isClosed) {
_eventController.add({
'eventType': 'error',
@ -391,7 +391,7 @@ class VolcanoTtsApiService extends GetxController {
});
}
} catch (e) {
Logger.error('处理TTS连接超时事件失败: $e');
Logger.error('处理TTS连接超时事件失败: ${e.toString()}');
}
}
timeout = null;
@ -427,7 +427,7 @@ class VolcanoTtsApiService extends GetxController {
return result;
} catch (e) {
// 只记录错误,不尝试重连
Logger.error('连接过程中发生异常: $e');
Logger.error('连接过程中发生异常: ${e.toString()}');
return false;
} finally {
isConnecting.value = false;
@ -479,7 +479,7 @@ class VolcanoTtsApiService extends GetxController {
'error': '会话开始超时'
});
} catch (e) {
Logger.error('处理TTS会话超时事件失败: $e');
Logger.error('处理TTS会话超时事件失败: ${e.toString()}');
}
}
timeout = null;
@ -515,7 +515,7 @@ class VolcanoTtsApiService extends GetxController {
return result;
} catch (e) {
// 只记录错误,不尝试恢复
Logger.error('开始TTS会话失败: $e');
Logger.error('开始TTS会话失败: ${e.toString()}');
return false;
}
}
@ -902,10 +902,10 @@ class VolcanoTtsApiService extends GetxController {
'error': error.toString()
});
Logger.error('火山语音TTS错误: $error');
Logger.error('火山语音TTS错误: ${error.toString()}');
} catch (e) {
// 如果在处理错误时发生异常,记录到日志但不再抛出异常
Logger.error('处理TTS错误时发生异常: $e,原始错误: $error');
Logger.error('处理TTS错误时发生异常: ${e.toString()},原始错误: ${error.toString()}');
}
}
@ -1445,7 +1445,7 @@ class VolcanoTtsApiService extends GetxController {
return success;
} catch (e) {
Logger.error('TTS一次性合成失败: $e');
Logger.error('TTS一次性合成失败: ${e.toString()}');
// 确保会话结束
endSession();
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 语音识别服务初始化成功');
return true;
} catch (e) {
Logger.error('Azure 语音识别服务初始化失败: $e');
Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false;
rethrow;
}
@ -69,7 +69,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
try {
throw Exception('无效的识别结果格式');
} catch (e) {
Logger.error('语音识别失败: $e');
Logger.error('语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString());
}
}
@ -112,7 +112,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: $e');
Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow;
}
}
@ -152,7 +152,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
},
onError: (error) {
isConnected = false;
Logger.error('连接失败: $error');
Logger.error('连接失败: ${error.toString()}');
},
onDone: () {
isConnected = false;
@ -161,7 +161,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
cancelOnError: true,
);
} 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");
if (msg.isNotEmpty) {
if (data['cn']['st']['type'] == "0") {
@ -206,7 +206,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
}
} catch (e) {
Logger.error("接受消息异常 $e");
Logger.error("接受消息异常 ${e.toString()}");
}
}
@ -242,7 +242,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = false;
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
Logger.error('停止连续语音识别失败: ${e.toString()}');
rethrow;
}
}
@ -266,8 +266,8 @@ class XunfeiAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别资源已释放');
} catch (e) {
Logger.error('释放语音识别资源失败: $e');
Logger.error('释放语音识别资源失败: ${e.toString()}');
_isInitialized = false;
}
}
}
}

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

@ -76,7 +76,7 @@ class AndroidVoiceInteractionService extends GetxService {
_handleVoiceInteractionEvent(event);
}
}, onError: (error) {
Logger.error('语音交互事件通道错误: $error');
Logger.error('语音交互事件通道错误: ${error.toString()}');
});
}
@ -106,7 +106,7 @@ class AndroidVoiceInteractionService extends GetxService {
Logger.info('语音交互服务初始化完成');
return this;
} catch (e) {
Logger.error('语音交互服务初始化失败: $e');
Logger.error('语音交互服务初始化失败: ${e.toString()}');
return this;
}
}
@ -123,7 +123,7 @@ class AndroidVoiceInteractionService extends GetxService {
_isServiceRunning.value = isRunning;
Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
} catch (e) {
Logger.error('检查语音交互服务状态失败: $e');
Logger.error('检查语音交互服务状态失败: ${e.toString()}');
_isServiceRunning.value = false;
}
}
@ -151,7 +151,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
Logger.error('启动语音交互服务失败: $e');
Logger.error('启动语音交互服务失败: ${e.toString()}');
return false;
}
}
@ -175,7 +175,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
Logger.error('停止语音交互服务失败: $e');
Logger.error('停止语音交互服务失败: ${e.toString()}');
return false;
}
}
@ -199,7 +199,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
Logger.error('暂停语音交互失败: $e');
Logger.error('暂停语音交互失败: ${e.toString()}');
return false;
}
}
@ -213,7 +213,7 @@ class AndroidVoiceInteractionService extends GetxService {
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) {
case 'chatHistory':
@ -252,7 +252,7 @@ class AndroidVoiceInteractionService extends GetxService {
break;
default:
Logger.warning('收到未知类型的语音交互事件: $eventType');
Logger.warning('收到未知类型的语音交互事件: ${eventType.toString()}');
break;
}
}
@ -297,7 +297,7 @@ class AndroidVoiceInteractionService extends GetxService {
// 保存更新后的历史记录
await chatHistoryService.saveHistory(agentId, existingMessages);
} 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状态,不自动启动语音识别
Logger.info('iOS语音交互服务初始化完成,处于idle状态');
} catch (e) {
Logger.error('iOS语音交互服务初始化失败: $e');
Logger.error('iOS语音交互服务初始化失败: ${e.toString()}');
}
return this;
@ -110,13 +110,13 @@ class IosVoiceInteractionService extends GetxService {
// 监听识别结果
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent, onError: (error) {
Logger.error('语音识别错误: $error');
Logger.error('语音识别错误: ${error.toString()}');
_startContinuousListening(); // 尝试重新启动
});
Logger.info('开始连续语音识别');
} catch (e) {
Logger.error('启动语音识别失败: $e');
Logger.error('启动语音识别失败: ${e.toString()}');
}
}
@ -263,7 +263,7 @@ class IosVoiceInteractionService extends GetxService {
// 处理完成
isProcessingAI.value = false;
} catch (e) {
Logger.error('AI响应处理失败: $e');
Logger.error('AI响应处理失败: ${e.toString()}');
// 错误恢复
isProcessingAI.value = false;
}
@ -437,7 +437,7 @@ class IosVoiceInteractionService extends GetxService {
Logger.info('保存聊天历史记录成功');
} catch (e) {
Logger.error('保存聊天历史记录失败: $e');
Logger.error('保存聊天历史记录失败: ${e.toString()}');
}
}
@ -463,4 +463,4 @@ class IosVoiceInteractionService extends GetxService {
_eventStreamController.add(event);
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:get/get.dart';
import 'package:agent_service/agent_service.dart';
import 'package:logger/logger.dart';
import 'package:chat_storage/chat_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:app_settings/app_settings.dart';
import 'package:deep_voice/core/utils/logger.dart';
class Message {
final bool isUser;
@ -35,8 +35,6 @@ class Message {
class AgentController extends GetxController {
static AgentController get to => Get.find();
final logger = Logger();
// 聊天消息列表
final messages = <Message>[].obs;
@ -77,6 +75,8 @@ class AgentController extends GetxController {
bool _isNewAssistantResponse = true;
String _currentResponseId = '';
final TAG = 'AgentController';
// 会话ID
final String sessionId = 'default_agent';
@ -108,7 +108,7 @@ class AgentController extends GetxController {
isTtsEnabled.value = storage.read('agent_tts_enabled') ?? true;
} catch (e) {
// 如果读取失败,使用默认值
logger.e('加载TTS偏好设置失败: $e');
Logger.e(TAG, '加载TTS偏好设置失败: $e');
isTtsEnabled.value = true;
}
}
@ -127,14 +127,14 @@ class AgentController extends GetxController {
final storage = GetStorage();
await storage.write('agent_tts_enabled', isTtsEnabled.value);
} catch (e) {
logger.e('保存TTS偏好设置失败: $e');
Logger.e(TAG, '保存TTS偏好设置失败: $e');
}
}
// 加载聊天历史记录
Future<void> _loadChatHistory() async {
try {
logger.i('正在加载聊天历史...');
Logger.i(TAG, '正在加载聊天历史...');
final result = await ChatStorage.getMessages(
sessionId: sessionId,
page: 1,
@ -159,14 +159,14 @@ class AgentController extends GetxController {
historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
messages.assignAll(historyMessages);
logger.i('成功加载 ${historyMessages.length} 条聊天记录');
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录');
} else {
// 没有历史记录时添加欢迎消息
_addWelcomeMessage();
logger.i('没有历史记录,添加欢迎消息');
Logger.i(TAG, '没有历史记录,添加欢迎消息');
}
} catch (e) {
logger.e('加载聊天历史失败: $e');
Logger.e(TAG, '加载聊天历史失败: $e');
_addWelcomeMessage();
} finally {
isHistoryLoaded.value = true;
@ -271,12 +271,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.ttsStarted:
isSpeaking.value = true;
Logger.i(TAG, 'TTS开始播放');
break;
case AgentServiceEventType.ttsCompleted:
case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled:
isSpeaking.value = false;
Logger.i(TAG, 'TTS停止播放');
break;
case AgentServiceEventType.imageProcessing:
@ -338,7 +340,7 @@ class AgentController extends GetxController {
messages.refresh();
}
logger.e('代理服务错误: ${event.data['message']}');
Logger.e(TAG, '代理服务错误: ${event.data['message']}');
break;
case AgentServiceEventType.autoStop:
@ -372,14 +374,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.functionCall:
final functionName = event.data['name'] ?? '';
final arguments = event.data['arguments'] ?? '';
// logger.i('Fultter 开始调用 mcp: $functionName, $arguments');
// debugPrint('Fultter 开始调用 mcp: $functionName, $arguments');
break;
case AgentServiceEventType.functionCallResult:
final responseId = event.data['responseId'] ?? '';
final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? '';
final functionCall = event.data['function_call'] ?? '';
logger.i('Flutter 结束调用 mcp: $result, $functionCall');
Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall');
if (result.isNotEmpty) {
final parsedResult = jsonDecode(result);
final meta = parsedResult["meta"];
@ -389,7 +391,7 @@ final responseId = event.data['responseId'] ?? '';
try {
metaResult = jsonDecode(meta);
} catch (e) {
print('解析meta数据失败: $e');
Logger.e(TAG, '解析meta数据失败: $e');
}
}
if(metaResult.isNotEmpty){
@ -451,7 +453,7 @@ final responseId = event.data['responseId'] ?? '';
await AgentService.processTextInput(text,
speakResponse: isTtsEnabled.value);
} catch (e) {
logger.e('发送文本消息失败: $e');
Logger.e(TAG, '发送文本消息失败: $e');
isProcessing.value = false;
}
}
@ -459,7 +461,7 @@ final responseId = event.data['responseId'] ?? '';
// 发送图片消息
Future<void> sendImageMessage(String imagePath, {String? text}) async {
if (imagePath.isEmpty || !File(imagePath).existsSync()) {
logger.e('图片不存在: $imagePath');
Logger.e(TAG, '图片不存在: $imagePath');
return;
}
@ -493,7 +495,7 @@ final responseId = event.data['responseId'] ?? '';
speakResponse: isTtsEnabled.value,
);
} catch (e) {
logger.e('处理图片失败: $e');
Logger.e(TAG, '处理图片失败: $e');
isProcessing.value = false;
isImageProcessing.value = false;
}
@ -554,7 +556,7 @@ final responseId = event.data['responseId'] ?? '';
if (!status.isGranted) {
allGranted = false;
deniedPermission = permission;
logger.e('权限 ${permission.toString()} 未被授予: $status');
Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
}
});
@ -579,7 +581,7 @@ final responseId = event.data['responseId'] ?? '';
return false;
} catch (e) {
logger.e('检查麦克风权限失败: $e');
Logger.e(TAG, '检查麦克风权限失败: $e');
return false;
}
}
@ -592,7 +594,7 @@ final responseId = event.data['responseId'] ?? '';
// 在实际应用中,你可能需要使用platform channel获取真实的SDK版本
return 31; // 对应Android 12
} catch (e) {
logger.e('获取Android SDK版本失败: $e');
Logger.e(TAG, '获取Android SDK版本失败: $e');
}
}
return 0;
@ -641,7 +643,7 @@ final responseId = event.data['responseId'] ?? '';
try {
await AgentService.startConversation();
} catch (e) {
logger.e('启动语音输入失败: $e');
Logger.e(TAG, '启动语音输入失败: $e');
}
}
@ -652,7 +654,7 @@ final responseId = event.data['responseId'] ?? '';
try {
await AgentService.stopConversation();
} catch (e) {
logger.e('停止语音输入失败: $e');
Logger.e(TAG, '停止语音输入失败: $e');
}
}
@ -670,7 +672,7 @@ final responseId = event.data['responseId'] ?? '';
// 重置回复状态
_isNewAssistantResponse = true;
} catch (e) {
logger.e('清除聊天历史失败: $e');
Logger.e(TAG, '清除聊天历史失败: $e');
}
}
@ -692,7 +694,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true;
}
} catch (e) {
logger.e('选择图片失败: $e');
Logger.e(TAG, '选择图片失败: $e');
}
}
@ -714,7 +716,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true;
}
} catch (e) {
logger.e('拍照失败: $e');
Logger.e(TAG, '拍照失败: $e');
}
}
@ -756,7 +758,7 @@ final responseId = event.data['responseId'] ?? '';
// 播放音乐
void musicStatus() async {
var status = await _musiceManager.getStatus();
// logger.i('获取当前音乐播放状态 $status');
// debugPrint('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying'];
currentUrl.value = status['url'];
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -53,5 +53,6 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependencies {
// 添加Microsoft语音SDK
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.Result
import io.flutter.plugin.common.EventChannel
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import com.microsoft.cognitiveservices.speech.audio.*
import com.yunqiinnovation.ble_service.BleService
import com.yunqiinnovation.ble_service.BleConst
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
/** AzureSpeechPlugin */
class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
class AzureSpeechPlugin: FlutterPlugin {
private val tag = "AzureSpeechPlugin"
private lateinit var context: Context
private val mainHandler = Handler(Looper.getMainLooper())
// 协程相关
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
// ASR相关
private lateinit var asrChannel : MethodChannel
@ -38,9 +33,8 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
private lateinit var ttsEventChannel: EventChannel
private var ttsEventSink: EventChannel.EventSink? = null
private lateinit var azureTtsHelper: AzureTtsHelper
// 是否已注册BLE回调
private var isRegisteredToBle = false
// 是否已添加TTS事件监听器
private var isTtsListenerAdded = false
// ASR 事件发送方法
private fun sendAsrEvent(event: Map<String, Any>) {
@ -66,7 +60,6 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
}
// 确保在主线程上发送事件
mainHandler.post {
try {
ttsEventSink?.success(event)
@ -104,6 +97,7 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
ttsEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
ttsEventSink = events
setupTtsEventListener() // 在事件通道准备好时设置TTS事件监听器
}
override fun onCancel(arguments: Any?) {
@ -114,19 +108,53 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
// 初始化Azure语音服务
azureTtsHelper = AzureTtsHelper(context)
azureAsrHelper = AzureAsrHelper(context)
}
// 2. 初始化BleService并注册回调
if (BleService.initialize(context)) {
if (BleService.addCallback(this)) {
isRegisteredToBle = true
FileLogger.d(tag, "已注册BLE回调")
} else {
FileLogger.w(tag, "BLE回调注册失败")
}
} else {
FileLogger.e(tag, "BLE服务初始化失败")
/**
* 设置TTS事件监听器
*/
private fun setupTtsEventListener() {
if (!isTtsListenerAdded) {
// 添加TTS事件监听器
azureTtsHelper.addListener(object : TtsEventListener {
override fun onEvent(event: TtsEvent) {
// 将TTS事件转换为Flutter可理解的格式
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方法处理器
@ -265,6 +293,11 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
azureAsrHelper.dispose()
result.success(true)
}
"pushAudioData" -> {
val data = call.argument<ByteArray>("data") ?: return result.error("INVALID_ARGUMENTS", "音频数据不能为空", null)
azureAsrHelper.pushAudioData(data)
result.success(true)
}
else -> {
result.notImplemented()
}
@ -279,34 +312,36 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
"initialize" -> {
val subscriptionKey = call.argument<String>("subscriptionKey") ?: ""
val region = call.argument<String>("region") ?: ""
val useCustomAudioOutput = call.argument<Boolean>("useCustomAudioOutput") ?: false // 默认使用自定义播放器
val useCustomAudioOutput = call.argument<Boolean>("useCustomAudioOutput") ?: false
// 先释放现有资源
azureTtsHelper.dispose()
// 创建TTS回调
val ttsCallback = createTtsCallback()
// 初始化TTS引擎
val success = azureTtsHelper.initialize(
subscriptionKey = subscriptionKey,
region = region,
callback = ttsCallback
)
// 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器
if (success && useCustomAudioOutput) {
// 创建SimpleAudioPlayer实例
val customPlayer = SimpleAudioPlayer(context)
try {
// 初始化TTS引擎(使用新的接口方法)
val success = azureTtsHelper.initialize(
ttsAppId = "", // Azure TTS不需要appId
ttsAppToken = subscriptionKey,
ttsResource = region,
language = "zh-CN"
)
// 获取并设置音频输出流
val outputStream = customPlayer.getAudioOutputStream()
if (outputStream != null) {
azureTtsHelper.setCustomAudioOutputStream(outputStream)
// 如果初始化成功且需要使用自定义音频输出,则设置自定义音频播放器
if (success && useCustomAudioOutput) {
// 创建SimpleAudioPlayer实例
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" -> {
val voiceName = call.argument<String>("voiceName") ?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null)
@ -315,18 +350,28 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
"speakText" -> {
val text = call.argument<String>("text") ?: return result.error("INVALID_ARGUMENTS", "文本不能为空", null)
// 直接传递参数给speakText方法
val success = azureTtsHelper.speakText(text)
result.success(success)
// 直接调用speakOnce方法
try {
val success = azureTtsHelper.speakOnce(text)
result.success(success)
} catch (e: Exception) {
result.error("SPEAK_ERROR", "语音合成失败: ${e.message}", null)
}
}
"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" -> {
result.success(azureTtsHelper.isSpeaking())
}
"dispose" -> {
azureTtsHelper.dispose()
azureTtsHelper.release()
result.success(true)
}
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) {
@ -374,49 +388,11 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope {
ttsEventChannel.setStreamHandler(null)
try {
// 取消所有协程
job.cancel()
// 取消BLE回调注册
if (isRegisteredToBle) {
BleService.removeCallback(this)
isRegisteredToBle = false
}
// 释放资源
azureTtsHelper.dispose()
azureTtsHelper.release()
azureAsrHelper.dispose()
} catch (e: Exception) {
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.audio.*
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 java.io.ByteArrayInputStream
import java.io.InputStream
import kotlin.coroutines.CoroutineContext
/**
* Azure TTS Helper
@ -15,18 +19,13 @@ import kotlin.coroutines.CoroutineContext
* 基于微软Azure语音服务的TTS实现
* 参考文档: 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 {
private const val TAG = "AzureTtsHelper"
private const val DEFAULT_LANGUAGE = "zh-CN"
private const val DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural"
}
// 协程上下文
private val job = SupervisorJob()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
// Azure语音服务配置
private var speechConfig: SpeechConfig? = null
private var synthesizer: SpeechSynthesizer? = null
@ -39,8 +38,9 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
private var currentPitch = "0%"
private var currentVolume = "100%"
// 全局回调
private var ttsCallback: TtsCallback? = null
// 事件监听器列表
private val eventListeners = mutableListOf<TtsEventListener>()
private val audioDataListeners = mutableListOf<AudioDataListener>()
// 自定义音频输出流
private var customAudioOutputStream: PushAudioOutputStream? = null
@ -48,50 +48,28 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
// 流式文本处理的缓冲区
private val streamBuffer = StringBuilder()
private var lastSpeakTime = 0L
/**
* 简化的TTS回调接口
*/
interface TtsCallback {
/**
* 合成开始
*/
fun onSynthesisStarted() {}
/**
* 合成完成
*/
fun onSynthesisCompleted() {}
/**
* 合成取消
*/
fun onSynthesisCanceled() {}
}
/**
* 初始化TTS引擎
*
* @param subscriptionKey Azure语音服务订阅密钥
* @param region Azure语音服务区域
* @param callback 可选,全局TTS回调接口
* @param ttsAppId 未使用,保留为空即可
* @param ttsAppToken Azure语音服务订阅密钥
* @param ttsResource Azure语音服务区域
* @param language 语言代码,如"zh-CN"
* @return 是否初始化成功
*/
fun initialize(
subscriptionKey: String,
region: String,
callback: TtsCallback? = null
override fun initialize(
ttsAppId: String,
ttsAppToken: String,
ttsResource: String,
language: String
): Boolean {
try {
// 释放之前的资源
dispose()
// 存储全局回调
ttsCallback = callback
// 创建语音配置
speechConfig = SpeechConfig.fromSubscription(subscriptionKey, region)
// speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Audio24Khz48KBitRateMonoMp3)
speechConfig = SpeechConfig.fromSubscription(ttsAppToken, ttsResource)
speechConfig?.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm)
speechConfig?.setSpeechSynthesisVoiceName(currentVoice)
@ -111,7 +89,7 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
setupEventListeners()
isInitialized = true
FileLogger.d(TAG, "TTS引擎初始化成功: 区域=$region")
FileLogger.d(TAG, "TTS引擎初始化成功: 区域=$ttsResource")
return true
} catch (e: Exception) {
@ -144,12 +122,51 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
}
/**
* 设置TTS回调
*
* @param callback TTS回调接口
* 添加TTS事件监听器
*/
override fun addListener(listener: TtsEventListener) {
if (!eventListeners.contains(listener)) {
eventListeners.add(listener)
}
}
/**
* 移除TTS事件监听器
*/
fun setTtsCallback(callback: TtsCallback?) {
ttsCallback = callback
override fun removeListener(listener: TtsEventListener) {
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 {
// 合成开始事件
SynthesisStarted?.addEventListener { _, _ ->
ttsCallback?.onSynthesisStarted()
notifyEvent(TtsEventType.SYNTHESIS_STARTED)
}
// 合成完成事件
SynthesisCompleted?.addEventListener { _, _ ->
isSpeaking = false
ttsCallback?.onSynthesisCompleted()
notifyEvent(TtsEventType.SYNTHESIS_COMPLETED)
}
// 合成取消事件
SynthesisCanceled?.addEventListener { _, _ ->
SynthesisCanceled?.addEventListener { _, eventArgs ->
FileLogger.d(TAG, "语音合成取消")
isSpeaking = false
ttsCallback?.onSynthesisCanceled()
notifyEvent(TtsEventType.SYNTHESIS_CANCELED)
}
}
}
/**
* 设置语音
* 设置语音角色
*/
fun setVoice(voiceName: String): Boolean {
override fun setVoice(voiceName: String): Boolean {
if (!isInitialized) return false
try {
@ -190,6 +208,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
return true
} catch (e: Exception) {
FileLogger.e(TAG, "设置语音失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "VOICE_SET_FAILED",
"errorMessage" to "设置语音失败: ${e.message}"
))
return false
}
}
@ -215,6 +237,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
setupEventListeners()
} catch (e: Exception) {
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
} catch (e: Exception) {
FileLogger.e(TAG, "设置语音参数失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "PARAMS_SET_FAILED",
"errorMessage" to "设置语音参数失败: ${e.message}"
))
return false
}
}
@ -246,50 +276,52 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
* 生成SSML
*/
private fun generateSsml(rawText: String): String {
// 1. 定义要静音的符号和表情符号列表
val symbolsToMute = listOf(
"#", "*", "@", "%", "^", "&",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
)
// 1. 定义要静音的符号和表情符号列表
val symbolsToMute = listOf(
"#", "*", "@", "%", "^", "&",
"😀", "😂", "😊", "😍", "😢", "😎", "😉", "👍", "🙌", "🎉"
)
// 2. 转义 XML 保留字符
val escapedText = rawText
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
// 2. 转义 XML 保留字符
val escapedText = rawText
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
// 3. 静音处理特殊符号和表情符号
// 使用空白替换法,直接将符号替换为空格
var processedText = escapedText
symbolsToMute.forEach { sym ->
processedText = processedText.replace(sym, "")
}
// 3. 静音处理特殊符号和表情符号
// 使用空白替换法,直接将符号替换为空格
var processedText = escapedText
symbolsToMute.forEach { sym ->
processedText = processedText.replace(sym, "")
}
// 4. 构造简化的SSML文档,减少嵌套层级
return """
<speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN">
<voice name="$currentVoice">
<mstts:express-as style="cheerful">
<prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume">
<say-as interpret-as="text">$processedText</say-as>
</prosody>
</mstts:express-as>
</voice>
</speak>
""".trimIndent()
}
// 4. 构造简化的SSML文档,减少嵌套层级
return """
<speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN">
<voice name="$currentVoice">
<mstts:express-as style="cheerful">
<prosody rate="$currentRate" pitch="$currentPitch" volume="$currentVolume">
<say-as interpret-as="text">$processedText</say-as>
</prosody>
</mstts:express-as>
</voice>
</speak>
""".trimIndent()
}
/**
* 合成文本为语音
*
* @param text 要合成的文本
* @return 是否成功开始合成
* 单次播放文本(非流式)
*/
fun speakText(text: String): Boolean {
override fun speakOnce(text: String): Boolean {
if (!isInitialized) {
FileLogger.e(TAG, "TTS引擎未初始化")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
return false
}
@ -299,38 +331,47 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
isSpeaking = true
// 使用纯异步方式
// 直接调用SpeakSsmlAsync,SDK内部已经是异步的
synthesizer?.SpeakSsmlAsync(ssml)
// 返回true表示已开始合成请求
return true
} catch (e: Exception) {
isSpeaking = false
FileLogger.e(TAG, "语音合成异常: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "SYNTHESIS_ERROR",
"errorMessage" to "语音合成异常: ${e.message}"
))
return false
}
}
/**
* 处理流式文本并在遇到标点符号时播放
*
* @param text 收到的文本流片段
* @return 是否成功处理
* 流式合成文本
*/
fun speakStream(text: String): Boolean {
override fun speakStream(text: String): Boolean {
if (!isInitialized || text.isBlank()) {
if (!isInitialized) {
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
}
return false
}
try {
// 添加新文本到缓冲区
streamBuffer.append(text)
// 增加500ms防抖逻辑
val currentTime = System.currentTimeMillis()
if (currentTime - lastSpeakTime < 600) {
return true
}
lastSpeakTime = currentTime
val currentText = streamBuffer.toString()
// 定义标点符号列表
@ -354,24 +395,30 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
streamBuffer.delete(0, lastPunctuationIndex + 1)
// 播放提取的文本
return speakText(textToSpeak)
return speakOnce(textToSpeak)
}
// 如果没有找到标点符号,则等待更多文本
return true
} catch (e: Exception) {
FileLogger.e(TAG, "流式语音合成失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "STREAM_FAILED",
"errorMessage" to "流式语音合成失败: ${e.message}"
))
return false
}
}
/**
* 播放剩余的流式文本
*
* @return 是否成功播放剩余文本
*/
fun flushStream(): Boolean {
override fun flushStream(): Boolean {
if (!isInitialized) {
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "NOT_INITIALIZED",
"errorMessage" to "TTS引擎未初始化"
))
return false
}
@ -388,9 +435,13 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
}
// 播放剩余文本
return speakText(remainingText)
return speakOnce(remainingText)
} catch (e: Exception) {
FileLogger.e(TAG, "刷新流式文本失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "FLUSH_FAILED",
"errorMessage" to "刷新流式文本失败: ${e.message}"
))
return false
}
}
@ -398,7 +449,7 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
/**
* 停止当前语音合成
*/
fun stopSpeaking(): Boolean {
override fun stop(): Boolean {
if (!isInitialized) return false
try {
@ -407,6 +458,11 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
isSpeaking = false
return true
} catch (e: Exception) {
FileLogger.e(TAG, "停止语音合成失败: ${e.message}")
notifyEvent(TtsEventType.ERROR, mapOf(
"errorCode" to "STOP_FAILED",
"errorMessage" to "停止语音合成失败: ${e.message}"
))
return false
}
}
@ -414,11 +470,25 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
/**
* 释放资源
*/
fun dispose() {
override fun release() {
dispose()
}
/**
* 释放资源(内部方法)
*/
private fun dispose() {
try {
stopSpeaking()
// 停止播放
stop()
// 关闭合成器
synthesizer?.close()
synthesizer = null
// 关闭配置
speechConfig?.close()
speechConfig = null
// 关闭自定义音频输出流
try {
@ -428,15 +498,10 @@ class AzureTtsHelper(private val context: Context) : CoroutineScope {
// 忽略关闭异常
}
// 清理引用
ttsCallback = null
// 清空流缓冲区
streamBuffer.clear()
// 取消所有协程
job.cancel()
// 重置状态
isInitialized = false
isSpeaking = false
} catch (e: Exception) {

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

@ -3,7 +3,24 @@ version = "1.0.0"
plugins {
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 {
@ -22,15 +39,18 @@ android {
kotlinOptions {
jvmTarget = "1.8"
languageVersion = "1.7"
apiVersion = "1.7"
}
}
dependencies {
implementation(kotlin("stdlib-jdk7"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android: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:logging-interceptor:4.11.0")
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) {
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.launch
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
/**
@ -168,6 +172,7 @@ class BytedanceSpeechPlugin : FlutterPlugin {
"disposeTTS" -> {
disposeTTS(result)
}
else -> {
result.notImplemented()
}
@ -235,33 +240,45 @@ class BytedanceSpeechPlugin : FlutterPlugin {
bytedanceTTS?.release()
// 创建新的TTS实例
bytedanceTTS = BytedanceTTS(
context = context,
// 音频数据回调
audioCallback = { audioData ->
sendTTSAudioData(audioData)
},
// 完成回调
completionCallback = {
sendTTSEvent("completed", null)
},
// 错误回调
errorCallback = { code, message ->
val params = HashMap<String, Any>()
params["code"] = code
params["message"] = message
sendTTSEvent("error", params)
},
// 状态回调
statusCallback = { status ->
val params = HashMap<String, Any>()
params["status"] = status
sendTTSEvent("status", params)
bytedanceTTS = BytedanceTTS(context)
// 添加事件监听器
bytedanceTTS?.addListener(object : TtsEventListener {
override fun onEvent(event: TtsEvent) {
when (event.type) {
TtsEventType.SYNTHESIS_STARTED -> {
sendTTSEvent("started", null)
}
TtsEventType.SYNTHESIS_COMPLETED -> {
sendTTSEvent("completed", null)
}
TtsEventType.SYNTHESIS_CANCELED -> {
sendTTSEvent("canceled", null)
}
TtsEventType.ERROR -> {
val params = HashMap<String, Any>()
params["code"] = event.params["errorCode"] ?: "UNKNOWN_ERROR"
params["message"] = event.params["errorMessage"] ?: "未知错误"
sendTTSEvent("error", params)
}
}
}
)
})
// 添加音频数据监听器
bytedanceTTS?.addAudioDataListener(object : AudioDataListener {
override fun onAudioData(data: ByteArray) {
sendTTSAudioData(data)
}
})
// 初始化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) {
result.success(true)
@ -314,18 +331,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return
}
scope.launch {
try {
val success = bytedanceTTS?.speakOnce(text) ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "合成文本异常: ${e.message}", e)
result.error(
"SPEAK_EXCEPTION",
"合成文本异常: ${e.message}",
null
)
}
try {
val success = bytedanceTTS?.speakOnce(text) ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "合成文本异常: ${e.message}", e)
result.error(
"SPEAK_EXCEPTION",
"合成文本异常: ${e.message}",
null
)
}
}
@ -338,18 +353,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return
}
scope.launch {
try {
val success = bytedanceTTS?.speakStream(text) ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "流式合成异常: ${e.message}", e)
result.error(
"STREAM_EXCEPTION",
"流式合成异常: ${e.message}",
null
)
}
try {
val success = bytedanceTTS?.speakStream(text) ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "流式合成异常: ${e.message}", e)
result.error(
"STREAM_EXCEPTION",
"流式合成异常: ${e.message}",
null
)
}
}
@ -362,18 +375,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return
}
scope.launch {
try {
val success = bytedanceTTS?.flushStream() ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "刷新流异常: ${e.message}", e)
result.error(
"FLUSH_EXCEPTION",
"刷新流异常: ${e.message}",
null
)
}
try {
val success = bytedanceTTS?.flushStream() ?: false
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "刷新流异常: ${e.message}", e)
result.error(
"FLUSH_EXCEPTION",
"刷新流异常: ${e.message}",
null
)
}
}
@ -386,18 +397,16 @@ class BytedanceSpeechPlugin : FlutterPlugin {
return
}
scope.launch {
try {
bytedanceTTS?.stop()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "停止TTS异常: ${e.message}", e)
result.error(
"STOP_EXCEPTION",
"停止TTS异常: ${e.message}",
null
)
}
try {
val success = bytedanceTTS?.stop() ?: true
result.success(success)
} catch (e: Exception) {
Log.e(TAG, "停止TTS异常: ${e.message}", e)
result.error(
"STOP_EXCEPTION",
"停止TTS异常: ${e.message}",
null
)
}
}
@ -418,6 +427,7 @@ class BytedanceSpeechPlugin : FlutterPlugin {
)
}
}
/**
* 发送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.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.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CancellationException
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -11,24 +22,16 @@ import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.concurrent.atomic.AtomicBoolean
/**
* 火山引擎大模型语音合成实现
*
* 基于WebSocket协议实现双向流式TTS
*/
class BytedanceTTS(
private val context: Context,
private val audioCallback: (ByteArray) -> Unit,
private val completionCallback: () -> Unit,
private val errorCallback: (String, String) -> Unit,
private val statusCallback: (String) -> Unit
) {
class BytedanceTTS(private val context: Context) : ITtsService, CoroutineScope {
companion object {
private const val TAG = "BytedanceTTS"
@ -80,8 +83,23 @@ class BytedanceTTS(
private const val STATUS_SPEAKING = "speaking"
private const val STATUS_STOPPED = "stopped"
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连接
private var webSocket: WebSocket? = null
private val client: OkHttpClient
@ -94,8 +112,8 @@ class BytedanceTTS(
// TTS配置参数
private var appId: String = ""
private var accessToken: String = ""
private var speaker: String = "zh_female_shuangkuaisisi_moon_bigtts" // 默认语音,使用Java示例的音色
private var format: String = "mp3" // 使用mp3格式
private var speaker: String = "zh_female_shuangkuaisisi_emo_v2_mars_bigtts" // 默认语音,使用Java示例的音色
private var format: String = "pcm" // 使用mp3格式
private var sampleRate: Int = 24000 // 使用24kHz采样率
private var enableTimestamp: Boolean = false
private var language: String = "zh-CN"
@ -103,6 +121,16 @@ class BytedanceTTS(
// 流式处理状态
private var isStreamMode = false
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 {
// 配置OkHttp客户端
@ -113,61 +141,161 @@ class BytedanceTTS(
.build()
// 生成会话ID
sessionId = UUID.randomUUID().toString().replace("-", "")
Log.d(TAG, "BytedanceTTS实例化完成,会话ID: $sessionId")
// sessionId = UUID.randomUUID().toString().replace("-", "")
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 {
updateStatus(STATUS_INITIALIZING)
// 设置必要参数
this@BytedanceTTS.appId = ttsAppId // 火山引擎中的应用ID
this@BytedanceTTS.accessToken = ttsAppToken // 火山引擎访问令牌
this@BytedanceTTS.language = language
// 释放旧连接
release()
// 建立新连接
connect()
// 设置基本参数
this.appId = ttsAppId
this.accessToken = ttsAppToken
this.language = language
// 等待连接和会话建立
var attempts = 0
while (!isSessionStarted && attempts < 10) {
delay(500) // 增加等待时间
attempts++
Log.d(TAG, "等待会话建立...尝试 $attempts/10")
}
// 重置连接状态
connectionAttempts = 0
isConnecting.set(false)
lastConnectionAttemptTime = 0
if (!isSessionStarted) {
Log.e(TAG, "初始化超时: 无法建立会话")
errorCallback("INIT_TIMEOUT", "初始化超时: 无法在5秒内建立会话")
return@withContext false
}
// 释放旧连接
webSocket?.cancel()
webSocket = null
isConnected = false
isSessionStarted = false
Log.d(TAG, "字节跳动TTS初始化成功,会话ID: $sessionId")
true
return true
} catch (e: Exception) {
Log.e(TAG, "初始化失败: ${e.message}", e)
errorCallback("INIT_ERROR", e.message ?: "初始化失败")
Log.e(TAG, "初始化失败: ${e.message}")
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 {
speaker = voiceName
return true
} 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
}
}
@ -175,140 +303,298 @@ class BytedanceTTS(
/**
* 单次播放(非流式)
*/
suspend fun speakOnce(text: String): Boolean = withContext(Dispatchers.IO) {
override fun speakOnce(text: String): Boolean {
try {
if (text.isEmpty()) {
errorCallback("PARAM_ERROR", "合成文本不能为空")
return@withContext false
return false
}
isStreamMode = false
// 如果没有连接或会话,重新初始化
if (webSocket == null || !isConnected) {
connect()
// 等待连接和会话建立
var attempts = 0
while (!isSessionStarted && attempts < 10) {
delay(300)
attempts++
launch {
if (!ensureSessionReady()) {
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "SESSION_ERROR",
"errorMessage" to "无法建立会话"))
return@launch
}
if (!isSessionStarted) {
errorCallback("SESSION_ERROR", "无法建立会话")
return@withContext false
}
sendTextRequest(text)
}
// 发送文本进行合成
sendTextRequest(text)
return@withContext true
return true
} catch (e: Exception) {
Log.e(TAG, "语音合成失败: ${e.message}", e)
errorCallback("SYNTH_ERROR", e.message ?: "语音合成失败")
Log.e(TAG, "语音合成失败: ${e.message}")
updateStatus(STATUS_ERROR)
false
return false
}
}
/**
* 流式播放开始
*/
suspend fun speakStream(text: String): Boolean = withContext(Dispatchers.IO) {
override fun speakStream(text: String): Boolean {
try {
if (text.isEmpty()) {
return@withContext true // 空文本直接返回成功
return true
}
Log.d(TAG, "开始流式合成,text: $text")
isStreamMode = true
// 确保会话已就绪
ensureSessionReady()
if (!isSessionStarted && !ensureSessionReady()) {
Log.e(TAG, "流式播放失败:无法建立会话")
return false
}
// 发送文本进行合成,但不自动结束会话
sendStreamTextRequest(text)
updateStatus(STATUS_SPEAKING)
return@withContext true
// 如果文本处理协程不活跃,重新启动它
if (textProcessingJob == null || textProcessingJob?.isActive == false) {
startTextProcessing()
}
// Log.d(TAG, "speakStream $sessionId, $text ")
textChannel.trySend(text)
return true
} catch (e: Exception) {
Log.e(TAG, "流式合成失败: ${e.message}", e)
errorCallback("STREAM_ERROR", e.message ?: "流式合成失败")
Log.e(TAG, "流式合成失败: ${e.message}")
updateStatus(STATUS_ERROR)
false
return false
}
}
/**
* 结束流式播放
*/
suspend fun flushStream(): Boolean = withContext(Dispatchers.IO) {
override fun flushStream(): Boolean {
try {
if (!isStreamMode) {
return@withContext true
}
if (isSessionStarted) {
finishSession()
if (!isStreamMode) return true
// Log.d(TAG, "flushStream: $sessionId")
launch {
withContext(Dispatchers.IO) {
delay(300) // 确保现有文本处理完成
}
if (isSessionStarted) {
isSessionStarted = false
finishSession()
}
}
isStreamMode = false
updateStatus(STATUS_STOPPED)
return@withContext true
return true
} catch (e: Exception) {
Log.e(TAG, "结束流式合成失败: ${e.message}", e)
errorCallback("FLUSH_ERROR", e.message ?: "结束流式合成失败")
false
Log.e(TAG, "结束流式合成失败: ${e.message}")
return false
}
}
/**
* 停止语音合成
* 释放资源
*/
suspend fun stop() = withContext(Dispatchers.IO) {
if (isSessionStarted) {
finishSession()
override fun release() {
Log.d(TAG, "BytedanceTTS release called.")
// 关闭文本通道
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() {
webSocket?.cancel()
webSocket = null
isConnected = false
isSessionStarted = false
updateStatus(STATUS_STOPPED)
override fun removeListener(listener: TtsEventListener) {
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) {
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() {
// 如果没有连接或会话,重新初始化
if (webSocket == null || !isConnected) {
connect()
// 等待连接和会话建立
private fun ensureSessionReady(): Boolean {
// 如果已连接和会话已开始,直接返回成功
if (isConnected && isSessionStarted) {
return true
}
// 如果已连接但会话未开始,只需要开始会话
if (isConnected && !isSessionStarted) {
Log.d(TAG, "WebSocket已连接,正在启动新会话...")
return startSessionOnly()
}
// 如果正在连接中,等待连接完成
if (isConnecting.get()) {
Log.d(TAG, "已有连接正在进行中,等待其完成...")
// 等待连接完成,最多5秒
var attempts = 0
while (!isSessionStarted && attempts < 10) {
delay(300)
attempts++
while (isConnecting.get() && attempts < 10) {
try {
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 {
Thread.sleep(ms)
} catch (e: InterruptedException) {
// 忽略中断异常
// 重新生成会话ID
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) {
currentStatus = status
statusCallback(status)
}
/**
* 建立WebSocket连接
*/
private fun connect() {
webSocket?.cancel()
webSocket = null
val url = "wss://openspeech.bytedance.com/api/v3/tts/bidirection"
val connectId = UUID.randomUUID().toString()
val request = Request.Builder()
.url(url)
.header("X-Api-App-Key", appId)
.header("X-Api-Access-Key", accessToken)
.header("X-Api-Resource-Id", "volc.service_type.10029")
.header("X-Api-Connect-Id", UUID.randomUUID().toString())
.header("X-Api-Connect-Id", connectId)
.build()
Log.d(TAG, "正在连接WebSocket: $url, appId: $appId")
Log.d(TAG, "当前语音角色配置: speaker=$speaker, format=$format, sampleRate=$sampleRate")
isConnected = false
isSessionStarted = false
webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
val logId = response.header("X-Tt-Logid")
Log.d(TAG, "TTS WebSocket连接成功, X-Tt-Logid: $logId")
// 发送开始连接事件
isConnected = true
startConnection(webSocket)
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
try {
val data = bytes.toByteArray()
Log.d(TAG, "收到二进制消息,大小: ${data.size} 字节")
val response = parseResponse(data)
// 处理事件
when (response.event) {
EVENT_CONNECTION_FAILED, EVENT_SESSION_FAILED -> {
val errorMsg = if (response.responseMetaJson.isNotEmpty()) {
@ -360,99 +644,68 @@ class BytedanceTTS(
} else {
"连接或会话失败"
}
Log.e(TAG, "连接或会话失败: $errorMsg, 事件类型: ${response.event}")
errorCallback("TTS_ERROR", errorMsg)
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "TTS_ERROR",
"errorMessage" to errorMsg))
webSocket.close(1000, "Error")
updateStatus(STATUS_ERROR)
}
EVENT_CONNECTION_STARTED -> {
isConnected = true
Log.d(TAG, "连接已建立,开始TTS会话")
// 开始TTS会话
startTtsSession(webSocket)
isSpeaking = false // 错误情况下显式设置
isSessionStarted = false
isConnected = false
isConnecting.set(false)
}
EVENT_SESSION_STARTED -> {
isSessionStarted = true
Log.d(TAG, "TTS会话已开始,会话ID: $sessionId")
updateStatus(STATUS_READY)
connectionAttempts = 0
}
EVENT_TTS_RESPONSE -> {
// 音频数据
if (response.messageType == AUDIO_ONLY_RESPONSE && response.payload.isNotEmpty()) {
Log.d(TAG, "收到音频数据,大小: ${response.payload.size} 字节")
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}")
notifyAudioData(response.payload)
}
}
EVENT_TTS_SENTENCE_START -> {
Log.d(TAG, "句子合成开始")
}
EVENT_TTS_SENTENCE_END -> {
Log.d(TAG, "句子合成结束")
}
EVENT_CONNECTION_FINISHED -> {
isConnected = false
isSessionStarted = false
Log.d(TAG, "连接已结束")
updateStatus(STATUS_STOPPED)
completionCallback()
isConnecting.set(false)
webSocket.close(1000, "Connection finished")
}
EVENT_SESSION_FINISHED -> {
isSessionStarted = false
Log.d(TAG, "会话已结束,流模式: $isStreamMode")
if (!isStreamMode) {
if (!isStreamMode) {
finishConnection(webSocket)
} else {
// 流模式下保持连接
updateStatus(STATUS_READY)
}
}
else -> {
Log.d(TAG, "收到未处理的事件类型: ${response.event}")
}
}
} catch (e: Exception) {
Log.e(TAG, "解析响应失败: ${e.message}", e)
errorCallback("PARSE_ERROR", e.message ?: "解析响应失败")
Log.e(TAG, "解析响应失败: ${e.message}")
updateStatus(STATUS_ERROR)
isSpeaking = false // 错误情况下显式设置
isConnecting.set(false)
}
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
isConnected = false
isSessionStarted = false
Log.e(TAG, "连接失败: ${t.message}", t)
errorCallback("CONNECTION_ERROR", t.message ?: "连接失败")
isSpeaking = false
isConnecting.set(false)
Log.e(TAG, "WebSocket连接失败: ${t.message}")
notifyEvent(TtsEventType.ERROR, mapOf("errorCode" to "CONNECTION_ERROR",
"errorMessage" to (t.message ?: "连接失败")))
updateStatus(STATUS_ERROR)
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
isConnected = false
isSessionStarted = false
isSpeaking = false
isConnecting.set(false)
updateStatus(STATUS_STOPPED)
Log.d(TAG, "TTS WebSocket连接关闭: $code, $reason")
notifyEvent(TtsEventType.SYNTHESIS_CANCELED)
}
})
}
@ -533,15 +786,14 @@ class BytedanceTTS(
reqParams.put("speaker", speaker)
val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式
audioParams.put("sample_rate", 24000) // 提高采样率
audioParams.put("format", format)
audioParams.put("sample_rate", sampleRate)
audioParams.put("enable_timestamp", enableTimestamp)
reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams)
val payload = jsonObject.toString().toByteArray()
Log.d(TAG, "开始TTS会话请求: ${jsonObject.toString()}")
sendEvent(webSocket, header, optional, payload)
}
@ -574,12 +826,15 @@ class BytedanceTTS(
jsonObject.put("namespace", "BidirectionalTTS")
val reqParams = JSONObject()
reqParams.put("text", text)
// 将#和*替换为空格
val processedText = text.replace("#", " ").replace("*", " ")
reqParams.put("text", processedText)
reqParams.put("speaker", speaker)
val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式
audioParams.put("sample_rate", 24000) // 提高采样率
audioParams.put("format", format)
audioParams.put("sample_rate", sampleRate)
audioParams.put("emotion", "happy")
reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams)
@ -588,9 +843,6 @@ class BytedanceTTS(
Log.d(TAG, "发送文本合成请求: ${jsonObject.toString()}")
sendEvent(ws, header, optional, payload)
// 发送完文本后结束会话
updateStatus(STATUS_SPEAKING)
}
}
@ -622,18 +874,21 @@ class BytedanceTTS(
jsonObject.put("namespace", "BidirectionalTTS")
val reqParams = JSONObject()
reqParams.put("text", text)
// 将#和*替换为空格
val processedText = text.replace("#", " ").replace("*", " ")
reqParams.put("text", processedText)
reqParams.put("speaker", speaker)
val audioParams = JSONObject()
audioParams.put("format", "mp3") // 尝试改用mp3格式
audioParams.put("sample_rate", 24000) // 提高采样率
audioParams.put("format", format)
audioParams.put("sample_rate", sampleRate)
audioParams.put("emotion", "happy")
reqParams.put("audio_params", audioParams)
jsonObject.put("req_params", reqParams)
val payload = jsonObject.toString().toByteArray()
Log.d(TAG, "发送流式文本合成请求: ${jsonObject.toString()}")
// Log.d(TAG, "发送流式文本合成请求: ${jsonObject.toString()}")
sendEvent(ws, header, optional, payload)
// 流式模式下不自动结束会话
@ -663,6 +918,9 @@ class BytedanceTTS(
val payload = "{}".toByteArray()
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)
val success = webSocket.send(ByteString.of(*requestBytes))
Log.d(TAG, "发送WebSocket消息: 消息类型=${header.messageType}, 事件=${optional.event}, 大小=${requestBytes.size}字节, 成功=$success")
webSocket.send(ByteString.of(*requestBytes))
} catch (e: Exception) {
Log.e(TAG, "发送WebSocket消息失败: ${e.message}", e)
Log.e(TAG, "发送WebSocket消息失败: ${e.message}")
}
}
@ -972,4 +1229,5 @@ class BytedanceTTS(
return result
}
}
}

2
local_plugins/classic_bluetooth/pubspec.yaml

@ -9,8 +9,6 @@ environment:
dependencies:
flutter:
sdk: flutter
deepvoice_utils:
path: ../deepvoice_utils
flutter:
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

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

@ -9,7 +9,7 @@ plugins {
android {
// 命名空间,对应你插件的包名(需与代码内包名保持一致)
namespace = "com.yunqiinnovation.open_ai_service"
namespace = "com.deep_voice.speech"
// 目标 SDK 版本
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"
* @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 要合成的文本
* @return 是否成功开始合成
*/
suspend fun speakOnce(text: String): Boolean
fun speakOnce(text: String): Boolean
/**
* 流式合成
@ -40,21 +40,21 @@ interface ITtsService {
* @param text 要合成的文本片段
* @return 是否成功处理
*/
suspend fun speakStream(text: String): Boolean
fun speakStream(text: String): Boolean
/**
* 刷新并播放流式文本缓冲区中的剩余内容
*
* @return 是否成功处理
*/
suspend fun flushStream(): Boolean
fun flushStream(): Boolean
/**
* 停止当前合成和播放
*
* @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,
/** 开始播放 */
STARTED,
/** 播放完成 */
COMPLETED,
/** 播放取消 */
CANCELED,
/** 发生错误 */
ERROR
}
@ -31,7 +22,8 @@ enum class TtsEventType {
*
* @param type 事件类型
* @param params 事件参数,根据不同的事件类型包含不同的内容
* 错误事件通常包含 "errorCode" 和 "errorMessage"
* - ERROR: 包含 "errorCode" 和 "errorMessage"
* - CANCELED: 包含 "reason" 和 "errorDetails"
*/
data class TtsEvent(
val type: TtsEventType,

2
pubspec.yaml

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

Loading…
Cancel
Save