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

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();
}
}