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.
376 lines
10 KiB
376 lines
10 KiB
import 'dart:async';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:get/get.dart';
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// 识别事件类型
|
|
enum RecognitionEventType {
|
|
/// 最终识别结果
|
|
finalResult,
|
|
|
|
/// 中间识别结果(实时反馈)
|
|
recognizing,
|
|
|
|
/// 会话开始
|
|
sessionStarted,
|
|
|
|
/// 会话结束
|
|
sessionStopped,
|
|
|
|
/// 识别取消
|
|
canceled,
|
|
|
|
/// 无匹配结果
|
|
noMatch,
|
|
|
|
/// 识别错误
|
|
error,
|
|
}
|
|
|
|
/// 识别事件
|
|
class RecognitionEvent {
|
|
/// 事件类型
|
|
final RecognitionEventType type;
|
|
|
|
/// 识别文本(仅在 finalResult 和 recognizing 类型中有效)
|
|
final String text;
|
|
|
|
/// 错误信息(仅在 error 和 canceled 类型中有效)
|
|
final String? error;
|
|
|
|
/// 取消原因(仅在 canceled 类型中有效)
|
|
final String? reason;
|
|
|
|
/// 错误详情(仅在 canceled 类型中有效)
|
|
final String? errorDetails;
|
|
|
|
RecognitionEvent({
|
|
required this.type,
|
|
this.text = '',
|
|
this.error,
|
|
this.reason,
|
|
this.errorDetails,
|
|
});
|
|
|
|
/// 从原生事件映射创建识别事件
|
|
factory RecognitionEvent.fromMap(Map<dynamic, dynamic> map) {
|
|
final eventType = map['eventType'] as String;
|
|
|
|
RecognitionEventType type;
|
|
switch (eventType) {
|
|
case 'recognizing':
|
|
type = RecognitionEventType.recognizing;
|
|
break;
|
|
case 'finalResult':
|
|
type = RecognitionEventType.finalResult;
|
|
break;
|
|
case 'sessionStarted':
|
|
type = RecognitionEventType.sessionStarted;
|
|
break;
|
|
case 'sessionStopped':
|
|
type = RecognitionEventType.sessionStopped;
|
|
break;
|
|
case 'canceled':
|
|
type = RecognitionEventType.canceled;
|
|
break;
|
|
case 'noMatch':
|
|
type = RecognitionEventType.noMatch;
|
|
break;
|
|
case 'error':
|
|
type = RecognitionEventType.error;
|
|
break;
|
|
default:
|
|
throw ArgumentError('Unknown event type: $eventType');
|
|
}
|
|
|
|
return RecognitionEvent(
|
|
type: type,
|
|
text: map['text'] as String? ?? '',
|
|
error: map['error'] as String?,
|
|
reason: map['reason'] as String?,
|
|
errorDetails: map['errorDetails'] as String?,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return 'RecognitionEvent{type: $type, text: $text, error: $error, reason: $reason, errorDetails: $errorDetails}';
|
|
}
|
|
}
|
|
|
|
/// Azure 语音识别服务
|
|
class AzureAsrService extends GetxService {
|
|
static AzureAsrService get to => Get.find();
|
|
|
|
// 方法通道
|
|
static const MethodChannel _methodChannel = MethodChannel('com.example.deep_voice/azure_asr');
|
|
|
|
// 事件通道
|
|
static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/azure_asr_events');
|
|
|
|
// 是否已初始化
|
|
bool _isInitialized = false;
|
|
|
|
// 连续识别相关
|
|
bool _isContinuousRecognitionActive = false;
|
|
StreamController<RecognitionEvent>? _eventStreamController;
|
|
StreamSubscription? _eventSubscription;
|
|
|
|
// 公开的事件流
|
|
Stream<RecognitionEvent>? _recognitionStream;
|
|
Stream<RecognitionEvent>? get recognitionStream => _recognitionStream;
|
|
|
|
// 最新的识别结果
|
|
String _latestRecognizedText = '';
|
|
String get latestRecognizedText => _latestRecognizedText;
|
|
|
|
/// 初始化 Azure 语音识别服务
|
|
///
|
|
/// [subscriptionKey] Azure 语音服务的订阅密钥
|
|
/// [serviceRegion] Azure 语音服务的区域
|
|
/// [language] 识别语言,默认为中文
|
|
Future<bool> initialize({
|
|
required String subscriptionKey,
|
|
required String serviceRegion,
|
|
String language = 'zh-CN',
|
|
}) async {
|
|
if (_isInitialized) return true;
|
|
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>(
|
|
'initialize',
|
|
{
|
|
'subscriptionKey': subscriptionKey,
|
|
'serviceRegion': serviceRegion,
|
|
'language': language,
|
|
},
|
|
);
|
|
|
|
_isInitialized = result ?? false;
|
|
|
|
if (_isInitialized) {
|
|
Logger.info('Azure 语音识别服务初始化成功');
|
|
} else {
|
|
Logger.error('Azure 语音识别服务初始化失败');
|
|
}
|
|
|
|
return _isInitialized;
|
|
} catch (e) {
|
|
Logger.error('初始化 Azure 语音识别服务失败', e);
|
|
_isInitialized = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 重置识别器
|
|
/// 在切换识别模式前调用,确保资源被正确释放
|
|
Future<bool> _resetRecognizer() async {
|
|
try {
|
|
// 如果连续识别正在进行,先停止它
|
|
if (_isContinuousRecognitionActive) {
|
|
await stopContinuousRecognition();
|
|
}
|
|
|
|
// 清理事件流资源
|
|
_cleanupEventStream();
|
|
|
|
// 调用原生方法重置识别器
|
|
await _methodChannel.invokeMethod<bool>('resetRecognizer');
|
|
|
|
Logger.info('Azure 语音识别器已重置');
|
|
return true;
|
|
} catch (e) {
|
|
Logger.error('重置 Azure 语音识别器失败', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 执行一次性语音识别
|
|
Future<String> recognizeOnce() async {
|
|
_checkInitialized();
|
|
|
|
try {
|
|
// 重置识别器,确保资源被正确释放
|
|
await _resetRecognizer();
|
|
|
|
final result = await _methodChannel.invokeMethod<String>('recognizeOnce');
|
|
final text = result ?? '';
|
|
_latestRecognizedText = text;
|
|
return text;
|
|
} catch (e) {
|
|
Logger.error('一次性语音识别失败', e);
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 开始连续语音识别
|
|
///
|
|
/// 返回一个布尔值,表示是否成功启动连续识别
|
|
Future<bool> startContinuousRecognition() async {
|
|
_checkInitialized();
|
|
|
|
if (_isContinuousRecognitionActive) {
|
|
Logger.warning('连续识别已经在进行中');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// 重置识别器,确保资源被正确释放
|
|
await _resetRecognizer();
|
|
|
|
// 创建事件流控制器
|
|
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
|
|
|
|
// 设置事件监听
|
|
_eventSubscription = _eventChannel
|
|
.receiveBroadcastStream()
|
|
.map<RecognitionEvent>((dynamic event) => RecognitionEvent.fromMap(event))
|
|
.listen(
|
|
(event) {
|
|
_handleRecognitionEvent(event);
|
|
_eventStreamController?.add(event);
|
|
},
|
|
onError: (error) {
|
|
Logger.error('Azure 语音识别事件流错误', error);
|
|
_eventStreamController?.addError(error);
|
|
_cleanupEventStream();
|
|
},
|
|
);
|
|
|
|
// 开始连续识别
|
|
final result = await _methodChannel.invokeMethod<bool>('startContinuousRecognition');
|
|
_isContinuousRecognitionActive = result ?? false;
|
|
|
|
// 设置公开的流
|
|
_recognitionStream = _eventStreamController?.stream;
|
|
|
|
return _isContinuousRecognitionActive;
|
|
} catch (e) {
|
|
Logger.error('开始连续语音识别失败', e);
|
|
_cleanupEventStream();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 停止连续语音识别
|
|
Future<bool> stopContinuousRecognition() async {
|
|
_checkInitialized();
|
|
|
|
if (!_isContinuousRecognitionActive) {
|
|
Logger.warning('连续识别未在进行中');
|
|
return true; // 已经停止,直接返回成功
|
|
}
|
|
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>('stopContinuousRecognition');
|
|
final success = result ?? false;
|
|
|
|
if (success) {
|
|
_isContinuousRecognitionActive = false;
|
|
// 不立即清理事件流,等待 sessionStopped 事件
|
|
}
|
|
|
|
return success;
|
|
} catch (e) {
|
|
Logger.error('停止连续语音识别失败', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 设置识别语言
|
|
Future<bool> setRecognitionLanguage(String language) async {
|
|
_checkInitialized();
|
|
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>(
|
|
'setRecognitionLanguage',
|
|
{'language': language},
|
|
);
|
|
return result ?? false;
|
|
} catch (e) {
|
|
Logger.error('设置识别语言失败', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 检查连续识别是否活跃
|
|
bool isContinuousRecognitionActive() {
|
|
return _isContinuousRecognitionActive;
|
|
}
|
|
|
|
/// 处理来自原生端的识别事件
|
|
void _handleRecognitionEvent(RecognitionEvent event) {
|
|
switch (event.type) {
|
|
case RecognitionEventType.finalResult:
|
|
_latestRecognizedText = event.text;
|
|
break;
|
|
case RecognitionEventType.sessionStopped:
|
|
_isContinuousRecognitionActive = false;
|
|
// 延迟清理事件流,确保所有事件都被处理
|
|
Future.delayed(Duration(milliseconds: 500), _cleanupEventStream);
|
|
break;
|
|
case RecognitionEventType.canceled:
|
|
_isContinuousRecognitionActive = false;
|
|
// 延迟清理事件流,确保所有事件都被处理
|
|
Future.delayed(Duration(milliseconds: 500), _cleanupEventStream);
|
|
break;
|
|
default:
|
|
// 其他事件类型不需要特殊处理
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 清理事件流资源
|
|
void _cleanupEventStream() {
|
|
_eventSubscription?.cancel();
|
|
_eventSubscription = null;
|
|
|
|
// 只有在控制器存在且未关闭时才关闭
|
|
if (_eventStreamController != null && !_eventStreamController!.isClosed) {
|
|
_eventStreamController?.close();
|
|
}
|
|
_eventStreamController = null;
|
|
|
|
_recognitionStream = null;
|
|
}
|
|
|
|
/// 检查是否已初始化
|
|
void _checkInitialized() {
|
|
if (!_isInitialized) {
|
|
throw StateError('Azure 语音识别服务未初始化');
|
|
}
|
|
}
|
|
|
|
/// 释放资源
|
|
Future<void> dispose() async {
|
|
try {
|
|
if (_isContinuousRecognitionActive) {
|
|
await stopContinuousRecognition();
|
|
}
|
|
|
|
await _eventSubscription?.cancel();
|
|
_eventSubscription = null;
|
|
|
|
// 只有在控制器存在且未关闭时才关闭
|
|
if (_eventStreamController != null && !_eventStreamController!.isClosed) {
|
|
await _eventStreamController?.close();
|
|
}
|
|
_eventStreamController = null;
|
|
|
|
_recognitionStream = null;
|
|
|
|
await _methodChannel.invokeMethod<bool>('dispose');
|
|
|
|
_isInitialized = false;
|
|
Logger.info('Azure 语音识别服务资源已释放');
|
|
} catch (e) {
|
|
Logger.error('释放 Azure 语音识别服务资源失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
dispose();
|
|
super.onClose();
|
|
}
|
|
}
|