import 'dart:async'; import 'dart:collection'; import 'package:ai_chat_assistant/utils/common_util.dart'; import 'package:ai_chat_core/ai_chat_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_tts/flutter_tts.dart'; @Deprecated('Use TtsService interface and its implementations instead') class LocalTtsService { final FlutterTts _flutterTts = FlutterTts(); bool _isPlaying = false; // TTS 播放队列 - 存储文本段 final Queue _ttsQueue = Queue(); // TTS 事件回调 VoidCallback? _onStartHandler; VoidCallback? _onCompletionHandler; Function(String)? _onErrorHandler; // 有序播放队列 - 确保播放顺序与文本顺序一致 List _orderedTasks = []; // 有序任务队列 int _nextTaskIndex = 0; // 下一个要处理的任务索引 bool _isProcessingTasks = false; // 是否正在处理任务 bool _streamCompleted = false; // 流是否完成 LocalTtsService() { _initializeTts(); } /// 初始化TTS引擎 Future _initializeTts() async { try { // 获取可用的TTS引擎列表 await _printAvailableEngines(); // 推荐并设置最佳引擎 // await setEngine("com.k2fsa.sherpa.onnx.tts.engine"); // 设置TTS参数 await _flutterTts.setLanguage("en-US"); // 默认英文 await _flutterTts.setSpeechRate(0.5); // 语速 await _flutterTts.setVolume(1.0); // 音量 await _flutterTts.setPitch(1.0); // 音调 // 设置TTS事件回调 _flutterTts.setStartHandler(() { Logger.i('TTS开始播放'); _isPlaying = true; _onStartHandler?.call(); }); _flutterTts.setCompletionHandler(() { Logger.i('TTS播放完成'); _isPlaying = false; _handleTtsPlaybackComplete(); }); _flutterTts.setErrorHandler((msg) { Logger.e('TTS错误: $msg'); _isPlaying = false; _onErrorHandler?.call(msg); _handleTtsPlaybackComplete(); // 错误时也要处理下一个任务 }); Logger.i('TTS引擎初始化完成'); } catch (e) { Logger.e('TTS引擎初始化失败: $e'); } } /// 设置最佳TTS引擎 // 已移除TtsEngineManager相关代码,避免未定义错误。 /// 打印可用的TTS引擎 Future _printAvailableEngines() async { try { // 获取可用的TTS引擎 dynamic engines = await _flutterTts.getEngines; Logger.i('可用的TTS引擎: $engines'); // 获取可用的语言 dynamic languages = await _flutterTts.getLanguages; Logger.i('支持的语言: $languages'); // 获取当前默认引擎 dynamic defaultEngine = await _flutterTts.getDefaultEngine; Logger.i('当前默认引擎: $defaultEngine'); } catch (e) { Logger.e('获取TTS引擎信息失败: $e'); } } /// 清空所有队列和缓存 void clearAll() { _ttsQueue.clear(); _orderedTasks.clear(); _nextTaskIndex = 0; _isProcessingTasks = false; _streamCompleted = false; _isPlaying = false; // 重置播放状态 Logger.i('所有TTS队列和缓存已清空'); } /// 推送文本到TTS队列进行流式处理(保证顺序) void pushTextForStreamTTS(String text) { if (text.trim().isEmpty) { Logger.i('接收到空字符串,跳过推送到TTS队列'); return; } String cleanedText = CommonUtil.cleanText(text, true); if (cleanedText.isEmpty) { Logger.i('清理后的文本为空,跳过推送到TTS队列'); return; } // 创建有序任务 int taskIndex = _orderedTasks.length; TtsTask task = TtsTask( index: taskIndex, text: cleanedText, status: TtsTaskStatus.ready, ); _orderedTasks.add(task); Logger.i('添加TTS任务 (索引: $taskIndex): $cleanedText'); // 尝试处理队列中的下一个任务 _processNextReadyTask(); } /// 处理队列中下一个就绪的任务 void _processNextReadyTask() { // 如果正在播放,则不处理下一个任务 if (_isPlaying) { Logger.i('当前正在播放音频,等待播放完成'); return; } // 寻找下一个可以播放的任务 while (_nextTaskIndex < _orderedTasks.length) { TtsTask task = _orderedTasks[_nextTaskIndex]; if (task.status == TtsTaskStatus.ready) { Logger.i('按顺序播放TTS (索引: ${task.index}): ${task.text}'); _playTts(task.text); return; // 等待当前音频播放完成 } else { // 跳过已处理的任务 _nextTaskIndex++; continue; } } // 检查是否所有任务都已处理完 if (_streamCompleted && _nextTaskIndex >= _orderedTasks.length) { Logger.i('=== 所有TTS任务播放完成 ==='); _isProcessingTasks = false; _onCompletionHandler?.call(); } } /// 播放TTS Future _playTts(String text) async { if (_isPlaying) { Logger.i('已有TTS正在播放,跳过本次播放'); return; } try { Logger.i('开始播放TTS: $text'); // 检测语言并设置 bool isChinese = CommonUtil.containChinese(text); String targetLanguage = isChinese ? "zh-CN" : "en-US"; // 检查语言是否可用,如果不可用则使用默认语言 bool isLanguageOk = await isLanguageAvailable(targetLanguage); if (!isLanguageOk) { Logger.i('语言 $targetLanguage 不可用,使用默认语言'); // 可以在这里设置备用语言或保持当前语言 } else { await _flutterTts.setLanguage(targetLanguage); Logger.i('设置TTS语言为: $targetLanguage'); } // 播放TTS await _flutterTts.speak(text); } catch (e) { Logger.i('播放TTS失败: $e'); // 播放失败,重置状态并继续下一个 _isPlaying = false; _handleTtsPlaybackComplete(); } } /// 处理TTS播放完成 void _handleTtsPlaybackComplete() { if (!_isPlaying) { Logger.i('TTS已停止播放,处理下一个任务'); // 标记当前任务为已完成 if (_nextTaskIndex < _orderedTasks.length && _orderedTasks[_nextTaskIndex].status == TtsTaskStatus.ready) { _orderedTasks[_nextTaskIndex].status = TtsTaskStatus.completed; _nextTaskIndex++; } // 处理下一个任务 _processNextReadyTask(); } } /// 标记流完成 void markSSEStreamCompleted() { Logger.i('=== 标记SSE流完成 ==='); _streamCompleted = true; // 尝试完成剩余任务 _processNextReadyTask(); } /// 停止播放 void stopSSEPlayback() { Logger.i("停止SSE播放"); // 立即停止TTS播放,无论当前是否在播报 _flutterTts.stop(); _isPlaying = false; clearAll(); } // 设置事件处理器 void setStartHandler(VoidCallback handler) { _onStartHandler = handler; } void setCompletionHandler(VoidCallback handler) { _onCompletionHandler = handler; } void setErrorHandler(Function(String) handler) { _onErrorHandler = handler; } // 保留原有的播放逻辑用于完整文本处理 Future processCompleteText(String text) async { if (text.trim().isEmpty) { Logger.i("文本为空,跳过TTS处理"); return; } Logger.i("开始处理完整文本TTS,原始文本长度: ${text.length}字"); // 清理缓存和重置队列 clearAll(); // 确保停止当前播放 await _stopCurrentPlayback(); // 直接使用流式处理 await _processCompleteTextAsStream(text); } /// 将完整文本作为流式处理 Future _processCompleteTextAsStream(String text) async { // 清理markdown和异常字符 String cleanedText = _cleanTextForTts(text); Logger.i("清理后文本长度: ${cleanedText.length}字"); if (cleanedText.trim().isEmpty) { Logger.i("清理后文本为空,跳过TTS"); return; } // 分割成句子并逐个处理 List sentences = _splitTextIntoSentences(cleanedText); Logger.i("=== 文本分段完成,共 ${sentences.length} 个句子 ==="); // 推送每个句子到流式TTS处理 for (int i = 0; i < sentences.length; i++) { final sentence = sentences[i].trim(); if (sentence.isEmpty) continue; Logger.i("处理句子 ${i + 1}/${sentences.length}: $sentence"); pushTextForStreamTTS(sentence); } // 标记流完成 markSSEStreamCompleted(); } // 停止当前播放的辅助方法 Future _stopCurrentPlayback() async { try { Logger.i("停止当前播放"); if (_isPlaying) { await _flutterTts.stop(); _isPlaying = false; Logger.i("TTS.stop() 调用完成"); } // 短暂延迟确保播放器状态稳定 await Future.delayed(Duration(milliseconds: 300)); Logger.i("播放器状态稳定延迟完成"); } catch (e) { Logger.i('停止当前播放错误: $e'); } } // 保留原有的方法用于兼容性 List _splitTextIntoSentences(String text) { List result = []; if (text.length <= 50) { result.add(text); return result; } // 支持中英文句子分割 List sentenceEnders = [ '。', '!', '?', ';', '\n', // 中文 '.', '!', '?', ';' // 英文 ]; String remainingText = text; while (remainingText.isNotEmpty) { if (remainingText.length <= 50) { result.add(remainingText.trim()); break; } // 在50字以内查找最佳分割点 String candidate = remainingText.substring(0, 50); int lastSentenceEndIndex = -1; for (int i = candidate.length - 1; i >= 0; i--) { if (sentenceEnders.contains(candidate[i])) { lastSentenceEndIndex = i; break; } } if (lastSentenceEndIndex != -1) { // 找到句子结束符,以此为分割点 String chunk = candidate.substring(0, lastSentenceEndIndex + 1).trim(); if (chunk.isNotEmpty) { result.add(chunk); } remainingText = remainingText.substring(lastSentenceEndIndex + 1).trim(); } else { // 没找到句子结束符,寻找逗号分割点(中英文逗号) int lastCommaIndex = -1; for (int i = candidate.length - 1; i >= 20; i--) { if (candidate[i] == ',' || candidate[i] == ',') { lastCommaIndex = i; break; } } if (lastCommaIndex != -1) { String chunk = candidate.substring(0, lastCommaIndex + 1).trim(); if (chunk.isNotEmpty) { result.add(chunk); } remainingText = remainingText.substring(lastCommaIndex + 1).trim(); } else { // 没找到合适的分割点,强制在50字处分割 result.add(candidate.trim()); remainingText = remainingText.substring(50).trim(); } } } return result.where((chunk) => chunk.trim().isNotEmpty).toList(); } String _cleanTextForTts(String text) { String cleanText = text // 修正markdown粗体格式,保留内容 .replaceAllMapped(RegExp(r'\*\*(.*?)\*\*'), (m) => m.group(1) ?? '') // 粗体:**文字** -> 文字 .replaceAllMapped( RegExp(r'\*(.*?)\*'), (m) => m.group(1) ?? '') // 斜体:*文字* -> 文字 .replaceAllMapped( RegExp(r'`([^`]+)`'), (m) => m.group(1) ?? '') // 行内代码:`代码` -> 代码 .replaceAll(RegExp(r'```[^`]*```', multiLine: true), '') // 代码块:完全移除 .replaceAll(RegExp(r'```[\s\S]*?```'), '') // 代码块:完全移除(备用) .replaceAll( RegExp(r'^#{1,6}\s+(.*)$', multiLine: true), r'$1') // 标题:# 标题 -> 标题 .replaceAll( RegExp(r'\[([^\]]+)\]\([^\)]+\)'), r'$1') // 链接:[文字](url) -> 文字 .replaceAll( RegExp(r'^>\s*(.*)$', multiLine: true), r'$1') // 引用:> 文字 -> 文字 .replaceAll(RegExp(r'^[-*+]\s+(.*)$', multiLine: true), r'$1') // 无序列表:- 项目 -> 项目 .replaceAll(RegExp(r'^\d+\.\s+(.*)$', multiLine: true), r'$1') // 有序列表:1. 项目 -> 项目 .replaceAll(RegExp(r'^\s*[-*]{3,}\s*$', multiLine: true), '') // 分隔线:移除 .replaceAll(RegExp(r'\n\s*\n'), '\n') // 多个换行替换为单个换行 .replaceAll(RegExp(r'\n'), ' ') // 换行替换为空格 // 移除代码相关的变量和符号(但保留正常文字中的这些字符) .replaceAll(RegExp(r'\$\d+(?=\s|$|[,。!?;:])'), '') // 移除独立的 $1, $2, $3 等 .replaceAll(RegExp(r'\$\{[^}]*\}'), '') // 移除 ${variable} 格式 .replaceAll(RegExp(r'(?<=\s|^)\$[a-zA-Z_]\w*(?=\s|$|[,。!?;:])'), '') // 移除独立的 $variable 格式 // 移除HTML相关内容 .replaceAll(RegExp(r'\\[ntr]'), ' ') // 移除转义字符 .replaceAll(RegExp(r'&[a-zA-Z0-9]+;'), ' ') // 移除HTML实体 .replaceAll(RegExp(r'<[^>]*>'), '') // 秼除HTML标签 // 移除明显的代码结构(保留正常文字中的括号) .replaceAll(RegExp(r'\{[^}]*\}(?=\s|$)'), '') // 移除独立的花括号内容 .replaceAll( RegExp(r'(?<=\s|^)@[a-zA-Z_]\w*(?=\s|$)'), '') // 移除独立的 @mentions // 移除纯编程符号行,但保留文字中的标点 .replaceAll( RegExp(r'^[#%&*+=|\\/<>^~`\s]*$', multiLine: true), '') // 移除纯符号行 .replaceAll( RegExp(r'(?<=\s)[#%&*+=|\\/<>^~`]+(?=\s)'), ' ') // 移除词间的编程符号 // 移除表情符号 .replaceAll(RegExp(r'[\u{1F600}-\u{1F64F}]', unicode: true), '') // 表情符号 .replaceAll( RegExp(r'[\u{1F300}-\u{1F5FF}]', unicode: true), '') // 符号和象形文字 .replaceAll( RegExp(r'[\u{1F680}-\u{1F6FF}]', unicode: true), '') // 运输和地图符号 .replaceAll(RegExp(r'[\u{1F1E0}-\u{1F1FF}]', unicode: true), '') // 旗帜 .replaceAll(RegExp(r'[\u{2600}-\u{26FF}]', unicode: true), '') // 杂项符号 // 移除特殊的非文字字符,但保留基本标点 .replaceAll( RegExp(r'[^\u4e00-\u9fa5a-zA-Z0-9\s\.,!?;:,。!?;:、""' '()()[\]【】《》〈〉「」『』\-—_\n]'), ' '); // 清理多余的空格和标点 cleanText = cleanText .replaceAll(RegExp(r'\s+'), ' ') // 多个空格替换为单个空格 .replaceAll(RegExp(r'[。]{2,}'), '。') // 多个句号替换为单个 .replaceAll(RegExp(r'[,]{2,}'), ',') // 多个逗号替换为单个 .replaceAll(RegExp(r'[!]{2,}'), '!') // 多个感叹号替换为单个 .replaceAll(RegExp(r'[?]{2,}'), '?') // 多个问号替换为单个 .replaceAll(RegExp(r'^\s+|\s+$'), '') // 移除首尾空格 .trim(); return cleanText; } // 单次语音播放方法 Future speak(String text) async { if (text.trim().isEmpty) return false; try { await stop(); await processCompleteText(text); return true; } catch (e) { Logger.i('TTS 播放错误: $e'); _onErrorHandler?.call('TTS 播放错误: $e'); return false; } } // 停止播放 Future stop() async { try { await _stopCurrentPlayback(); clearAll(); } catch (e) { Logger.i('停止音频播放错误: $e'); } } Future pause() async { try { if (_isPlaying) { await _flutterTts.pause(); } } catch (e) { Logger.i('暂停音频播放错误: $e'); } } Future resume() async { try { // flutter_tts没有resume方法,需要重新播放当前文本 // 这里可以根据需要实现 Logger.i('TTS不支持恢复,需要重新播放'); } catch (e) { Logger.i('恢复音频播放错误: $e'); } } bool get isPlaying => _isPlaying; bool get hasQueuedItems => _orderedTasks.isNotEmpty; int get queueLength => _orderedTasks.length; // 获取当前播放状态 bool get isCurrentlyPlaying => _isPlaying; // 获取任务队列状态信息 Map get sseQueueStatus => { 'totalTasks': _orderedTasks.length, 'completedTasks': _nextTaskIndex, 'isProcessing': _isProcessingTasks, 'streamCompleted': _streamCompleted, }; // 获取队列状态信息(保留兼容性) Map get queueStatus => { 'totalUrls': 0, 'currentIndex': 0, 'isQueuePlaying': false, 'isAllSegmentsProcessed': false, 'expectedSegments': 0, }; // 获取有序任务数量(用于测试) int getOrderedTasksCount() => _orderedTasks.length; // 获取下一个任务索引(用于测试) int getNextTaskIndex() => _nextTaskIndex; // dispose方法 void dispose() { clearAll(); // flutter_tts会自动清理资源 } /// 获取可用的TTS引擎列表 Future> getAvailableEngines() async { try { dynamic engines = await _flutterTts.getEngines; return engines ?? []; } catch (e) { Logger.i('获取TTS引擎列表失败: $e'); return []; } } /// 设置TTS引擎 Future setEngine(String engineName) async { try { int result = await _flutterTts.setEngine(engineName); Logger.i('设置TTS引擎: $engineName, 结果: $result'); return result == 1; // 1表示成功 } catch (e) { Logger.i('设置TTS引擎失败: $e'); return false; } } /// 获取当前默认引擎 Future getCurrentEngine() async { try { dynamic engine = await _flutterTts.getDefaultEngine; return engine?.toString(); } catch (e) { Logger.i('获取当前TTS引擎失败: $e'); return null; } } /// 获取支持的语言列表 Future> getSupportedLanguages() async { try { dynamic languages = await _flutterTts.getLanguages; return languages ?? []; } catch (e) { Logger.i('获取支持的语言列表失败: $e'); return []; } } /// 检查特定语言是否可用 Future isLanguageAvailable(String language) async { try { dynamic result = await _flutterTts.isLanguageAvailable(language); return result == true; } catch (e) { Logger.i('检查语言可用性失败: $e'); return false; } } } /// TTS任务类 class TtsTask { final int index; final String text; TtsTaskStatus status; TtsTask({ required this.index, required this.text, required this.status, }); } /// TTS任务状态枚举 enum TtsTaskStatus { ready, // 就绪 completed, // 已完成 failed, // 失败 }