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.
236 lines
6.6 KiB
236 lines
6.6 KiB
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:speech_to_text/speech_to_text.dart';
|
|
import 'package:speech_to_text/speech_recognition_result.dart';
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// ASR状态枚举
|
|
enum AsrState {
|
|
notInitialized, // 未初始化
|
|
initialized, // 已初始化
|
|
listening, // 正在监听
|
|
notListening, // 未监听
|
|
error // 错误状态
|
|
}
|
|
|
|
/// Flutter ASR服务,负责本地语音转文本功能
|
|
class FlutterAsrService extends GetxService {
|
|
// 语音识别实例
|
|
late SpeechToText _speech;
|
|
|
|
// 当前ASR状态
|
|
final Rx<AsrState> _asrState = AsrState.notInitialized.obs;
|
|
AsrState get asrState => _asrState.value;
|
|
|
|
// 可用的语音识别语言
|
|
final RxList<LocaleName> _locales = <LocaleName>[].obs;
|
|
List<LocaleName> get locales => _locales;
|
|
|
|
// 当前选择的语言
|
|
final RxString _currentLocale = ''.obs;
|
|
String get currentLocale => _currentLocale.value;
|
|
|
|
// 识别结果
|
|
final RxString _lastWords = ''.obs;
|
|
String get lastWords => _lastWords.value;
|
|
|
|
// 识别准确度
|
|
final RxDouble _confidence = 0.0.obs;
|
|
double get confidence => _confidence.value;
|
|
|
|
// 是否可用
|
|
final RxBool _isAvailable = false.obs;
|
|
bool get isAvailable => _isAvailable.value;
|
|
|
|
// 是否正在监听
|
|
bool get isListening => _speech.isListening;
|
|
|
|
// 识别结果流
|
|
final _recognitionResultsController = StreamController<String>.broadcast();
|
|
Stream<String> get recognitionResults => _recognitionResultsController.stream;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_initSpeech();
|
|
}
|
|
|
|
/// 初始化语音识别
|
|
Future<void> _initSpeech() async {
|
|
_speech = SpeechToText();
|
|
|
|
try {
|
|
final available = await _speech.initialize(
|
|
onStatus: _onStatusChanged,
|
|
onError: _onErrorListener,
|
|
debugLogging: kDebugMode,
|
|
);
|
|
|
|
_isAvailable.value = available;
|
|
|
|
if (available) {
|
|
_asrState.value = AsrState.initialized;
|
|
|
|
// 获取可用的语言
|
|
final locales = await _speech.locales();
|
|
_locales.assignAll(locales);
|
|
|
|
// 默认选择系统语言,如果支持的话
|
|
final systemLocale = await _speech.systemLocale();
|
|
if (systemLocale != null) {
|
|
_currentLocale.value = systemLocale.localeId;
|
|
} else if (_locales.isNotEmpty) {
|
|
_currentLocale.value = _locales.first.localeId;
|
|
}
|
|
|
|
Logger.info('语音识别初始化成功,可用语言数量: ${_locales.length}');
|
|
} else {
|
|
_asrState.value = AsrState.error;
|
|
Logger.error('语音识别不可用');
|
|
}
|
|
} catch (e) {
|
|
_asrState.value = AsrState.error;
|
|
_isAvailable.value = false;
|
|
Logger.error('语音识别初始化失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 开始监听
|
|
///
|
|
/// [listenFor] 监听时长,默认30秒
|
|
/// [pauseFor] 暂停时长,默认3秒,超过此时长无语音输入会自动停止
|
|
/// [partialResults] 是否返回部分结果,默认true
|
|
/// [onDevice] 是否使用设备本地识别,默认false
|
|
/// [listenMode] 监听模式,默认设备默认模式
|
|
Future<bool> startListening({
|
|
Duration listenFor = const Duration(seconds: 30),
|
|
Duration pauseFor = const Duration(seconds: 3),
|
|
bool partialResults = true,
|
|
bool onDevice = false,
|
|
ListenMode listenMode = ListenMode.deviceDefault,
|
|
}) async {
|
|
if (!_isAvailable.value) {
|
|
Logger.error('语音识别不可用,无法开始监听');
|
|
return false;
|
|
}
|
|
|
|
// 清空上次的识别结果
|
|
_lastWords.value = '';
|
|
_confidence.value = 0.0;
|
|
|
|
try {
|
|
final started = await _speech.listen(
|
|
onResult: _onSpeechResult,
|
|
listenFor: listenFor,
|
|
pauseFor: pauseFor,
|
|
localeId: _currentLocale.value,
|
|
listenOptions: SpeechListenOptions(
|
|
partialResults: partialResults,
|
|
onDevice: onDevice,
|
|
listenMode: listenMode,
|
|
),
|
|
);
|
|
|
|
if (started) {
|
|
_asrState.value = AsrState.listening;
|
|
Logger.info('开始语音识别监听');
|
|
} else {
|
|
Logger.error('开始语音识别监听失败');
|
|
}
|
|
|
|
return started;
|
|
} catch (e) {
|
|
Logger.error('开始语音识别监听出错: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 停止监听
|
|
Future<void> stopListening() async {
|
|
if (!_speech.isListening) return;
|
|
|
|
try {
|
|
await _speech.stop();
|
|
_asrState.value = AsrState.notListening;
|
|
Logger.info('停止语音识别监听');
|
|
} catch (e) {
|
|
Logger.error('停止语音识别监听出错: $e');
|
|
}
|
|
}
|
|
|
|
/// 取消监听
|
|
Future<void> cancelListening() async {
|
|
if (!_speech.isListening) return;
|
|
|
|
try {
|
|
await _speech.cancel();
|
|
_lastWords.value = '';
|
|
_confidence.value = 0.0;
|
|
_asrState.value = AsrState.notListening;
|
|
Logger.info('取消语音识别监听');
|
|
} catch (e) {
|
|
Logger.error('取消语音识别监听出错: $e');
|
|
}
|
|
}
|
|
|
|
/// 设置当前语言
|
|
void setLocale(String localeId) {
|
|
if (_currentLocale.value != localeId) {
|
|
_currentLocale.value = localeId;
|
|
Logger.info('设置语音识别语言: $localeId');
|
|
}
|
|
}
|
|
|
|
/// 语音识别结果回调
|
|
void _onSpeechResult(SpeechRecognitionResult result) {
|
|
final resultText = result.recognizedWords;
|
|
_lastWords.value = resultText;
|
|
|
|
if (result.finalResult) {
|
|
// 计算置信度 - 仅在最终结果时有效
|
|
if (result.alternates.isNotEmpty) {
|
|
_confidence.value = result.alternates.first.confidence;
|
|
}
|
|
|
|
// 发送最终结果到流
|
|
_recognitionResultsController.add(resultText);
|
|
|
|
Logger.info('语音识别最终结果: "$resultText", 置信度: ${_confidence.value}');
|
|
_asrState.value = AsrState.notListening;
|
|
} else {
|
|
// 部分结果
|
|
Logger.info('语音识别部分结果: "$resultText"');
|
|
}
|
|
}
|
|
|
|
/// 状态变化回调
|
|
void _onStatusChanged(String status) {
|
|
Logger.info('语音识别状态变化: $status');
|
|
|
|
switch (status) {
|
|
case 'listening':
|
|
_asrState.value = AsrState.listening;
|
|
break;
|
|
case 'notListening':
|
|
_asrState.value = AsrState.notListening;
|
|
break;
|
|
case 'done':
|
|
_asrState.value = AsrState.notListening;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 错误回调
|
|
void _onErrorListener(dynamic error) {
|
|
Logger.error('语音识别错误: ${error.errorMsg}');
|
|
_asrState.value = AsrState.error;
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_speech.cancel();
|
|
_recognitionResultsController.close();
|
|
super.onClose();
|
|
}
|
|
}
|