import 'dart:async'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../../core/utils/logger.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:path_provider/path_provider.dart'; /// 语音合成事件类型 enum SynthesisEventType { /// 合成开始 synthesisStarted, /// 合成进度更新 synthesisProgress, /// 合成完成 synthesisCompleted, /// 合成取消 synthesisCanceled, /// 合成错误 error, } /// 语音合成事件 class SynthesisEvent { /// 事件类型 final SynthesisEventType type; /// 音频文件路径(仅在 synthesisCompleted 类型中有效) final String? audioFilePath; /// 进度百分比(仅在 synthesisProgress 类型中有效) final int? progressPercent; /// 错误信息(仅在 error 和 synthesisCanceled 类型中有效) final String? error; /// 取消原因(仅在 synthesisCanceled 类型中有效) final String? reason; /// 错误详情(仅在 synthesisCanceled 类型中有效) final String? errorDetails; SynthesisEvent({ required this.type, this.audioFilePath, this.progressPercent, this.error, this.reason, this.errorDetails, }); /// 从原生事件映射创建合成事件 factory SynthesisEvent.fromMap(Map map) { final eventType = map['eventType'] as String; SynthesisEventType type; switch (eventType) { case 'synthesisStarted': type = SynthesisEventType.synthesisStarted; break; case 'synthesisProgress': type = SynthesisEventType.synthesisProgress; break; case 'synthesisCompleted': type = SynthesisEventType.synthesisCompleted; break; case 'synthesisCanceled': type = SynthesisEventType.synthesisCanceled; break; case 'error': type = SynthesisEventType.error; break; default: throw ArgumentError('未知事件类型: $eventType'); } return SynthesisEvent( type: type, audioFilePath: map['audioFilePath'] as String?, progressPercent: map['progressPercent'] as int?, error: map['error'] as String?, reason: map['reason'] as String?, errorDetails: map['errorDetails'] as String?, ); } @override String toString() { return 'SynthesisEvent{type: $type, audioFilePath: $audioFilePath, progressPercent: $progressPercent, error: $error, reason: $reason, errorDetails: $errorDetails}'; } } /// TTS 状态 enum TtsState { /// 空闲 idle, /// 合成中 synthesizing, /// 播放中 playing, /// 已停止 stopped, } /// Azure 文本转语音服务 class AzureTtsService extends GetxService { static AzureTtsService get to => Get.find(); // 方法通道 static const MethodChannel _methodChannel = MethodChannel('com.example.deep_voice/azure_tts'); // 事件通道 static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/azure_tts_events'); // 是否已初始化 bool _isInitialized = false; bool get isInitialized => _isInitialized; // 当前TTS状态 final Rx _ttsState = TtsState.idle.obs; TtsState get ttsState => _ttsState.value; // 合成相关 StreamController? _eventStreamController; StreamSubscription? _eventSubscription; // 公开的事件流 Stream? _synthesisStream; Stream? get synthesisStream => _synthesisStream; // 当前语音名称 String _currentVoice = 'zh-CN-XiaoxiaoNeural'; String get currentVoice => _currentVoice; set currentVoice(String voice) { _currentVoice = voice; _setVoice(voice); } // 语速 (-100 to 100) int _rate = 0; int get rate => _rate; set rate(int value) { if (value < -100 || value > 100) { throw ArgumentError('语速必须在 -100 到 100 之间'); } _rate = value; _setSpeechRate(value); } // 音调 (-100 to 100) int _pitch = 0; int get pitch => _pitch; set pitch(int value) { if (value < -100 || value > 100) { throw ArgumentError('音调必须在 -100 到 100 之间'); } _pitch = value; _setPitch(value); } // 音量 (0 to 100) int _volume = 100; int get volume => _volume; set volume(int value) { if (value < 0 || value > 100) { throw ArgumentError('音量必须在 0 到 100 之间'); } _volume = value; _setVolume(value); } // 可用的语音列表 final RxList> _voices = >[].obs; List> get voices => _voices; /// 初始化 Azure 文本转语音服务 /// /// [subscriptionKey] Azure 语音服务的订阅密钥 /// [serviceRegion] Azure 语音服务的区域 /// [defaultVoice] 默认语音名称,默认为中文女声 Future initialize({ String? subscriptionKey, String? serviceRegion, String? defaultVoice, }) async { if (_isInitialized) return true; try { // 从环境变量中读取配置,如果没有传入参数 final key = subscriptionKey ?? dotenv.env['AZURE_TTS_SUBSCRIPTION_KEY'] ?? ''; final region = serviceRegion ?? dotenv.env['AZURE_TTS_SERVICE_REGION'] ?? 'eastasia'; final voice = defaultVoice ?? dotenv.env['AZURE_TTS_DEFAULT_VOICE'] ?? 'zh-CN-XiaoxiaoNeural'; // 检查配置是否完整 if (key.isEmpty) { Logger.warning('Azure 文本转语音服务的订阅密钥未设置,请检查 .env 文件'); return false; } // 设置事件监听 _setupEventChannel(); final result = await _methodChannel.invokeMethod( 'initialize', { 'subscriptionKey': key, 'serviceRegion': region, 'defaultVoice': voice, }, ); _isInitialized = result ?? false; if (_isInitialized) { Logger.info('Azure 文本转语音服务初始化成功'); _currentVoice = voice; // 获取可用语音列表 await getAvailableVoices(); } else { Logger.error('Azure 文本转语音服务初始化失败'); } return _isInitialized; } catch (e) { Logger.error('初始化 Azure 文本转语音服务失败', e); _isInitialized = false; return false; } } /// 设置事件通道监听 void _setupEventChannel() { // 创建事件流控制器 _eventStreamController = StreamController.broadcast(); // 设置事件监听 _eventSubscription = _eventChannel .receiveBroadcastStream() .map((dynamic event) => SynthesisEvent.fromMap(event)) .listen( (event) { _handleSynthesisEvent(event); _eventStreamController?.add(event); }, onError: (error) { Logger.error('Azure 文本转语音事件流错误', error); _eventStreamController?.addError(error); }, ); // 设置公开的流 _synthesisStream = _eventStreamController?.stream; } /// 处理合成事件 void _handleSynthesisEvent(SynthesisEvent event) { switch (event.type) { case SynthesisEventType.synthesisStarted: _ttsState.value = TtsState.synthesizing; break; case SynthesisEventType.synthesisCompleted: _ttsState.value = TtsState.idle; break; case SynthesisEventType.synthesisCanceled: case SynthesisEventType.error: _ttsState.value = TtsState.idle; break; default: // 其他事件类型不需要特殊处理 break; } } /// 获取可用的语音列表 Future>> getAvailableVoices() async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod>('getAvailableVoices'); if (result != null) { final voicesList = result.map((item) { final map = Map.from(item as Map); return { 'name': map['name'] as String, 'locale': map['locale'] as String, 'gender': map['gender'] as String, 'displayName': map['displayName'] as String, }; }).toList(); _voices.value = List>.from(voicesList); return _voices; } return []; } catch (e) { Logger.error('获取可用语音列表失败', e); return []; } } /// 设置语音 Future _setVoice(String voiceName) async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod( 'setVoice', {'voiceName': voiceName}, ); return result ?? false; } catch (e) { Logger.error('设置语音失败', e); return false; } } /// 设置语速 Future _setSpeechRate(int rate) async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod( 'setSpeechRate', {'rate': rate}, ); return result ?? false; } catch (e) { Logger.error('设置语速失败', e); return false; } } /// 设置音调 Future _setPitch(int pitch) async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod( 'setPitch', {'pitch': pitch}, ); return result ?? false; } catch (e) { Logger.error('设置音调失败', e); return false; } } /// 设置音量 Future _setVolume(int volume) async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod( 'setVolume', {'volume': volume}, ); return result ?? false; } catch (e) { Logger.error('设置音量失败', e); return false; } } /// 合成文本为语音并保存到文件 /// /// [text] 要合成的文本 /// [fileName] 保存的文件名(不包含路径和扩展名) /// /// 返回保存的音频文件路径,如果失败则返回 null Future synthesizeToFile(String text, String fileName) async { _checkInitialized(); if (text.isEmpty) { Logger.warning('合成文本为空'); return null; } try { // 获取临时目录 final tempDir = await getTemporaryDirectory(); final filePath = '${tempDir.path}/$fileName.wav'; // 设置状态 _ttsState.value = TtsState.synthesizing; final result = await _methodChannel.invokeMethod( 'synthesizeToFile', { 'text': text, 'filePath': filePath, }, ); // 合成完成后设置状态 _ttsState.value = TtsState.idle; return result; } catch (e) { Logger.error('合成文本到文件失败', e); // 发生错误时设置状态 _ttsState.value = TtsState.idle; return null; } } /// 合成文本并直接播放 /// /// [text] 要合成并播放的文本 /// /// 返回是否成功开始播放 Future speak(String text) async { _checkInitialized(); if (text.isEmpty) { Logger.warning('播放文本为空'); return false; } try { // 设置状态 _ttsState.value = TtsState.synthesizing; final result = await _methodChannel.invokeMethod( 'speak', {'text': text}, ); if (result == true) { _ttsState.value = TtsState.playing; } else { _ttsState.value = TtsState.idle; } return result ?? false; } catch (e) { Logger.error('合成并播放文本失败', e); // 发生错误时设置状态 _ttsState.value = TtsState.idle; return false; } } /// 停止播放 Future stop() async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod('stop'); if (result == true) { _ttsState.value = TtsState.stopped; } return result ?? false; } catch (e) { Logger.error('停止播放失败', e); return false; } } /// 暂停播放 Future pause() async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod('pause'); return result ?? false; } catch (e) { Logger.error('暂停播放失败', e); return false; } } /// 恢复播放 Future resume() async { _checkInitialized(); try { final result = await _methodChannel.invokeMethod('resume'); return result ?? false; } catch (e) { Logger.error('恢复播放失败', e); return false; } } /// 检查是否已初始化 void _checkInitialized() { if (!_isInitialized) { throw StateError('Azure 文本转语音服务未初始化'); } } /// 释放资源 Future dispose() async { try { await _eventSubscription?.cancel(); _eventSubscription = null; // 只有在控制器存在且未关闭时才关闭 if (_eventStreamController != null && !_eventStreamController!.isClosed) { await _eventStreamController?.close(); } _eventStreamController = null; _synthesisStream = null; if (_isInitialized) { await _methodChannel.invokeMethod('dispose'); } _isInitialized = false; Logger.info('Azure 文本转语音服务资源已释放'); } catch (e) { Logger.error('释放 Azure 文本转语音服务资源失败', e); } } @override void onClose() { dispose(); super.onClose(); } }