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