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.

384 lines
12 KiB

import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get/get.dart';
import '../../core/utils/logger.dart';
/// 识别事件类型
enum RecognitionEventType {
started,
recognizing,
finalResult,
error,
completed,
}
/// 识别事件
class RecognitionEvent {
final RecognitionEventType type;
final String text;
final String? error;
RecognitionEvent({
required this.type,
this.text = '',
this.error,
});
}
/// 火山语音识别服务
///
/// 该服务提供了通过平台通道与 Android 上的火山语音识别 SDK 交互的接口
class VolcanoAsrService extends GetxService {
// 平台通道
static const MethodChannel _channel = MethodChannel('com.example.deep_voice/volcano_asr');
static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/volcano_asr_events');
// 配置参数
late final String _appId;
late final String _token;
late final String _cluster; // 集群区域
late final String? _resourceId; // 资源ID,用于大模型流式识别API鉴权
// 状态变量
final _isListening = false.obs;
final _isInitialized = false.obs;
final _errorMessage = ''.obs;
final _recognitionResults = <String>[].obs;
// 是否使用标准语音识别SDK
final bool _useStandardASR = true; // 使用标准语音识别SDK (API v2)
// 添加识别结果流控制器
final _recognitionStreamController = StreamController<RecognitionEvent>.broadcast();
// 事件订阅
StreamSubscription? _eventSubscription;
// 最新的识别结果
final _latestRecognizedText = ''.obs;
String get latestRecognizedText => _latestRecognizedText.value;
/// 检查连续识别是否处于活动状态
bool isContinuousRecognitionActive() {
return _isListening.value;
}
VolcanoSrcService() {
// 从环境变量获取配置
_appId = dotenv.env['VOLCANO_ASR_APP_ID'] ?? '';
_token = dotenv.env['VOLCANO_ASR_APP_TOKEN'] ?? '';
// 从环境变量获取集群区域
_cluster = dotenv.env['VOLCANO_ASR_CLUSTER'] ?? '';
// 从环境变量获取资源ID
_resourceId = dotenv.env['VOLCANO_ASR_RESOURCE_ID'];
Logger.info('火山语音识别配置: APP_ID=${_appId.isNotEmpty ? "已设置" : "未设置"}, APP_KEY=${_token.isNotEmpty ? "已设置" : "未设置"}');
Logger.info('使用标准语音识别SDK: $_useStandardASR');
Logger.info('集群区域: $_cluster');
Logger.info('资源ID: $_resourceId');
if (_appId.isEmpty || _token.isEmpty) {
_errorMessage.value = '火山语音识别配置不完整,请检查环境变量';
Logger.error(_errorMessage.value);
}
}
// 获取可观察状态
RxBool get isListening => _isListening;
bool get isInitialized => _isInitialized.value;
String get errorMessage => _errorMessage.value;
List<String> get recognitionResults => _recognitionResults;
// 添加识别结果流getter
Stream<RecognitionEvent> get recognitionStream => _recognitionStreamController.stream;
@override
void onInit() {
super.onInit();
_setupMethodCallHandler();
}
/// 设置方法通道处理器
Future<void> _setupMethodCallHandler() async {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onRecognitionResult':
final String result = call.arguments as String;
_onRecognitionResult(result);
break;
case 'onRecognitionError':
final String error = call.arguments as String;
_errorMessage.value = error;
Logger.error('识别错误: $error');
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.error,
error: error,
));
break;
case 'onListeningStateChanged':
final bool isListening = call.arguments as bool;
_isListening.value = isListening;
Logger.info('监听状态变化: $isListening');
break;
}
});
}
/// 处理识别结果
void _onRecognitionResult(String result) {
if (result.isNotEmpty) {
_recognitionResults.add(result);
// 将结果发送到流
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.finalResult,
text: result,
));
Logger.info('识别结果: $result');
}
}
/// 初始化语音识别引擎
Future<bool> initialize() async {
if (_isInitialized.value) return true;
if (_appId.isEmpty || _token.isEmpty) {
_errorMessage.value = '火山语音识别配置不完整,请检查环境变量';
Logger.error(_errorMessage.value);
return false;
}
try {
_errorMessage.value = '';
Logger.info('开始初始化火山语音识别服务,APP_ID: ${_appId.substring(0, math.min(3, _appId.length))}***,APP_KEY: ${_token.length > 10 ? "${_token.substring(0, 5)}..." : _token}');
Logger.info('使用标准语音识别SDK配置 (API v2)');
Logger.info('集群区域: $_cluster');
Logger.info('资源ID: $_resourceId');
final Map<String, dynamic> params = {
'subscriptionKey': _appId,
'serviceRegion': _token,
'cluster': _cluster,
'resourceId': _resourceId,
};
final bool result = await _channel.invokeMethod('initialize', params);
if (result) {
_isInitialized.value = true;
Logger.info('火山语音识别服务初始化成功');
} else {
_errorMessage.value = '初始化失败,请检查配置';
Logger.error('火山语音识别服务初始化失败');
Logger.info('请检查以下可能的问题:');
Logger.info('1. 确保您的APP_ID和APP_KEY正确');
Logger.info('2. 确保您的应用ID和密钥正确且有效');
Logger.info('3. 确保您的应用已被授权使用语音识别服务');
}
return result;
} catch (e) {
_errorMessage.value = '初始化异常: $e';
Logger.error('初始化火山语音识别服务异常: $e');
Logger.info('请检查以下可能的问题:');
Logger.info('1. 确保您的网络连接正常');
Logger.info('2. 确保集群区域设置正确 (当前: $_cluster)');
Logger.info('3. 确保请求参数格式正确');
Logger.info('4. 确保Token格式正确,需要添加Bearer;前缀');
_isInitialized.value = false;
return false;
}
}
/// 开始一次性识别
Future<bool> startOneTimeRecognition() async {
if (_isListening.value) {
Logger.warning('已经在进行语音识别,请先停止当前识别');
return false;
}
if (!_isInitialized.value) {
try {
final bool initialized = await initialize();
if (!initialized) {
Logger.error('语音识别服务未初始化,无法开始识别');
_errorMessage.value = '语音识别服务未初始化,无法开始识别';
return false;
}
} catch (e) {
Logger.error('初始化语音识别服务失败: $e');
_errorMessage.value = '初始化语音识别服务失败: $e';
return false;
}
}
try {
_errorMessage.value = '';
_recognitionResults.clear();
final bool result = await _channel.invokeMethod('recognizeOnce');
_isListening.value = result;
if (result) {
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.started,
text: '',
));
}
return result;
} catch (e) {
Logger.error('开始一次性识别失败: $e');
_errorMessage.value = e.toString();
return false;
}
}
/// 开始连续识别
Future<bool> startContinuousRecognition() async {
//启动前先尝试停止当前识别
if (_isListening.value) {
Logger.warning('已经在进行语音识别,请先停止当前识别');
return false;
}
if (!_isInitialized.value) {
try {
final bool initialized = await initialize();
if (!initialized) {
Logger.error('语音识别服务未初始化,无法开始识别');
_errorMessage.value = '语音识别服务未初始化,无法开始识别';
return false;
}
} catch (e) {
Logger.error('初始化语音识别服务失败: $e');
_errorMessage.value = '初始化语音识别服务失败: $e';
return false;
}
}
try {
_errorMessage.value = '';
_recognitionResults.clear();
// 设置事件监听
_eventSubscription = _eventChannel
.receiveBroadcastStream()
.listen(_handleNativeEvent, onError: (error) {
_errorMessage.value = error.toString();
_isListening.value = false;
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.error,
error: error.toString(),
));
});
final bool result = await _channel.invokeMethod('startContinuousRecognition');
_isListening.value = result;
if (result) {
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.started,
text: '',
));
}
return result;
} catch (e) {
Logger.error('开始连续识别失败: $e');
_errorMessage.value = e.toString();
_cleanupEventStream();
return false;
}
}
/// 处理来自原生端的事件
void _handleNativeEvent(dynamic event) {
if (event is! Map) return;
final Map<dynamic, dynamic> eventMap = event;
final String eventType = eventMap['eventType'] as String? ?? '';
switch (eventType) {
case 'recognizing':
final String text = eventMap['text'] as String? ?? '';
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.recognizing,
text: text,
));
break;
case 'finalResult':
final String text = eventMap['text'] as String? ?? '';
_latestRecognizedText.value = text;
_recognitionResults.add(text);
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.finalResult,
text: text,
));
break;
case 'error':
final String error = eventMap['error'] as String? ?? '未知错误';
_errorMessage.value = error;
_isListening.value = false;
_recognitionStreamController.add(RecognitionEvent(
type: RecognitionEventType.error,
error: error,
));
break;
}
}
/// 清理事件流
void _cleanupEventStream() {
_eventSubscription?.cancel();
_eventSubscription = null;
}
/// 停止识别
Future<bool> stopRecognition() async {
if (!_isListening.value) {
Logger.warning('当前没有进行语音识别');
return false;
}
try {
final bool result = await _channel.invokeMethod('stopContinuousRecognition');
_isListening.value = !result;
if (result) {
_cleanupEventStream();
}
return result;
} catch (e) {
Logger.error('停止识别失败: $e');
_errorMessage.value = e.toString();
return false;
}
}
/// 清理资源
Future<void> dispose() async {
try {
if (_isListening.value) {
await stopRecognition();
}
_cleanupEventStream();
} catch (e) {
Logger.error('清理语音识别资源失败: $e');
}
}
@override
void onClose() {
_eventSubscription?.cancel();
_recognitionStreamController.close();
super.onClose();
}
}