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? _eventStreamController; StreamSubscription? _eventSubscription; // 公开的事件流 Stream? _recognitionStream; Stream? get recognitionStream => _recognitionStream; // 最新的识别结果 final _latestRecognizedText = ''.obs; String get latestRecognizedText => _latestRecognizedText.value; // 识别状态 final isListening = false.obs; // 识别结果列表 final RxList _recognitionResults = [].obs; List 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 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 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 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.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 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 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 dispose() async { try { if (isListening.value) { await stopRecognition(); } _cleanupEventStream(); } catch (e) { Logger.error('清理语音识别资源失败: $e'); } } @override void onClose() { dispose(); super.onClose(); } }