From 6ff7dccce25449e82fe3de7ad8e8caeeae94ac97 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Fri, 16 May 2025 17:46:39 +0100 Subject: [PATCH] add --- android/build.gradle.kts | 2 +- lib/core/themes/theme_service.dart | 4 +- lib/core/utils/logger.dart | 85 +-- lib/data/services/ble_manager.dart | 41 +- lib/data/services/bluetooth_manager.dart | 8 +- lib/data/services/chat_history_service.dart | 2 +- lib/data/services/deapsound_ai_service.dart | 6 +- lib/data/services/kouzi_ai_service.dart | 6 +- lib/data/services/speech_factory.dart | 4 +- .../speech_impl/azure_asr_service.dart | 21 +- .../speech_impl/azure_tts_service.dart | 22 +- .../speech_impl/flutter_asr_service.dart | 18 +- .../speech_impl/flutter_tts_service.dart | 24 +- .../speech_impl/voice_clone_tts_service.dart | 22 +- .../speech_impl/volcano_asr_api_service.dart | 38 +- .../speech_impl/volcano_tts_api_service.dart | 16 +- .../speech_impl/xunfei_asr_service.dart | 20 +- .../android_voice_interaction_service.dart | 18 +- .../ios_voice_interaction_service.dart | 12 +- .../agent/controllers/agent_controller.dart | 54 +- .../chat/controllers/chat_controller.dart | 10 +- .../meeting_details_controller.dart | 8 +- .../controllers/pairing_controller.dart | 14 +- .../controllers/speech_test_controller.dart | 18 +- .../controllers/translation_controller.dart | 26 +- .../translation_detail_controller.dart | 8 +- .../agent_service/AgentService.kt | 473 ++++++------- local_plugins/agent_service/pubspec.yaml | 12 - .../azure_speech/android/build.gradle.kts | 3 +- .../azure_speech/AzureSpeechPlugin.kt | 224 +++--- .../azure_speech/AzureTtsHelper.kt | 289 +++++--- .../bytedance_speech/android/build.gradle.kts | 26 +- .../bytedance_speech/BytedanceASR.kt | 2 +- .../bytedance_speech/BytedanceAudioPlayer.kt | 329 +++++++++ .../bytedance_speech/BytedanceSpeechPlugin.kt | 154 +++-- .../bytedance_speech/BytedanceTTS.kt | 650 ++++++++++++------ local_plugins/classic_bluetooth/pubspec.yaml | 2 - .../.gradle/8.10/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/cache.properties | 2 +- local_plugins/speech/android/build.gradle.kts | 2 +- .../speech/android/settings.gradle.kts | 2 +- .../com/deep_voice/speech/ITtsService.kt | 10 +- .../kotlin/com/deep_voice/speech/TtsEvents.kt | 12 +- pubspec.yaml | 2 - 45 files changed, 1658 insertions(+), 1043 deletions(-) create mode 100644 local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 88678470b..412f86b15 100644 --- a/android/build.gradle.kts +++ b/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") // 其他依赖... } } diff --git a/lib/core/themes/theme_service.dart b/lib/core/themes/theme_service.dart index fd438f9d8..a70c3617c 100644 --- a/lib/core/themes/theme_service.dart +++ b/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()}'); } } } \ No newline at end of file diff --git a/lib/core/utils/logger.dart b/lib/core/utils/logger.dart index 3c5a15470..eba8701f5 100644 --- a/lib/core/utils/logger.dart +++ b/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'); } } diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 3234879a0..0c69076df 100644 --- a/lib/data/services/ble_manager.dart +++ b/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()); } -} +} \ No newline at end of file diff --git a/lib/data/services/bluetooth_manager.dart b/lib/data/services/bluetooth_manager.dart index e6e26ec02..c52a7f4b5 100644 --- a/lib/data/services/bluetooth_manager.dart +++ b/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; } } diff --git a/lib/data/services/chat_history_service.dart b/lib/data/services/chat_history_service.dart index 8749c5714..9a1688314 100644 --- a/lib/data/services/chat_history_service.dart +++ b/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()); } } diff --git a/lib/data/services/deapsound_ai_service.dart b/lib/data/services/deapsound_ai_service.dart index 186116ab4..c4667b97d 100644 --- a/lib/data/services/deapsound_ai_service.dart +++ b/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(); } } -} +} \ No newline at end of file diff --git a/lib/data/services/kouzi_ai_service.dart b/lib/data/services/kouzi_ai_service.dart index cf479ac56..c19f23d1a 100644 --- a/lib/data/services/kouzi_ai_service.dart +++ b/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(); } } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_factory.dart b/lib/data/services/speech_factory.dart index b1c7a1ec5..dd9a79c68 100644 --- a/lib/data/services/speech_factory.dart +++ b/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(); } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index 5c87577ee..fe0122717 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/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; } } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/azure_tts_service.dart b/lib/data/services/speech_impl/azure_tts_service.dart index 2e24a90b8..d6eccdf9b 100644 --- a/lib/data/services/speech_impl/azure_tts_service.dart +++ b/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 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()}'); } } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/flutter_asr_service.dart b/lib/data/services/speech_impl/flutter_asr_service.dart index 438953af8..c8716ad8f 100644 --- a/lib/data/services/speech_impl/flutter_asr_service.dart +++ b/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()}'); } } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/flutter_tts_service.dart b/lib/data/services/speech_impl/flutter_tts_service.dart index 810f82787..aca6fca66 100644 --- a/lib/data/services/speech_impl/flutter_tts_service.dart +++ b/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; } } diff --git a/lib/data/services/speech_impl/voice_clone_tts_service.dart b/lib/data/services/speech_impl/voice_clone_tts_service.dart index 2f57939a9..43079325f 100644 --- a/lib/data/services/speech_impl/voice_clone_tts_service.dart +++ b/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()}'); } } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/volcano_asr_api_service.dart b/lib/data/services/speech_impl/volcano_asr_api_service.dart index 3ffc4e75e..26f18e1c5 100644 --- a/lib/data/services/speech_impl/volcano_asr_api_service.dart +++ b/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; } -} +} \ No newline at end of file diff --git a/lib/data/services/speech_impl/volcano_tts_api_service.dart b/lib/data/services/speech_impl/volcano_tts_api_service.dart index e01e253ed..8a3196c03 100644 --- a/lib/data/services/speech_impl/volcano_tts_api_service.dart +++ b/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; diff --git a/lib/data/services/speech_impl/xunfei_asr_service.dart b/lib/data/services/speech_impl/xunfei_asr_service.dart index ef6791e3f..00b151019 100644 --- a/lib/data/services/speech_impl/xunfei_asr_service.dart +++ b/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; } } -} +} \ No newline at end of file diff --git a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart index f4771ffac..5dd9f96e7 100644 --- a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart +++ b/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()}'); } } diff --git a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart index 46dbb2391..1fc80ba89 100644 --- a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart +++ b/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('已发送识别开始事件'); } -} +} \ No newline at end of file diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 905359208..8a2532859 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/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 = [].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 _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 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']; } diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index 2f7fd5f70..62f6d0df0 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/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); } -} +} \ No newline at end of file diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart index b906902ed..9b8882d96 100644 --- a/lib/modules/meeting/controllers/meeting_details_controller.dart +++ b/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; } -} +} \ No newline at end of file diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 7a65da691..d15f0da8e 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/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); } -} +} \ No newline at end of file diff --git a/lib/modules/speech_test/controllers/speech_test_controller.dart b/lib/modules/speech_test/controllers/speech_test_controller.dart index 4a9d195f1..6cc840e5a 100644 --- a/lib/modules/speech_test/controllers/speech_test_controller.dart +++ b/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(); } -} +} \ No newline at end of file diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 7962d629f..374a2be01 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/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) ?? '未知语言'; } -} +} \ No newline at end of file diff --git a/lib/modules/translation/controllers/translation_detail_controller.dart b/lib/modules/translation/controllers/translation_detail_controller.dart index f7e69f2ce..78f284cee 100644 --- a/lib/modules/translation/controllers/translation_detail_controller.dart +++ b/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 { diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 9aa632d0a..23c50c5de 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/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()) - - // 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) { // 停止当前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) { - 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) { - // 检查当前是否在主线程 - 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}") - } - } - /** * 检查录音权限 */ diff --git a/local_plugins/agent_service/pubspec.yaml b/local_plugins/agent_service/pubspec.yaml index a12f742b4..f5dc12c7d 100644 --- a/local_plugins/agent_service/pubspec.yaml +++ b/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: diff --git a/local_plugins/azure_speech/android/build.gradle.kts b/local_plugins/azure_speech/android/build.gradle.kts index 5d465f450..8db97c1d7 100644 --- a/local_plugins/azure_speech/android/build.gradle.kts +++ b/local_plugins/azure_speech/android/build.gradle.kts @@ -53,5 +53,6 @@ tasks.withType { dependencies { // 添加Microsoft语音SDK implementation("com.microsoft.cognitiveservices.speech:client-sdk:1.43.0") - implementation(project(":ble_service")) + implementation(project(":speech")) + } \ No newline at end of file diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index df4dff206..d4b83d07b 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/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) { @@ -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("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("subscriptionKey") ?: "" val region = call.argument("region") ?: "" - val useCustomAudioOutput = call.argument("useCustomAudioOutput") ?: false // 默认使用自定义播放器 + val useCustomAudioOutput = call.argument("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("voiceName") ?: return result.error("INVALID_ARGUMENTS", "语音名称不能为空", null) @@ -315,18 +350,28 @@ class AzureSpeechPlugin: FlutterPlugin,BleService.Callback, CoroutineScope { "speakText" -> { val text = call.argument("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>) { - - } - - 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) { - - } } \ No newline at end of file diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt index f90e60cec..5a27836c7 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt +++ b/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() + private val audioDataListeners = mutableListOf() // 自定义音频输出流 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 = 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("&", "&") - .replace("<", "<") - .replace(">", ">") + // 2. 转义 XML 保留字符 + val escapedText = rawText + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") - // 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 """ - - - - - $processedText - - - - - """.trimIndent() -} + // 4. 构造简化的SSML文档,减少嵌套层级 + return """ + + + + + $processedText + + + + + """.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) { diff --git a/local_plugins/bytedance_speech/android/build.gradle.kts b/local_plugins/bytedance_speech/android/build.gradle.kts index e56441e6a..afd104ba2 100644 --- a/local_plugins/bytedance_speech/android/build.gradle.kts +++ b/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")) } \ No newline at end of file diff --git a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceASR.kt b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceASR.kt index 1446ed730..9e9da5ee8 100644 --- a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceASR.kt +++ b/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, "收到二进制消息") // 在实际实现中解析二进制消息 } diff --git a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceAudioPlayer.kt new file mode 100644 index 000000000..9587f401f --- /dev/null +++ b/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() + + // 播放状态监听 + 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() + } +} \ No newline at end of file diff --git a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt index 1a3447ffc..d86faa71f 100644 --- a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceSpeechPlugin.kt +++ b/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() - params["code"] = code - params["message"] = message - sendTTSEvent("error", params) - }, - // 状态回调 - statusCallback = { status -> - val params = HashMap() - 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() + 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端 diff --git a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt b/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt index 6e508bcab..d202a36e4 100644 --- a/local_plugins/bytedance_speech/android/src/main/kotlin/com/deep_voice/bytedance_speech/BytedanceTTS.kt +++ b/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() + private val audioDataListeners = mutableListOf() + + // 内部音频播放器 + 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(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 = 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 } } + } \ No newline at end of file diff --git a/local_plugins/classic_bluetooth/pubspec.yaml b/local_plugins/classic_bluetooth/pubspec.yaml index c618200f1..6e4012e2f 100644 --- a/local_plugins/classic_bluetooth/pubspec.yaml +++ b/local_plugins/classic_bluetooth/pubspec.yaml @@ -9,8 +9,6 @@ environment: dependencies: flutter: sdk: flutter - deepvoice_utils: - path: ../deepvoice_utils flutter: plugin: diff --git a/local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock b/local_plugins/opus/.gradle/8.10/fileHashes/fileHashes.lock index 4e477b3ee9982efc49e30adc213d1b0df26cce1c..cadc63f5e6ce4bb1b733f316c64dffab53aa7ab0 100644 GIT binary patch literal 17 UcmZQRo$b{-r|q*H0|cl605?(uiU0rr literal 17 UcmZQRo$b{-r|q*H0|Y1m05?tqh5!Hn diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 48bc9bac10dbdc56e568879768292aa37ce50fe6..e92059b6ad44898ba790f66f5aa21ce10ee005a8 100644 GIT binary patch literal 17 TcmZQ}Dp$CADo{Lz0RqebDLDgn literal 17 TcmZQ}Dp$CADo{Lz0Rr>@DKP_X diff --git a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties b/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties index d3c4e6543..ccc9213ad 100644 --- a/local_plugins/opus/.gradle/buildOutputCleanup/cache.properties +++ b/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 diff --git a/local_plugins/speech/android/build.gradle.kts b/local_plugins/speech/android/build.gradle.kts index f5bbc9337..4e5186a27 100644 --- a/local_plugins/speech/android/build.gradle.kts +++ b/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 diff --git a/local_plugins/speech/android/settings.gradle.kts b/local_plugins/speech/android/settings.gradle.kts index 1c33f4971..efad09063 100644 --- a/local_plugins/speech/android/settings.gradle.kts +++ b/local_plugins/speech/android/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "open_ai_service" \ No newline at end of file +rootProject.name = "speech" \ No newline at end of file diff --git a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt b/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt index a129fd0aa..91c8945d6 100644 --- a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/ITtsService.kt +++ b/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 /** * 释放资源 diff --git a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt b/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt index 036344edd..cb69a849d 100644 --- a/local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/TtsEvents.kt +++ b/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, diff --git a/pubspec.yaml b/pubspec.yaml index cd9173c35..0e74b1bee 100644 --- a/pubspec.yaml +++ b/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: