115 lines
3.2 KiB
Dart
115 lines
3.2 KiB
Dart
import 'package:flutter_tts/flutter_tts.dart';
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
// 文字转语音
|
|
class TtsManage {
|
|
static const _tag = "文字转语音";
|
|
static const _iflytekEngine = "com.iflytek.speechcloud";
|
|
|
|
static final FlutterTts _flutterTts = FlutterTts();
|
|
static bool _isInitialized = false;
|
|
static bool _isAvailable = false;
|
|
static Future<void>? _initializing;
|
|
|
|
/// 初始化 TTS。
|
|
///
|
|
/// 收银终端只使用讯飞引擎;未安装或初始化失败时直接禁用语音,
|
|
/// 避免 TTS 插件异常阻断应用启动和收银流程。
|
|
static Future<void> initialize() async {
|
|
if (_isInitialized) return;
|
|
if (_initializing != null) {
|
|
await _initializing;
|
|
return;
|
|
}
|
|
|
|
final initializing = _initialize();
|
|
_initializing = initializing;
|
|
try {
|
|
await initializing;
|
|
} finally {
|
|
_initializing = null;
|
|
}
|
|
}
|
|
|
|
static Future<void> _initialize() async {
|
|
try {
|
|
final engines = await _flutterTts.getEngines;
|
|
final hasIflytekEngine = engines is List &&
|
|
engines.any((engine) => engine.toString() == _iflytekEngine);
|
|
if (!hasIflytekEngine) {
|
|
logger.w("|$_tag|未安装讯飞TTS引擎,已禁用语音播报");
|
|
return;
|
|
}
|
|
|
|
await _flutterTts.setEngine(_iflytekEngine);
|
|
await _flutterTts.setLanguage("zh-CN");
|
|
await _flutterTts.setVolume(1.0);
|
|
await _flutterTts.setSpeechRate(0.95);
|
|
await _flutterTts.setPitch(2.0);
|
|
_isAvailable = true;
|
|
} catch (e, stack) {
|
|
logger.w("|$_tag|讯飞TTS初始化失败,已禁用语音播报error:|$e|stack:|$stack|");
|
|
} finally {
|
|
_isInitialized = true;
|
|
}
|
|
}
|
|
|
|
/// 播放文本;讯飞 TTS 不可用时静默跳过。
|
|
static Future<void> speak(String text) async {
|
|
await initialize();
|
|
if (!_isAvailable) return;
|
|
|
|
try {
|
|
await _flutterTts.speak(text, focus: true);
|
|
} catch (e, stack) {
|
|
logger.w("|$_tag|语音播放失败error:|$e|stack:|$stack|");
|
|
}
|
|
}
|
|
|
|
// 停止播放
|
|
static Future<void> stop() async {
|
|
if (!_isAvailable) return;
|
|
try {
|
|
await _flutterTts.stop();
|
|
} catch (e, stack) {
|
|
logger.w("|$_tag|停止语音播放失败error:|$e|stack:|$stack|");
|
|
}
|
|
}
|
|
|
|
// 释放资源
|
|
static Future<void> dispose() async {
|
|
await stop();
|
|
}
|
|
|
|
/// 将数字转换为中文读法(手机号码格式)
|
|
/// 特别处理:1读为"幺",其他数字正常读法
|
|
static String numberToChineseReading(String number) {
|
|
if (number.isEmpty) return "";
|
|
// 数字到中文读法的映射
|
|
const Map<String, String> digitToChinese = {
|
|
'0': '零',
|
|
'1': '幺', // 手机号中1读为"幺"
|
|
'2': '二',
|
|
'3': '三',
|
|
'4': '四',
|
|
'5': '五',
|
|
'6': '六',
|
|
'7': '七',
|
|
'8': '八',
|
|
'9': '九',
|
|
};
|
|
// 逐位转换
|
|
StringBuffer result = StringBuffer();
|
|
for (int i = 0; i < number.length; i++) {
|
|
String digit = number[i];
|
|
if (digitToChinese.containsKey(digit)) {
|
|
result.write(digitToChinese[digit]);
|
|
} else {
|
|
// 如果不是数字,保持原样
|
|
result.write(digit);
|
|
}
|
|
}
|
|
return result.toString();
|
|
}
|
|
}
|