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.
364 lines
9.4 KiB
364 lines
9.4 KiB
import 'dart:io';
|
|
import 'package:flutter_tts/flutter_tts.dart';
|
|
import 'package:get/get.dart';
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// Flutter TTS服务,负责本地文本转语音功能
|
|
class FlutterTtsService extends GetxService {
|
|
late FlutterTts _flutterTts;
|
|
|
|
// 当前TTS状态
|
|
TtsState _ttsState = TtsState.stopped;
|
|
TtsState get ttsState => _ttsState;
|
|
|
|
// 可用的语音
|
|
List<Map<String, String>> _voices = [];
|
|
List<Map<String, String>> get voices => _voices;
|
|
|
|
// 当前选择的语音
|
|
String _currentVoice = '';
|
|
String get currentVoice => _currentVoice;
|
|
set currentVoice(String voice) => _currentVoice = voice;
|
|
|
|
// 语速 (0.1-2.0)
|
|
double _rate = 0.5;
|
|
double get rate => _rate;
|
|
set rate(double value) => _rate = value;
|
|
|
|
// 音量 (0.0-1.0)
|
|
double _volume = 1.0;
|
|
double get volume => _volume;
|
|
set volume(double value) => _volume = value;
|
|
|
|
// 音调 (0.5-2.0)
|
|
double _pitch = 1.0;
|
|
double get pitch => _pitch;
|
|
set pitch(double value) => _pitch = value;
|
|
|
|
// 文本队列 - 仅包含待播放的文本
|
|
List<String> _textQueue = [];
|
|
List<String> get textQueue => _textQueue;
|
|
|
|
// 当前正在播放的文本
|
|
String _currentText = '';
|
|
String get currentText => _currentText;
|
|
|
|
// 是否正在处理队列
|
|
bool _isProcessingQueue = false;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_initTts();
|
|
}
|
|
|
|
/// 初始化TTS引擎
|
|
Future<void> _initTts() async {
|
|
_flutterTts = FlutterTts();
|
|
|
|
// 设置完成回调
|
|
_flutterTts.setCompletionHandler(() {
|
|
Logger.info('TTS播放完成: "$_currentText"');
|
|
|
|
// 清空当前播放文本
|
|
_currentText = '';
|
|
|
|
// 处理队列中的下一条文本
|
|
_processNextInQueue();
|
|
|
|
// 只有当队列为空且不再处理队列时才设置为stopped状态
|
|
if (_textQueue.isEmpty && !_isProcessingQueue) {
|
|
_ttsState = TtsState.stopped;
|
|
Logger.info('TTS队列播放完成,设置为stopped状态');
|
|
}
|
|
});
|
|
|
|
// 设置错误回调
|
|
_flutterTts.setErrorHandler((error) {
|
|
Logger.error('Flutter TTS错误: $error');
|
|
|
|
// 清空当前播放文本
|
|
_currentText = '';
|
|
|
|
// 尝试播放下一条文本
|
|
_processNextInQueue();
|
|
|
|
// 只有当队列为空且不再处理队列时才设置为stopped状态
|
|
if (_textQueue.isEmpty && !_isProcessingQueue) {
|
|
_ttsState = TtsState.stopped;
|
|
Logger.info('TTS队列播放出错且为空,设置为stopped状态');
|
|
}
|
|
});
|
|
|
|
// 设置开始回调
|
|
_flutterTts.setStartHandler(() {
|
|
_ttsState = TtsState.playing;
|
|
});
|
|
|
|
// 设置取消回调
|
|
_flutterTts.setCancelHandler(() {
|
|
// 清空当前播放文本
|
|
_currentText = '';
|
|
_isProcessingQueue = false;
|
|
_ttsState = TtsState.stopped;
|
|
Logger.info('TTS播放被取消,设置为stopped状态');
|
|
});
|
|
|
|
// 设置暂停回调
|
|
_flutterTts.setPauseHandler(() {
|
|
_ttsState = TtsState.paused;
|
|
});
|
|
|
|
// 设置继续回调
|
|
_flutterTts.setContinueHandler(() {
|
|
_ttsState = TtsState.playing;
|
|
});
|
|
|
|
// 加载可用语音
|
|
await _loadVoices();
|
|
|
|
Logger.info('Flutter TTS服务已初始化');
|
|
}
|
|
|
|
/// 加载可用的语音
|
|
Future<void> _loadVoices() async {
|
|
try {
|
|
if (Platform.isAndroid || Platform.isIOS) {
|
|
final voices = await _flutterTts.getVoices;
|
|
if (voices != null) {
|
|
_voices = [];
|
|
for (var voice in voices) {
|
|
if (voice is Map) {
|
|
final Map<String, String> voiceMap = {};
|
|
voice.forEach((key, value) {
|
|
if (key is String && value is String) {
|
|
voiceMap[key] = value;
|
|
} else if (key is String) {
|
|
voiceMap[key] = value.toString();
|
|
}
|
|
});
|
|
if (voiceMap.containsKey('name')) {
|
|
_voices.add(voiceMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果有可用语音,设置第一个为默认
|
|
if (_voices.isNotEmpty) {
|
|
_currentVoice = _voices[0]['name'] ?? '';
|
|
await _flutterTts.setVoice({"name": _currentVoice});
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('加载TTS语音失败', e);
|
|
}
|
|
}
|
|
|
|
/// 设置语音
|
|
Future<void> setVoice(String voiceName) async {
|
|
try {
|
|
_currentVoice = voiceName;
|
|
await _flutterTts.setVoice({"name": voiceName});
|
|
} catch (e) {
|
|
Logger.error('设置TTS语音失败', e);
|
|
}
|
|
}
|
|
|
|
/// 设置语速
|
|
Future<void> setRate(double rate) async {
|
|
try {
|
|
_rate = rate;
|
|
await _flutterTts.setSpeechRate(rate);
|
|
} catch (e) {
|
|
Logger.error('设置TTS语速失败', e);
|
|
}
|
|
}
|
|
|
|
/// 设置音量
|
|
Future<void> setVolume(double volume) async {
|
|
try {
|
|
_volume = volume;
|
|
await _flutterTts.setVolume(volume);
|
|
} catch (e) {
|
|
Logger.error('设置TTS音量失败', e);
|
|
}
|
|
}
|
|
|
|
/// 设置音调
|
|
Future<void> setPitch(double pitch) async {
|
|
try {
|
|
_pitch = pitch;
|
|
await _flutterTts.setPitch(pitch);
|
|
} catch (e) {
|
|
Logger.error('设置TTS音调失败', e);
|
|
}
|
|
}
|
|
|
|
/// 设置语言
|
|
Future<void> setLanguage(String language) async {
|
|
try {
|
|
await _flutterTts.setLanguage(language);
|
|
} catch (e) {
|
|
Logger.error('设置TTS语言失败', e);
|
|
}
|
|
}
|
|
|
|
/// 添加文本到队列并开始处理队列(如果尚未开始)
|
|
Future<void> speak(String text) async {
|
|
if (text.isEmpty) return;
|
|
|
|
// 添加到队列
|
|
_addToQueue(text);
|
|
|
|
// 开始处理队列(如果尚未开始)
|
|
_startQueueProcessingIfNeeded();
|
|
}
|
|
|
|
/// 添加多条文本到队列并开始处理队列(如果尚未开始)
|
|
Future<void> speakTexts(List<String> texts) async {
|
|
if (texts.isEmpty) return;
|
|
|
|
// 添加所有非空文本到队列
|
|
int addedCount = 0;
|
|
for (final text in texts) {
|
|
if (text.isNotEmpty) {
|
|
_textQueue.add(text);
|
|
addedCount++;
|
|
}
|
|
}
|
|
|
|
if (addedCount > 0) {
|
|
Logger.info('已添加$addedCount条文本到TTS队列');
|
|
|
|
// 开始处理队列(如果尚未开始)
|
|
_startQueueProcessingIfNeeded();
|
|
}
|
|
}
|
|
|
|
/// 添加文本到队列
|
|
void _addToQueue(String text) {
|
|
if (text.isEmpty) return;
|
|
|
|
_textQueue.add(text);
|
|
Logger.info('已添加文本到TTS队列: "${text.length > 20 ? text.substring(0, 20) + '...' : text}"');
|
|
}
|
|
|
|
/// 开始处理队列(如果尚未开始)
|
|
void _startQueueProcessingIfNeeded() {
|
|
// 如果队列为空,不需要处理
|
|
if (_textQueue.isEmpty) return;
|
|
|
|
// 如果已经在处理队列或正在播放,不需要重新开始
|
|
if (_isProcessingQueue || _ttsState == TtsState.playing) return;
|
|
|
|
// 开始处理队列
|
|
_isProcessingQueue = true;
|
|
_processNextInQueue();
|
|
}
|
|
|
|
/// 处理队列中的下一条文本
|
|
void _processNextInQueue() {
|
|
// 如果队列为空,结束处理但不立即设置stopped状态
|
|
if (_textQueue.isEmpty) {
|
|
_currentText = '';
|
|
_isProcessingQueue = false;
|
|
|
|
// 只有当当前没有文本在播放时才设置为stopped状态
|
|
if (_currentText.isEmpty) {
|
|
_ttsState = TtsState.stopped;
|
|
Logger.info('TTS队列为空,设置为stopped状态');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 获取队列中的第一条文本并播放
|
|
_speakNextText();
|
|
}
|
|
|
|
/// 播放队列中的下一条文本
|
|
Future<void> _speakNextText() async {
|
|
if (_textQueue.isEmpty) {
|
|
_isProcessingQueue = false;
|
|
|
|
// 只有当当前没有文本在播放时才设置为stopped状态
|
|
if (_currentText.isEmpty) {
|
|
_ttsState = TtsState.stopped;
|
|
Logger.info('TTS队列播放完成,设置为stopped状态');
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 获取队列中的第一条文本
|
|
final text = _textQueue.removeAt(0);
|
|
_currentText = text;
|
|
|
|
try {
|
|
// 设置TTS参数
|
|
await _flutterTts.setSpeechRate(_rate);
|
|
await _flutterTts.setVolume(_volume);
|
|
await _flutterTts.setPitch(_pitch);
|
|
|
|
if (_currentVoice.isNotEmpty) {
|
|
await _flutterTts.setVoice({"name": _currentVoice});
|
|
}
|
|
|
|
// 播放文本
|
|
await _flutterTts.speak(text);
|
|
Logger.info('正在播放文本,队列中还有${_textQueue.length}条待播放');
|
|
} catch (e) {
|
|
Logger.error('TTS播放文本失败', e);
|
|
// 清空当前文本
|
|
_currentText = '';
|
|
// 尝试播放下一个
|
|
_processNextInQueue();
|
|
}
|
|
}
|
|
|
|
/// 停止播放并清空队列
|
|
Future<void> stop() async {
|
|
try {
|
|
await _flutterTts.stop();
|
|
|
|
// 重置队列处理状态
|
|
_textQueue.clear();
|
|
_currentText = '';
|
|
_isProcessingQueue = false;
|
|
_ttsState = TtsState.stopped;
|
|
|
|
Logger.info('已停止TTS播放并清空队列');
|
|
} catch (e) {
|
|
Logger.error('停止TTS播放失败', e);
|
|
}
|
|
}
|
|
|
|
/// 暂停播放
|
|
Future<void> pause() async {
|
|
try {
|
|
await _flutterTts.pause();
|
|
_ttsState = TtsState.paused;
|
|
} catch (e) {
|
|
Logger.error('暂停TTS播放失败', e);
|
|
}
|
|
}
|
|
|
|
/// 清空队列并停止播放
|
|
Future<void> clearQueue() async {
|
|
_textQueue.clear();
|
|
await stop();
|
|
Logger.info('已清空TTS队列');
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_flutterTts.stop();
|
|
super.onClose();
|
|
}
|
|
}
|
|
|
|
/// TTS状态枚举
|
|
enum TtsState {
|
|
playing,
|
|
stopped,
|
|
paused,
|
|
}
|