Files
xianyan/lib/core/services/input/speech_service.dart
Developer d2bd53f3bc refactor: 完成v6.5.28版本迭代,修复多项体验问题
主要变更:
1. 重构多处Provider初始化逻辑,使用Future.microtask避免build阶段修改state
2. 重命名"灵感"模块为"工作流","天气诗词"改为"情景诗词"
3. 新增插件系统页面与路由,添加speech_to_text_windows插件支持
4. 优化玻璃容器性能,添加性能节流与模糊值适配
5. 新增离线横幅、阅读体验控制器、手势控制器等组件
6. 完善头像审核状态展示与缓存管理
7. 修复键盘弹出与页面路由问题
8. 更新依赖与项目配置,优化widget默认数据
2026-05-26 01:47:47 +08:00

247 lines
6.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — 语音输入服务
/// 创建时间: 2026-05-25
/// 更新时间: 2026-05-25
/// 作用: 基于speech_to_text实现语音转文字
/// 上次更新: 初始创建
/// ============================================================
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:speech_to_text/speech_recognition_error.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';
import '../../utils/logger.dart';
import '../device/haptic_service.dart';
/// 语音识别服务状态
enum SpeechServiceState { unavailable, available, listening, processing, error }
/// 语音识别结果
class SpeechResult {
const SpeechResult({
required this.text,
required this.isFinal,
this.confidence = 0.0,
});
final String text;
final bool isFinal;
final double confidence;
}
/// 语音输入服务 — 全局单例
///
/// 基于speech_to_text实现语音转文字支持中文和英文。
/// 与HapticService集成开始/停止时触觉反馈。
class SpeechService {
SpeechService._();
static final SpeechService instance = SpeechService._();
final SpeechToText _speech = SpeechToText();
SpeechServiceState _state = SpeechServiceState.unavailable;
String _lastResult = '';
String _currentLocale = 'zh_CN';
final _stateController = StreamController<SpeechServiceState>.broadcast();
final _resultController = StreamController<SpeechResult>.broadcast();
final _errorController = StreamController<String>.broadcast();
/// 语音识别状态流
Stream<SpeechServiceState> get onStateChanged => _stateController.stream;
/// 识别结果流
Stream<SpeechResult> get onResult => _resultController.stream;
/// 错误流
Stream<String> get onError => _errorController.stream;
/// 当前状态
SpeechServiceState get state => _state;
/// 语音识别是否可用
bool get isAvailable => _state != SpeechServiceState.unavailable;
/// 是否正在监听
bool get isListening => _state == SpeechServiceState.listening;
/// 最后一次识别结果
String get lastResult => _lastResult;
/// 当前语言
String get currentLocale => _currentLocale;
/// 初始化语音识别
///
/// 请求权限并初始化引擎,返回是否可用。
Future<bool> init() async {
if (_state != SpeechServiceState.unavailable) return isAvailable;
try {
final available = await _speech.initialize(
onError: _onError,
onStatus: _onStatus,
debugLogging: kDebugMode,
);
if (available) {
_state = SpeechServiceState.available;
_emitState();
Log.i('SpeechService: 初始化成功');
} else {
Log.w('SpeechService: 语音识别不可用');
}
return available;
} catch (e) {
Log.e('SpeechService: 初始化失败', e);
_state = SpeechServiceState.error;
_emitState();
return false;
}
}
/// 开始语音识别
///
/// [localeId] 语言标识默认zh_CN可传en_US切换英文。
Future<void> startListening({String? localeId}) async {
if (!isAvailable || isListening) return;
_currentLocale = localeId ?? 'zh_CN';
try {
await _speech.listen(
onResult: _onSpeechResult,
listenOptions: SpeechListenOptions(
localeId: _currentLocale,
pauseFor: const Duration(seconds: 5),
listenFor: const Duration(seconds: 30),
cancelOnError: true,
listenMode: ListenMode.dictation,
),
);
HapticService.light();
Log.i('SpeechService: 开始监听 (locale=$_currentLocale)');
} catch (e) {
Log.e('SpeechService: 启动监听失败', e);
_state = SpeechServiceState.error;
_emitState();
_emitError('启动语音识别失败');
}
}
/// 停止语音识别
///
/// 停止后仍会处理已接收的音频,可能产生最终结果。
Future<void> stopListening() async {
if (!isListening) return;
try {
await _speech.stop();
HapticService.light();
Log.i('SpeechService: 停止监听');
} catch (e) {
Log.e('SpeechService: 停止监听失败', e);
}
}
/// 取消语音识别
///
/// 取消后不会产生最终结果。
Future<void> cancelListening() async {
if (!isListening) return;
try {
await _speech.cancel();
HapticService.light();
Log.i('SpeechService: 取消监听');
} catch (e) {
Log.e('SpeechService: 取消监听失败', e);
}
}
/// 切换语言
void setLocale(String localeId) {
_currentLocale = localeId;
Log.i('SpeechService: 切换语言为 $localeId');
}
/// 获取可用语言列表
Future<List<LocaleName>> get locales => _speech.locales();
/// 获取系统默认语言
Future<String> get systemLocale async {
final locale = await _speech.systemLocale();
return locale?.localeId ?? 'zh_CN';
}
/// 释放资源
void dispose() {
_stateController.close();
_resultController.close();
_errorController.close();
}
// ============================================================
// 内部回调
// ============================================================
void _onSpeechResult(SpeechRecognitionResult result) {
final speechResult = SpeechResult(
text: result.recognizedWords,
isFinal: result.finalResult,
confidence: result.confidence,
);
if (result.finalResult && result.recognizedWords.isNotEmpty) {
_lastResult = result.recognizedWords;
}
if (!_resultController.isClosed) {
_resultController.add(speechResult);
}
}
void _onStatus(String status) {
Log.d('SpeechService: 状态变化 $status');
switch (status) {
case 'listening':
_state = SpeechServiceState.listening;
case 'notListening':
_state = SpeechServiceState.available;
case 'done':
_state = SpeechServiceState.available;
default:
break;
}
_emitState();
}
void _onError(SpeechRecognitionError error) {
Log.e(
'SpeechService: 识别错误 ${error.errorMsg} (permanent=${error.permanent})',
);
if (error.permanent) {
_state = SpeechServiceState.error;
_emitState();
}
_emitError(error.errorMsg);
}
void _emitState() {
if (!_stateController.isClosed) {
_stateController.add(_state);
}
}
void _emitError(String message) {
if (!_errorController.isClosed) {
_errorController.add(message);
}
}
}