import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../tts_service.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; /// 火山语音合成服务实现 class VolcanoTtsService extends GetxService implements TtsService { /// 方法通道 static const MethodChannel _channel = MethodChannel('volcano_speech/tts'); /// 事件通道 static const EventChannel _eventChannel = EventChannel('volcano_speech/tts_events'); /// 音频数据通道 static const EventChannel _audioDataChannel = EventChannel('volcano_speech/tts_audio_data'); /// 事件流控制器 final StreamController _eventStreamController = StreamController.broadcast(); /// 音频数据流控制器 final StreamController _audioDataStreamController = StreamController.broadcast(); /// 事件处理器 StreamSubscription? _eventSubscription; /// 音频数据处理器 StreamSubscription? _audioDataSubscription; /// 是否初始化完成 bool _isInitialized = false; /// 当前语音名称 String _currentVoice = "zh_female_qingxin"; /// 是否启用音频回调 bool _isAudioCallbackEnabled = false; @override String get currentVoice => _currentVoice; @override bool get isSpeaking => _isSpeaking; /// 是否正在播放 bool _isSpeaking = false; @override Stream get onEvent => _eventStreamController.stream; /// 音频数据流 Stream get onAudioData => _audioDataStreamController.stream; /// 构造函数 VolcanoTtsService() { _setupEventListeners(); _setupAudioDataListeners(); } /// 设置事件监听 void _setupEventListeners() { _eventSubscription = _eventChannel.receiveBroadcastStream().listen( (dynamic event) { if (event is! Map) return; final Map eventMap = event; final String? type = eventMap['type'] as String?; switch (type) { case 'started': _isSpeaking = true; _eventStreamController.add(TtsEvent(type: TtsEventType.started)); break; case 'completed': _isSpeaking = false; _eventStreamController.add(TtsEvent(type: TtsEventType.completed)); break; case 'canceled': _isSpeaking = false; _eventStreamController.add(TtsEvent(type: TtsEventType.canceled)); break; case 'error': _isSpeaking = false; final errorCode = eventMap['errorCode'] as String? ?? "-1"; final errorMessage = eventMap['errorMessage'] as String? ?? '未知错误'; _eventStreamController.add(TtsEvent( type: TtsEventType.error, error: '$errorMessage (错误码: $errorCode)', )); break; } }, onError: (Object error) { debugPrint('TTS事件流错误: $error'); _eventStreamController.add(TtsEvent( type: TtsEventType.error, error: '事件流错误: $error', )); }, ); } /// 设置音频数据监听 void _setupAudioDataListeners() { _audioDataSubscription = _audioDataChannel.receiveBroadcastStream().listen( (dynamic data) { if (data is Uint8List) { _audioDataStreamController.add(data); } }, onError: (Object error) { debugPrint('TTS音频数据流错误: $error'); }, ); } @override Future initialize({List? supportedLanguages}) async { if (_isInitialized) return true; try { // 从.env文件读取配置 final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; if (appId.isEmpty || appToken.isEmpty) { debugPrint( '火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量'); return false; } // 初始化火山语音TTS final result = await _channel.invokeMethod('initialize', { 'appId': appId, 'appToken': appToken, }) ?? false; if (!result) { debugPrint('火山语音TTS初始化失败'); return false; } _isInitialized = true; return true; } catch (e) { debugPrint('火山语音TTS初始化异常:$e'); return false; } } /// 检查初始化状态 Future _ensureInitialized() async { if (!_isInitialized) { return await initialize(); } return true; } @override Future setVoice(String voiceName) async { if (!await _ensureInitialized()) return false; try { final result = await _channel.invokeMethod('setVoice', { 'voice': voiceName, }) ?? false; if (result) { _currentVoice = voiceName; } return result; } catch (e) { debugPrint('设置语音失败:$e'); return false; } } @override Future setTtsMode(String mod) async { if (!await _ensureInitialized()) return false; try { final result = await _channel.invokeMethod('setTtsMode', { 'mod': mod, }) ?? false; return result; } catch (e) { debugPrint('设置TTS模式失败:$e'); return false; } } @override Future setVoiceFlocking(String speakerProfileId) async { return true; } /// 设置是否启用音频数据回调 Future setEnableAudioCallback(bool enable) async { if (!await _ensureInitialized()) return false; try { final result = await _channel.invokeMethod('enableAudioCallback', { 'enable': enable, }) ?? false; if (result) { _isAudioCallbackEnabled = enable; } return result; } catch (e) { debugPrint('设置音频回调失败:$e'); return false; } } @override Future startspeak(String sessionid) async { if (!await _ensureInitialized()) return false; try { // 单次播放 final result = await _channel.invokeMethod('startspeak', { 'sessionid': sessionid, }) ?? false; return result; } catch (e) { debugPrint('单次播放失败:$e'); _eventStreamController.add(TtsEvent.error('单次播放失败:$e')); return false; } } @override Future speakOnce(String sessionid, String text) async { if (!await _ensureInitialized()) return false; try { // 单次播放 final result = await _channel.invokeMethod('speakOnce', { 'text': text, }) ?? false; return result; } catch (e) { debugPrint('单次播放失败:$e'); _eventStreamController.add(TtsEvent.error('单次播放失败:$e')); return false; } } @override Future speakStream(String sessionid, String text) async { if (!await _ensureInitialized()) return false; try { // 流式添加文本 final result = await _channel.invokeMethod('speakStream', { 'text': text, }) ?? false; return result; } catch (e) { debugPrint('流式播放失败:$e'); _eventStreamController.add(TtsEvent.error('流式播放失败:$e')); return false; } } @override Future flushStream( String sessionid, ) async { if (!await _ensureInitialized()) return false; try { // 刷新流式文本 return await _channel.invokeMethod('flushStream') ?? false; } catch (e) { debugPrint('刷新流式文本失败:$e'); return false; } } @override Future stop() async { if (!_isInitialized) return false; try { return await _channel.invokeMethod('stop') ?? false; } catch (e) { debugPrint('停止播放失败:$e'); return false; } } @override Future dispose() async { if (_isSpeaking) { await stop(); } await _eventSubscription?.cancel(); await _audioDataSubscription?.cancel(); if (!_eventStreamController.isClosed) { await _eventStreamController.close(); } if (!_audioDataStreamController.isClosed) { await _audioDataStreamController.close(); } if (_isInitialized) { await _channel.invokeMethod('release'); _isInitialized = false; } } @override Future setAudioOutputDevice(int type) { // TODO: implement setAudioOutputDevice throw UnimplementedError(); } }