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.
391 lines
15 KiB
391 lines
15 KiB
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import 'dart:developer' as developer;
|
|
|
|
/// 火山语音识别服务
|
|
/// 负责与原生层的火山语音识别引擎交互
|
|
class VoiceRecognitionService extends GetxService {
|
|
// 方法通道,用于调用原生方法
|
|
static const MethodChannel _methodChannel =
|
|
MethodChannel('com.example.deep_voice/speech_recognition');
|
|
|
|
// 事件通道,用于接收原生层的事件
|
|
static const EventChannel _eventChannel =
|
|
EventChannel('com.example.deep_voice/speech_recognition_events');
|
|
|
|
// 状态变量
|
|
final isInitialized = false.obs;
|
|
final isListening = false.obs;
|
|
final isConnecting = true.obs;
|
|
|
|
// 识别结果
|
|
final recognizedText = ''.obs;
|
|
|
|
// 回调函数
|
|
Function(String)? onRecognitionResult;
|
|
Function(String)? onError;
|
|
Function()? onRecognitionComplete;
|
|
Function(String)? onConnectionStatusChanged;
|
|
Function(String)? onSentenceComplete;
|
|
|
|
// 事件流订阅
|
|
StreamSubscription? _eventSubscription;
|
|
|
|
// 火山语音识别的 AppKey 和 AccessKey
|
|
late final String _appKey;
|
|
late final String _accessKey;
|
|
late final String _cluster;
|
|
|
|
@override
|
|
void onInit() {
|
|
developer.log('VoiceRecognitionService: 初始化服务', name: 'VoiceRecognition');
|
|
super.onInit();
|
|
_loadCredentials();
|
|
_setupEventListener();
|
|
}
|
|
|
|
/// 加载火山语音识别的凭证
|
|
void _loadCredentials() {
|
|
try {
|
|
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? '';
|
|
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? '';
|
|
_cluster = dotenv.env['VOLC_SPEECH_CLUSTER'] ?? 'volcano_asr';
|
|
|
|
developer.log('VoiceRecognitionService: 加载凭证 - AppKey: ${_appKey.isNotEmpty ? '已设置' : '未设置'}, AccessKey: ${_accessKey.isNotEmpty ? '已设置' : '未设置'}, Cluster: $_cluster', name: 'VoiceRecognition');
|
|
|
|
if (_appKey.isEmpty || _accessKey.isEmpty) {
|
|
developer.log('VoiceRecognitionService: 警告 - 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY', name: 'VoiceRecognition');
|
|
}
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 加载火山语音识别凭证失败: $e', name: 'VoiceRecognition');
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 设置事件监听器
|
|
void _setupEventListener() {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 设置事件监听器', name: 'VoiceRecognition');
|
|
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
|
|
(dynamic event) {
|
|
_handleEvent(event.toString());
|
|
},
|
|
onError: (dynamic error) {
|
|
developer.log('VoiceRecognitionService: 事件通道错误: $error', name: 'VoiceRecognition');
|
|
if (onError != null) {
|
|
onError!('事件通道错误: $error');
|
|
}
|
|
},
|
|
);
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 设置事件监听器失败: $e', name: 'VoiceRecognition');
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 处理来自原生层的事件
|
|
void _handleEvent(String eventJson) {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 收到原始事件数据: $eventJson', name: 'VoiceRecognition');
|
|
|
|
final eventData = jsonDecode(eventJson);
|
|
final eventName = eventData['event'];
|
|
final data = eventData['data'];
|
|
|
|
developer.log('VoiceRecognitionService: 收到事件: $eventName, 数据: $data', name: 'VoiceRecognition');
|
|
|
|
switch (eventName) {
|
|
case 'status':
|
|
_handleStatusEvent(data.toString());
|
|
break;
|
|
case 'result':
|
|
_handleResultEvent(data.toString());
|
|
break;
|
|
case 'error':
|
|
_handleErrorEvent(data.toString());
|
|
break;
|
|
case 'vad':
|
|
_handleVadEvent(data.toString());
|
|
break;
|
|
default:
|
|
developer.log('VoiceRecognitionService: 未知事件类型: $eventName', name: 'VoiceRecognition');
|
|
break;
|
|
}
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 处理事件失败: $e', name: 'VoiceRecognition');
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 处理状态事件
|
|
void _handleStatusEvent(String status) {
|
|
developer.log('VoiceRecognitionService: 状态变更: $status', name: 'VoiceRecognition');
|
|
|
|
switch (status) {
|
|
case 'initialized':
|
|
isInitialized.value = true;
|
|
isConnecting.value = false;
|
|
developer.log('VoiceRecognitionService: 引擎初始化完成', name: 'VoiceRecognition');
|
|
break;
|
|
case 'listening':
|
|
isListening.value = true;
|
|
developer.log('VoiceRecognitionService: 开始监听', name: 'VoiceRecognition');
|
|
break;
|
|
case 'idle':
|
|
isListening.value = false;
|
|
developer.log('VoiceRecognitionService: 停止监听', name: 'VoiceRecognition');
|
|
break;
|
|
}
|
|
|
|
if (onConnectionStatusChanged != null) {
|
|
onConnectionStatusChanged!(status);
|
|
}
|
|
}
|
|
|
|
/// 处理识别结果事件
|
|
void _handleResultEvent(String resultJson) {
|
|
try {
|
|
final resultData = jsonDecode(resultJson);
|
|
final text = resultData['text'];
|
|
final isFinal = resultData['isFinal'];
|
|
|
|
developer.log('VoiceRecognitionService: 识别结果: $text, 是否最终结果: $isFinal', name: 'VoiceRecognition');
|
|
|
|
// 更新识别文本
|
|
recognizedText.value = text;
|
|
|
|
// 调用回调函数
|
|
if (onRecognitionResult != null) {
|
|
onRecognitionResult!(text);
|
|
}
|
|
|
|
// 如果是最终结果,调用完成回调
|
|
if (isFinal && text.isNotEmpty) {
|
|
developer.log('VoiceRecognitionService: 句子识别完成: $text', name: 'VoiceRecognition');
|
|
|
|
if (onSentenceComplete != null) {
|
|
onSentenceComplete!(text);
|
|
}
|
|
|
|
if (onRecognitionComplete != null) {
|
|
onRecognitionComplete!();
|
|
}
|
|
}
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 处理识别结果失败: $e', name: 'VoiceRecognition');
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 处理错误事件
|
|
void _handleErrorEvent(String errorJson) {
|
|
try {
|
|
final errorData = jsonDecode(errorJson);
|
|
final errorMessage = errorData['message'];
|
|
final errorCode = errorData['code'] ?? 'unknown';
|
|
|
|
developer.log('VoiceRecognitionService: 语音识别错误: 代码=$errorCode, 消息=$errorMessage', name: 'VoiceRecognition', error: errorMessage);
|
|
|
|
if (onError != null) {
|
|
onError!(errorMessage);
|
|
}
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 处理错误事件失败: $e', name: 'VoiceRecognition');
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 处理 VAD 事件
|
|
void _handleVadEvent(String vadState) {
|
|
// 可以根据需要处理 VAD 状态变化
|
|
developer.log('VoiceRecognitionService: VAD 状态: $vadState', name: 'VoiceRecognition');
|
|
}
|
|
|
|
/// 检查引擎能力和支持的指令
|
|
Future<void> checkEngineCapabilities() async {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 检查引擎能力', name: 'VoiceRecognition');
|
|
|
|
// 检查是否已初始化
|
|
if (!isInitialized.value) {
|
|
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
|
|
await initialize();
|
|
}
|
|
|
|
// 调用原生方法检查引擎能力
|
|
final result = await _methodChannel.invokeMethod<String>('checkEngineCapabilities');
|
|
developer.log('VoiceRecognitionService: 引擎能力检查结果: $result', name: 'VoiceRecognition');
|
|
|
|
if (result != null) {
|
|
try {
|
|
final capabilities = jsonDecode(result);
|
|
developer.log('VoiceRecognitionService: 引擎版本: ${capabilities['version']}', name: 'VoiceRecognition');
|
|
|
|
if (capabilities['supported_params'] != null) {
|
|
developer.log('VoiceRecognitionService: 支持的参数: ${capabilities['supported_params']}', name: 'VoiceRecognition');
|
|
}
|
|
|
|
if (capabilities['supported_directives'] != null) {
|
|
developer.log('VoiceRecognitionService: 支持的指令: ${capabilities['supported_directives']}', name: 'VoiceRecognition');
|
|
}
|
|
} catch (e) {
|
|
developer.log('VoiceRecognitionService: 解析能力结果失败: $e', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 检查引擎能力失败: $e', name: 'VoiceRecognition', error: e);
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
}
|
|
}
|
|
|
|
/// 初始化语音识别引擎
|
|
Future<void> initialize() async {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 开始初始化语音引擎', name: 'VoiceRecognition');
|
|
isConnecting.value = true;
|
|
|
|
// 检查凭证
|
|
if (_appKey.isEmpty || _accessKey.isEmpty) {
|
|
developer.log('VoiceRecognitionService: 凭证未设置,无法初始化', name: 'VoiceRecognition', error: '火山语音识别凭证未设置');
|
|
throw '火山语音识别凭证未设置';
|
|
}
|
|
|
|
// 添加详细日志
|
|
developer.log('VoiceRecognitionService: 使用凭证 AppKey: $_appKey, AccessKey: ${_accessKey.substring(0, 4)}***, Cluster: $_cluster', name: 'VoiceRecognition');
|
|
|
|
// 调用原生方法初始化引擎
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<String>(
|
|
'initialize',
|
|
{
|
|
'appKey': _appKey,
|
|
'accessKey': _accessKey,
|
|
'cluster': _cluster,
|
|
},
|
|
);
|
|
|
|
developer.log('VoiceRecognitionService: 初始化结果: $result', name: 'VoiceRecognition');
|
|
|
|
// 初始化成功后,检查引擎能力
|
|
await checkEngineCapabilities();
|
|
} catch (e) {
|
|
developer.log('VoiceRecognitionService: 初始化方法调用失败: $e', name: 'VoiceRecognition', error: e);
|
|
|
|
// 尝试检查引擎是否已初始化
|
|
try {
|
|
final isEngineInitialized = await _methodChannel.invokeMethod<bool>('isInitialized') ?? false;
|
|
if (isEngineInitialized) {
|
|
developer.log('VoiceRecognitionService: 引擎已经初始化,忽略错误', name: 'VoiceRecognition');
|
|
isInitialized.value = true;
|
|
isConnecting.value = false;
|
|
|
|
// 引擎已初始化,检查引擎能力
|
|
await checkEngineCapabilities();
|
|
return;
|
|
}
|
|
} catch (checkError) {
|
|
developer.log('VoiceRecognitionService: 检查引擎初始化状态失败: $checkError', name: 'VoiceRecognition');
|
|
}
|
|
|
|
// 如果检查失败或引擎未初始化,则重新抛出异常
|
|
rethrow;
|
|
}
|
|
|
|
// 初始化成功后,状态会通过事件通道更新
|
|
} catch (e, stack) {
|
|
isConnecting.value = false;
|
|
developer.log('VoiceRecognitionService: 初始化语音识别引擎失败: $e', name: 'VoiceRecognition', error: e);
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
|
|
String errorMessage = '初始化失败: $e';
|
|
|
|
// 提供更具体的错误信息
|
|
if (e.toString().contains('error code: -7')) {
|
|
errorMessage = '初始化失败: 无法创建日志目录,请检查应用权限';
|
|
developer.log('VoiceRecognitionService: 错误代码 -7: 无法创建日志目录', name: 'VoiceRecognition');
|
|
} else if (e.toString().contains('INIT_ERROR')) {
|
|
errorMessage = '初始化失败: 引擎初始化错误,请检查网络连接和凭证';
|
|
developer.log('VoiceRecognitionService: INIT_ERROR: 引擎初始化错误', name: 'VoiceRecognition');
|
|
}
|
|
|
|
if (onError != null) {
|
|
onError!(errorMessage);
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 开始语音识别
|
|
Future<void> startRecognition() async {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 开始语音识别', name: 'VoiceRecognition');
|
|
|
|
// 检查是否已初始化
|
|
if (!isInitialized.value) {
|
|
developer.log('VoiceRecognitionService: 引擎未初始化,尝试初始化', name: 'VoiceRecognition');
|
|
await initialize();
|
|
}
|
|
|
|
developer.log('VoiceRecognitionService: 调用原生方法 startRecognition', name: 'VoiceRecognition');
|
|
|
|
// 调用原生方法开始识别
|
|
final result = await _methodChannel.invokeMethod<String>('startRecognition');
|
|
developer.log('VoiceRecognitionService: 开始识别结果: $result', name: 'VoiceRecognition');
|
|
|
|
// 状态会通过事件通道更新
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 开始语音识别失败: $e', name: 'VoiceRecognition', error: e);
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
|
|
if (onError != null) {
|
|
onError!('开始识别失败: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 停止语音识别
|
|
Future<void> stopRecognition() async {
|
|
try {
|
|
developer.log('VoiceRecognitionService: 停止语音识别', name: 'VoiceRecognition');
|
|
|
|
// 检查是否已初始化
|
|
if (!isInitialized.value) {
|
|
developer.log('VoiceRecognitionService: 引擎未初始化,无法停止识别', name: 'VoiceRecognition');
|
|
return;
|
|
}
|
|
|
|
developer.log('VoiceRecognitionService: 调用原生方法 stopRecognition', name: 'VoiceRecognition');
|
|
|
|
// 调用原生方法停止识别
|
|
final result = await _methodChannel.invokeMethod<String>('stopRecognition');
|
|
developer.log('VoiceRecognitionService: 停止识别结果: $result', name: 'VoiceRecognition');
|
|
|
|
// 状态会通过事件通道更新
|
|
} catch (e, stack) {
|
|
developer.log('VoiceRecognitionService: 停止语音识别失败: $e', name: 'VoiceRecognition', error: e);
|
|
developer.log('VoiceRecognitionService: 错误堆栈: $stack', name: 'VoiceRecognition');
|
|
|
|
if (onError != null) {
|
|
onError!('停止识别失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
developer.log('VoiceRecognitionService: 关闭服务', name: 'VoiceRecognition');
|
|
|
|
// 取消事件订阅
|
|
_eventSubscription?.cancel();
|
|
|
|
// 停止识别
|
|
stopRecognition();
|
|
|
|
super.onClose();
|
|
}
|
|
}
|