import 'dart:async'; import 'dart:io'; import '../../../data/models/appconfig.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import '../../../core/utils/logger.dart'; import '../tts_service.dart'; /// 微软 Text-to-Speech 服务 /// /// 该服务通过平台通道与原生 Microsoft Speech SDK 交互, /// 提供文本转语音功能。 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(); bool _isInitialized = false; late final String _subscriptionKey; late final String _serviceRegion; // 当前设置 String _currentVoice = 'zh-CN-XiaoxiaoNeural'; // 语音合成队列 final List _textQueue = []; bool _isProcessingQueue = false; // 事件流 final StreamController _eventStreamController = StreamController.broadcast(); StreamSubscription? _eventSubscription; // 可观察状态 final isEnabled = true.obs; final _isSpeaking = false.obs; String _currsessionid = ''; // 流式文本缓冲区 String _streamBuffer = ''; @override String get currentVoice => _currentVoice; @override bool get isSpeaking => _isSpeaking.value; @override Stream get onEvent => _eventStreamController.stream; AzureTtsService() { _loadConfig(); } /// 从环境变量加载配置 void _loadConfig() { // final _env = _storage.read("ENV") as Map; _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; } /// 设置事件通道 void _setupEventChannel() { _eventSubscription?.cancel(); _eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) { if (event is Map) { _handleTtsEvent(event); } }, onError: (error) { Logger.error('TTS事件流错误: ${error.toString()}'); _eventStreamController.addError(error); }); } /// 处理TTS事件 void _handleTtsEvent(Map event) { final String eventType = event['type'] as String? ?? ''; // Logger.debug('收到TTS事件: ${e.toString()}ventType, 数据: ${e.toString()}vent'); 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(); } break; } } @override Future initialize({ List? supportedLanguages, }) async { // 检查是否需要重新初始化 if (_isInitialized) { await dispose(); } try { if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { Logger.error('未找到Azure语音服务配置'); return false; } // 先设置事件通道,确保在调用initialize方法前已准备好 _setupEventChannel(); // 更新支持的语音列表(如果提供) final String language = supportedLanguages?.first ?? 'zh-CN'; final result = await _channel.invokeMethod('initialize', { 'subscriptionKey': _subscriptionKey, 'region': _serviceRegion, 'language': language, }); _isInitialized = result; return result; } catch (e) { Logger.error('初始化失败: ${e.toString()}'); return false; } } @override Future setVoice(String voiceName) async { if (!_isInitialized) await initialize(); if (voiceName == _currentVoice) return true; try { final result = await _channel.invokeMethod('setVoice', { 'voiceName': voiceName, }); if (result) _currentVoice = voiceName; return result; } catch (e) { Logger.error('设置语音失败: ${e.toString()}'); return false; } } @override Future setTtsMode(String mod) async { if (!_isInitialized) await initialize(); try { final result = await _channel.invokeMethod('setTtsMode', { 'mod': mod, }); return result; } catch (e) { Logger.error('设置TTS模式失败: ${e.toString()}'); return false; } } @override Future setVoiceFlocking(String speakerProfileId) async { final result = await _channel.invokeMethod('setVoiceFlocking', { 'speakerProfileId': speakerProfileId, }); return result; } @override Future startspeak(String sessionid) async { if (!_isInitialized) await initialize(); if (!isEnabled.value) return false; try { // 开始合成,传递参数 final result = await _channel.invokeMethod('startspeak', { 'sessionid': sessionid, }); return result == true; } catch (e) { Logger.error('语音合成失败: ${e.toString()}'); return false; } } @override Future speakOnce(String sessionid, String text) async { if (!_isInitialized) await initialize(); if (!isEnabled.value || text.isEmpty) return false; try { // 开始合成,传递参数 final result = await _channel.invokeMethod('speakText', { 'sessionid': sessionid, 'text': text, }); return result == true; } catch (e) { Logger.error('语音合成失败: ${e.toString()}'); return false; } } @override Future speakStream(String sessionid, String text) async { if (!isEnabled.value || text.isEmpty) return false; try { // 添加文本到缓冲区 _streamBuffer += text; // 如果缓冲区为空,直接返回 if (_streamBuffer.isEmpty) return true; // 使用正则表达式匹配句子,包括结束符号 // 匹配任意字符,直到遇到句子结束符号 final sentenceRegex = RegExp( r'([^。.!!??;;:\n\r]+[。.!!??;;::\n\r]|[^。.!!??;;:\n\r]+(?:\.{3,}|…)|[^。.!!??;;:\n\r]+["」』"])'); bool hasProcessed = false; // 查找所有完整句子 final matches = sentenceRegex.allMatches(_streamBuffer); final List sentences = []; int lastMatchEnd = 0; for (final match in matches) { // 提取完整句子(包含结束符号) final sentence = match.group(1)?.trim(); if (sentence != null && sentence.isNotEmpty) { sentences.add(sentence); lastMatchEnd = match.end; } } // 处理找到的句子 for (final sentence in sentences) { _textQueue.add(sentence); hasProcessed = true; } // 更新缓冲区,只保留未完成的部分 if (lastMatchEnd > 0) { _streamBuffer = _streamBuffer.substring(lastMatchEnd); } // 如果处理了文本并且队列未在处理中,开始处理队列 if (hasProcessed && !_isProcessingQueue) { _processQueue(); } return true; } catch (e) { Logger.error('处理流式文本失败: ${e.toString()}'); return false; } } @override Future flushStream(String sessionid) async { if (_textQueue.isEmpty && _streamBuffer.isEmpty) return true; try { // 处理缓冲区中可能的完整句子 await speakStream(sessionid, ''); // 如果缓冲区仍有剩余文本,将其添加到播放队列 if (_streamBuffer.isNotEmpty && _streamBuffer.trim().isNotEmpty) { _textQueue.add(_streamBuffer); // 清空缓冲区 _streamBuffer = ''; // 如果队列未在处理中,开始处理队列 if (!_isProcessingQueue) { _processQueue(); } } Logger.info('等待TTS队列播放完成,剩余${_textQueue.length}条'); // 等待队列处理完成 final completer = Completer(); // 监听事件流检查队列是否完成 final subscription = onEvent.listen((event) { // 当队列为空且不在处理中,且当前非说话状态,完成操作 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) { Logger.warning('等待TTS队列完成超时'); completer.complete(false); } }); // 等待完成 final result = await completer.future; // 清理 subscription.cancel(); timeout.cancel(); Logger.info('TTS队列播放完成'); return result; } catch (e) { Logger.error('刷新TTS流失败: ${e.toString()}'); return false; } } /// 处理语音合成队列 Future _processQueue() async { if (_textQueue.isEmpty || _isProcessingQueue) return; _isProcessingQueue = true; // 处理队列中的第一项 await _processNextInQueue(); } /// 处理队列中的下一项 Future _processNextInQueue() async { // 检查队列是否为空或已禁用 if (_textQueue.isEmpty || !isEnabled.value) { _textQueue.clear(); _isProcessingQueue = false; return; } try { // 获取当前项但暂不从队列中移除 final text = _textQueue.first; // 开始合成 final success = await speakOnce(_currsessionid, text); // 仅当合成操作失败时才从队列移除并继续处理下一项 // 成功的话会等待合成完成事件后自动处理下一项 if (!success) { _textQueue.removeAt(0); await _processNextInQueue(); } else { // 成功启动合成,从队列中移除当前项 // 注意:下一项的处理将在合成完成事件中触发 _textQueue.removeAt(0); } } catch (e) { Logger.error('处理语音队列出错: ${e.toString()}'); // 出错时移除当前项并尝试继续 if (_textQueue.isNotEmpty) { _textQueue.removeAt(0); } // 如果队列中还有项目,继续处理 if (_textQueue.isNotEmpty) { await _processNextInQueue(); } else { _isProcessingQueue = false; } } } @override Future stop() async { try { _textQueue.clear(); if (_isSpeaking.value) { final result = await _channel.invokeMethod('stopSpeaking'); // 重置缓存状态 _isSpeaking.value = false; _isProcessingQueue = false; return result; } return true; } catch (e) { Logger.error('停止语音合成出错: ${e.toString()}'); return false; } } /// 切换启用状态 void toggleEnabled() { isEnabled.toggle(); if (!isEnabled.value) stop(); } @override Future dispose() async { if (!_isInitialized) return; try { await stop(); await _eventSubscription?.cancel(); await _channel.invokeMethod('dispose'); _isInitialized = false; } catch (e) { Logger.error('释放资源失败: ${e.toString()}'); } } @override Future setAudioOutputDevice(int type) async { try { // 开始合成,传递参数 final result = await _channel.invokeMethod('setAudioOutputDevice', { 'type': type, }); return result; } catch (e) { Logger.error('音频输出设备类型失败: ${e.toString()}'); return false; } } }