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.

381 lines
11 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 VolcanoVoiceRecognitionService extends GetxService {
static const MethodChannel _channel = MethodChannel('com.example.deep_voice/speech_recognition');
static const EventChannel _eventChannel = EventChannel('com.example.deep_voice/speech_recognition_events');
bool _isInitialized = false;
late final String _subscriptionKey;
late final String _serviceRegion;
// 连续识别相关
bool _isContinuousRecognitionActive = false;
StreamController<RecognitionEvent>? _eventStreamController;
StreamSubscription? _eventSubscription;
// 公开的事件流
Stream<RecognitionEvent>? _recognitionStream;
Stream<RecognitionEvent>? get recognitionStream => _recognitionStream;
// 最新的识别结果
final _latestRecognizedText = ''.obs;
String get latestRecognizedText => _latestRecognizedText.value;
// 识别状态
final isListening = false.obs;
// 识别结果列表
final RxList<String> _recognitionResults = <String>[].obs;
List<String> get recognitionResults => _recognitionResults;
// 错误信息
final RxString _errorMessage = ''.obs;
String get errorMessage => _errorMessage.value;
VolcanoVoiceRecognitionService() {
_loadConfig();
}
/// 从环境变量加载配置
void _loadConfig() {
// 只使用统一的APP_ID和APP_KEY
_subscriptionKey = dotenv.env['VOLCANO_APP_ID'] ?? '';
_serviceRegion = dotenv.env['VOLCANO_APP_KEY'] ?? '';
Logger.info('火山语音识别配置: APP_ID=${_subscriptionKey.isNotEmpty ? "已设置" : "未设置"}, APP_KEY=${_serviceRegion.isNotEmpty ? "已设置" : "未设置"}');
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) {
throw Exception('未找到火山语音服务配置。请在 .env 文件中设置 VOLCANO_APP_ID 和 VOLCANO_APP_KEY');
}
}
@override
void onInit() {
super.onInit();
_setupMethodCallHandler();
}
/// 设置方法通道处理器
void _setupMethodCallHandler() {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onRecognitionResult':
final String result = call.arguments as String;
_handleRecognitionResult(result);
break;
case 'onRecognitionError':
final String error = call.arguments as String;
_handleRecognitionError(error);
break;
case 'onRecognitionComplete':
_handleRecognitionComplete();
break;
}
});
}
/// 处理识别结果
void _handleRecognitionResult(String result) {
Logger.debug('Recognition result: $result');
_recognitionResults.add(result);
_latestRecognizedText.value = result;
if (_eventStreamController != null) {
_eventStreamController!.add(RecognitionEvent(
type: RecognitionEventType.finalResult,
text: result,
));
}
}
/// 处理识别错误
void _handleRecognitionError(String error) {
Logger.error('Recognition error: $error');
_errorMessage.value = error;
isListening.value = false;
if (_eventStreamController != null) {
_eventStreamController!.add(RecognitionEvent(
type: RecognitionEventType.error,
text: '',
error: error,
));
}
}
/// 处理识别完成
void _handleRecognitionComplete() {
Logger.debug('Recognition complete');
isListening.value = false;
_isContinuousRecognitionActive = false;
if (_eventStreamController != null) {
_eventStreamController!.add(RecognitionEvent(
type: RecognitionEventType.completed,
text: '',
));
}
}
/// 初始化语音识别引擎
Future<bool> initialize() async {
if (_isInitialized) return true;
try {
Logger.info('开始初始化火山语音识别服务,APP_ID: ${_subscriptionKey.substring(0, math.min(3, _subscriptionKey.length))}***,APP_KEY: ${_serviceRegion.length > 10 ? "${_serviceRegion.substring(0, 5)}..." : _serviceRegion}');
final bool result = await _channel.invokeMethod('initialize', {
'subscriptionKey': _subscriptionKey,
'serviceRegion': _serviceRegion,
});
_isInitialized = result;
Logger.info('火山语音识别服务初始化${result ? '成功' : '失败'}');
return result;
} on PlatformException catch (e) {
// 检查是否是资源授权错误
if (e.code == 'INITIALIZATION_ERROR' &&
(e.message?.contains('资源授权错误') == true ||
e.message?.contains('requested resource not granted') == true ||
e.message?.contains('requested grant not found') == true)) {
Logger.error('火山语音识别初始化失败: 资源授权错误', e, StackTrace.current);
Logger.info('请检查以下几点:');
Logger.info('1. 确保您的火山引擎账户已开通语音识别服务');
Logger.info('2. 确保您的应用ID和密钥正确且有效');
Logger.info('3. 确保您的应用已被授权使用语音识别服务');
_isInitialized = false;
throw PlatformException(
code: 'RESOURCE_AUTHORIZATION_ERROR',
message: '语音识别服务授权失败: 请确保应用已开通语音识别服务并且密钥有效',
details: e.message
);
} else {
Logger.error('火山语音识别初始化失败: ${e.message}', e, StackTrace.current);
_isInitialized = false;
throw e;
}
} catch (e) {
Logger.error('火山语音识别初始化发生未知错误', e, StackTrace.current);
_isInitialized = false;
throw Exception('初始化火山语音识别服务失败: $e');
}
}
/// 开始一次性识别
Future<bool> startOneTimeRecognition() async {
if (isListening.value) {
Logger.warning('已经在进行语音识别,请先停止当前识别');
return false;
}
if (!_isInitialized) {
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('startOneTimeRecognition');
isListening.value = result;
if (result) {
_eventStreamController?.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) {
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();
// 创建事件流控制器
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
_recognitionStream = _eventStreamController?.stream;
// 设置事件监听
_eventSubscription = _eventChannel
.receiveBroadcastStream()
.listen(_handleNativeEvent, onError: (error) {
_handleRecognitionError(error.toString());
});
final bool result = await _channel.invokeMethod('startContinuousRecognition');
isListening.value = result;
_isContinuousRecognitionActive = result;
if (result) {
_eventStreamController?.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? ?? '';
_eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.recognizing,
text: text,
));
break;
case 'finalResult':
final String text = eventMap['text'] as String? ?? '';
_latestRecognizedText.value = text;
_recognitionResults.add(text);
_eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.finalResult,
text: text,
));
break;
case 'error':
final String error = eventMap['error'] as String? ?? '未知错误';
_handleRecognitionError(error);
break;
}
}
/// 清理事件流
void _cleanupEventStream() {
_eventSubscription?.cancel();
_eventSubscription = null;
_eventStreamController?.close();
_eventStreamController = null;
_recognitionStream = null;
}
/// 停止识别
Future<bool> stopRecognition() async {
if (!isListening.value) {
Logger.warning('当前没有进行语音识别');
return false;
}
try {
final bool result = await _channel.invokeMethod('stopRecognition');
isListening.value = !result;
_isContinuousRecognitionActive = !result;
if (result) {
_cleanupEventStream();
}
return result;
} catch (e) {
Logger.error('停止识别失败: $e');
_errorMessage.value = e.toString();
return false;
}
}
/// 检查连续识别是否活跃
bool isContinuousRecognitionActive() {
return _isContinuousRecognitionActive;
}
/// 清理资源
Future<void> dispose() async {
try {
if (isListening.value) {
await stopRecognition();
}
_cleanupEventStream();
} catch (e) {
Logger.error('清理语音识别资源失败: $e');
}
}
@override
void onClose() {
dispose();
super.onClose();
}
}