You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
7.5 KiB

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<TtsEvent> _eventStreamController =
StreamController<TtsEvent>.broadcast();
/// 音频数据流控制器
final StreamController<Uint8List> _audioDataStreamController =
StreamController<Uint8List>.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<TtsEvent> get onEvent => _eventStreamController.stream;
/// 音频数据流
Stream<Uint8List> get onAudioData => _audioDataStreamController.stream;
/// 构造函数
VolcanoTtsService() {
_setupEventListeners();
_setupAudioDataListeners();
}
/// 设置事件监听
void _setupEventListeners() {
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
(dynamic event) {
if (event is! Map) return;
final Map<dynamic, dynamic> 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<bool> initialize({List<String>? 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<bool>('initialize', {
'appId': appId,
'appToken': appToken,
}) ?? false;
if (!result) {
debugPrint('火山语音TTS初始化失败');
return false;
}
_isInitialized = true;
return true;
} catch (e) {
debugPrint('火山语音TTS初始化异常:$e');
return false;
}
}
/// 检查初始化状态
Future<bool> _ensureInitialized() async {
if (!_isInitialized) {
return await initialize();
}
return true;
}
@override
Future<bool> setVoice(String voiceName) async {
if (!await _ensureInitialized()) return false;
try {
final result = await _channel.invokeMethod<bool>('setVoice', {
'voice': voiceName,
}) ?? false;
if (result) {
_currentVoice = voiceName;
}
return result;
} catch (e) {
debugPrint('设置语音失败:$e');
return false;
}
}
/// 设置是否启用音频数据回调
Future<bool> setEnableAudioCallback(bool enable) async {
if (!await _ensureInitialized()) return false;
try {
final result = await _channel.invokeMethod<bool>('enableAudioCallback', {
'enable': enable,
}) ?? false;
if (result) {
_isAudioCallbackEnabled = enable;
}
return result;
} catch (e) {
debugPrint('设置音频回调失败:$e');
return false;
}
}
@override
Future<bool> speakOnce(String text) async {
if (!await _ensureInitialized()) return false;
try {
// 单次播放
final result = await _channel.invokeMethod<bool>('speakOnce', {
'text': text,
}) ?? false;
return result;
} catch (e) {
debugPrint('单次播放失败:$e');
_eventStreamController.add(TtsEvent.error('单次播放失败:$e'));
return false;
}
}
@override
Future<bool> speakStream(String text) async {
if (!await _ensureInitialized()) return false;
try {
// 流式添加文本
final result = await _channel.invokeMethod<bool>('speakStream', {
'text': text,
}) ?? false;
return result;
} catch (e) {
debugPrint('流式播放失败:$e');
_eventStreamController.add(TtsEvent.error('流式播放失败:$e'));
return false;
}
}
@override
Future<bool> flushStream() async {
if (!await _ensureInitialized()) return false;
try {
// 刷新流式文本
return await _channel.invokeMethod<bool>('flushStream') ?? false;
} catch (e) {
debugPrint('刷新流式文本失败:$e');
return false;
}
}
@override
Future<bool> stop() async {
if (!_isInitialized) return false;
try {
return await _channel.invokeMethod<bool>('stop') ?? false;
} catch (e) {
debugPrint('停止播放失败:$e');
return false;
}
}
@override
Future<void> 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<bool>('release');
_isInitialized = false;
}
}
}