|
|
|
@ -1,5 +1,6 @@ |
|
|
|
import 'dart:async'; |
|
|
|
import 'dart:io'; |
|
|
|
import 'package:deep_voice/data/models/appconfig.dart'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import 'package:get_storage/get_storage.dart'; |
|
|
|
@ -13,22 +14,24 @@ import '../tts_service.dart'; |
|
|
|
class AzureTtsService extends GetxService implements TtsService { |
|
|
|
static final AzureTtsService to = Get.put(AzureTtsService()); |
|
|
|
static const MethodChannel _channel = MethodChannel('azure_speech/tts'); |
|
|
|
static const EventChannel _eventChannel = EventChannel('azure_speech/tts_events'); |
|
|
|
|
|
|
|
final GetStorage _storage = GetStorage(); |
|
|
|
static const EventChannel _eventChannel = |
|
|
|
EventChannel('azure_speech/tts_events'); |
|
|
|
|
|
|
|
// final GetStorage _storage = GetStorage(); |
|
|
|
bool _isInitialized = false; |
|
|
|
late final String _subscriptionKey; |
|
|
|
late final String _serviceRegion; |
|
|
|
|
|
|
|
// 当前设置 |
|
|
|
String _currentVoice = 'zh-CN-XiaoxiaoNeural'; |
|
|
|
|
|
|
|
|
|
|
|
// 语音合成队列 |
|
|
|
final List<String> _textQueue = []; |
|
|
|
bool _isProcessingQueue = false; |
|
|
|
|
|
|
|
// 事件流 |
|
|
|
final StreamController<TtsEvent> _eventStreamController = StreamController<TtsEvent>.broadcast(); |
|
|
|
final StreamController<TtsEvent> _eventStreamController = |
|
|
|
StreamController<TtsEvent>.broadcast(); |
|
|
|
StreamSubscription? _eventSubscription; |
|
|
|
|
|
|
|
// 可观察状态 |
|
|
|
@ -53,9 +56,9 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
|
|
|
|
/// 从环境变量加载配置 |
|
|
|
void _loadConfig() { |
|
|
|
final _env = _storage.read("ENV") as Map<String, String>; |
|
|
|
_subscriptionKey = _env['AZURE_SPEECH_KEY'] ?? ''; |
|
|
|
_serviceRegion = _env['AZURE_SPEECH_REGION'] ?? ''; |
|
|
|
// final _env = _storage.read("ENV") as Map<String, String>; |
|
|
|
_subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; |
|
|
|
_serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; |
|
|
|
} |
|
|
|
|
|
|
|
/// 设置事件通道 |
|
|
|
@ -74,50 +77,50 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
/// 处理TTS事件 |
|
|
|
void _handleTtsEvent(Map<dynamic, dynamic> event) { |
|
|
|
final String eventType = event['type'] as String? ?? ''; |
|
|
|
|
|
|
|
|
|
|
|
// Logger.debug('收到TTS事件: $eventType, 数据: $event'); |
|
|
|
|
|
|
|
|
|
|
|
switch (eventType) { |
|
|
|
case 'synthesis_started': |
|
|
|
_isSpeaking.value = true; |
|
|
|
_eventStreamController.add(TtsEvent(type: TtsEventType.started)); |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'synthesizing': |
|
|
|
// _isSpeaking.value = true; |
|
|
|
// // 合成中事件暂时映射到started类型,因为TtsEventType中没有synthesizing类型 |
|
|
|
// _eventStreamController.add(TtsEvent(type: TtsEventType.started)); |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'synthesis_completed': |
|
|
|
_isSpeaking.value = false; |
|
|
|
_eventStreamController.add(TtsEvent(type: TtsEventType.completed)); |
|
|
|
|
|
|
|
|
|
|
|
// 如果有队列在处理中,继续处理下一项 |
|
|
|
if (_isProcessingQueue) { |
|
|
|
_processNextInQueue(); |
|
|
|
} |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'synthesis_canceled': |
|
|
|
_isSpeaking.value = false; |
|
|
|
final String reason = event['reason'] as String? ?? ''; |
|
|
|
|
|
|
|
|
|
|
|
_eventStreamController.add(TtsEvent.error('合成取消: $reason')); |
|
|
|
|
|
|
|
|
|
|
|
// 如果有队列在处理中,继续处理下一项(即使出错) |
|
|
|
if (_isProcessingQueue) { |
|
|
|
_processNextInQueue(); |
|
|
|
} |
|
|
|
break; |
|
|
|
|
|
|
|
|
|
|
|
case 'error': |
|
|
|
_isSpeaking.value = false; |
|
|
|
final String message = event['message'] as String? ?? '未知错误'; |
|
|
|
|
|
|
|
|
|
|
|
Logger.error('TTS错误: $message'); |
|
|
|
_eventStreamController.add(TtsEvent.error(message)); |
|
|
|
|
|
|
|
|
|
|
|
// 如果有队列在处理中,继续处理下一项(即使出错) |
|
|
|
if (_isProcessingQueue) { |
|
|
|
_processNextInQueue(); |
|
|
|
@ -189,7 +192,7 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
final result = await _channel.invokeMethod('speakText', { |
|
|
|
'text': text, |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return result == true; |
|
|
|
} catch (e) { |
|
|
|
Logger.error('语音合成失败: $e'); |
|
|
|
@ -277,21 +280,24 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
|
|
|
|
// 等待队列处理完成 |
|
|
|
final completer = Completer<bool>(); |
|
|
|
|
|
|
|
|
|
|
|
// 监听事件流检查队列是否完成 |
|
|
|
final subscription = onEvent.listen((event) { |
|
|
|
// 当队列为空且不在处理中,且当前非说话状态,完成操作 |
|
|
|
if (_textQueue.isEmpty && !_isProcessingQueue && !_isSpeaking.value && !completer.isCompleted) { |
|
|
|
if (_textQueue.isEmpty && |
|
|
|
!_isProcessingQueue && |
|
|
|
!_isSpeaking.value && |
|
|
|
!completer.isCompleted) { |
|
|
|
completer.complete(true); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 如果已经处于空闲状态,直接完成 |
|
|
|
if (_textQueue.isEmpty && !_isProcessingQueue && !_isSpeaking.value) { |
|
|
|
subscription.cancel(); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置超时 |
|
|
|
final timeout = Timer(const Duration(seconds: 60), () { |
|
|
|
if (!completer.isCompleted) { |
|
|
|
@ -299,14 +305,14 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
completer.complete(false); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 等待完成 |
|
|
|
final result = await completer.future; |
|
|
|
|
|
|
|
|
|
|
|
// 清理 |
|
|
|
subscription.cancel(); |
|
|
|
timeout.cancel(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.info('TTS队列播放完成'); |
|
|
|
return result; |
|
|
|
} catch (e) { |
|
|
|
@ -320,11 +326,11 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
if (_textQueue.isEmpty || _isProcessingQueue) return; |
|
|
|
|
|
|
|
_isProcessingQueue = true; |
|
|
|
|
|
|
|
|
|
|
|
// 处理队列中的第一项 |
|
|
|
await _processNextInQueue(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 处理队列中的下一项 |
|
|
|
Future<void> _processNextInQueue() async { |
|
|
|
// 检查队列是否为空或已禁用 |
|
|
|
@ -333,14 +339,14 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
_isProcessingQueue = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
// 获取当前项但暂不从队列中移除 |
|
|
|
final text = _textQueue.first; |
|
|
|
|
|
|
|
|
|
|
|
// 开始合成 |
|
|
|
final success = await speakOnce(text); |
|
|
|
|
|
|
|
|
|
|
|
// 仅当合成操作失败时才从队列移除并继续处理下一项 |
|
|
|
// 成功的话会等待合成完成事件后自动处理下一项 |
|
|
|
if (!success) { |
|
|
|
@ -353,12 +359,12 @@ class AzureTtsService extends GetxService implements TtsService { |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
Logger.error('处理语音队列出错: $e'); |
|
|
|
|
|
|
|
|
|
|
|
// 出错时移除当前项并尝试继续 |
|
|
|
if (_textQueue.isNotEmpty) { |
|
|
|
_textQueue.removeAt(0); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果队列中还有项目,继续处理 |
|
|
|
if (_textQueue.isNotEmpty) { |
|
|
|
await _processNextInQueue(); |
|
|
|
|