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.
264 lines
6.8 KiB
264 lines
6.8 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';
|
|
|
|
/// 火山语音识别服务
|
|
/// 负责与原生层的火山语音识别引擎交互
|
|
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;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_loadCredentials();
|
|
_setupEventListener();
|
|
}
|
|
|
|
/// 加载火山语音识别的凭证
|
|
void _loadCredentials() {
|
|
try {
|
|
_appKey = dotenv.env['VOLC_SPEECH_APP_KEY'] ?? '';
|
|
_accessKey = dotenv.env['VOLC_SPEECH_ACCESS_KEY'] ?? '';
|
|
|
|
if (_appKey.isEmpty || _accessKey.isEmpty) {
|
|
debugPrint('警告: 火山语音识别凭证未设置,请在 .env 文件中设置 VOLC_SPEECH_APP_KEY 和 VOLC_SPEECH_ACCESS_KEY');
|
|
}
|
|
} catch (e) {
|
|
debugPrint('加载火山语音识别凭证失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 设置事件监听器
|
|
void _setupEventListener() {
|
|
try {
|
|
_eventSubscription = _eventChannel.receiveBroadcastStream().listen(
|
|
(dynamic event) {
|
|
_handleEvent(event.toString());
|
|
},
|
|
onError: (dynamic error) {
|
|
debugPrint('事件通道错误: $error');
|
|
if (onError != null) {
|
|
onError!('事件通道错误: $error');
|
|
}
|
|
},
|
|
);
|
|
} catch (e) {
|
|
debugPrint('设置事件监听器失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 处理来自原生层的事件
|
|
void _handleEvent(String eventJson) {
|
|
try {
|
|
final eventData = jsonDecode(eventJson);
|
|
final eventName = eventData['event'];
|
|
final data = eventData['data'];
|
|
|
|
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;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('处理事件失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 处理状态事件
|
|
void _handleStatusEvent(String status) {
|
|
switch (status) {
|
|
case 'initialized':
|
|
isInitialized.value = true;
|
|
isConnecting.value = false;
|
|
break;
|
|
case 'listening':
|
|
isListening.value = true;
|
|
break;
|
|
case 'idle':
|
|
isListening.value = false;
|
|
break;
|
|
}
|
|
|
|
if (onConnectionStatusChanged != null) {
|
|
onConnectionStatusChanged!(status);
|
|
}
|
|
}
|
|
|
|
/// 处理识别结果事件
|
|
void _handleResultEvent(String resultJson) {
|
|
try {
|
|
final resultData = jsonDecode(resultJson);
|
|
final text = resultData['text'];
|
|
final isFinal = resultData['isFinal'];
|
|
|
|
// 更新识别文本
|
|
recognizedText.value = text;
|
|
|
|
// 调用回调函数
|
|
if (onRecognitionResult != null) {
|
|
onRecognitionResult!(text);
|
|
}
|
|
|
|
// 如果是最终结果,调用完成回调
|
|
if (isFinal && text.isNotEmpty) {
|
|
if (onSentenceComplete != null) {
|
|
onSentenceComplete!(text);
|
|
}
|
|
|
|
if (onRecognitionComplete != null) {
|
|
onRecognitionComplete!();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint('处理识别结果失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 处理错误事件
|
|
void _handleErrorEvent(String errorJson) {
|
|
try {
|
|
final errorData = jsonDecode(errorJson);
|
|
final errorMessage = errorData['message'];
|
|
|
|
debugPrint('语音识别错误: $errorMessage');
|
|
|
|
if (onError != null) {
|
|
onError!(errorMessage);
|
|
}
|
|
} catch (e) {
|
|
debugPrint('处理错误事件失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 处理 VAD 事件
|
|
void _handleVadEvent(String vadState) {
|
|
// 可以根据需要处理 VAD 状态变化
|
|
debugPrint('VAD 状态: $vadState');
|
|
}
|
|
|
|
/// 初始化语音识别引擎
|
|
Future<void> initialize() async {
|
|
try {
|
|
isConnecting.value = true;
|
|
|
|
// 检查凭证
|
|
if (_appKey.isEmpty || _accessKey.isEmpty) {
|
|
throw '火山语音识别凭证未设置';
|
|
}
|
|
|
|
// 调用原生方法初始化引擎
|
|
final result = await _methodChannel.invokeMethod<String>(
|
|
'initialize',
|
|
{
|
|
'appKey': _appKey,
|
|
'accessKey': _accessKey,
|
|
},
|
|
);
|
|
|
|
debugPrint('初始化结果: $result');
|
|
|
|
// 初始化成功后,状态会通过事件通道更新
|
|
} catch (e) {
|
|
isConnecting.value = false;
|
|
debugPrint('初始化语音识别引擎失败: $e');
|
|
if (onError != null) {
|
|
onError!('初始化失败: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 开始语音识别
|
|
Future<void> startRecognition() async {
|
|
try {
|
|
// 检查是否已初始化
|
|
if (!isInitialized.value) {
|
|
await initialize();
|
|
}
|
|
|
|
// 调用原生方法开始识别
|
|
final result = await _methodChannel.invokeMethod<String>('startRecognition');
|
|
debugPrint('开始识别结果: $result');
|
|
|
|
// 状态会通过事件通道更新
|
|
} catch (e) {
|
|
debugPrint('开始语音识别失败: $e');
|
|
if (onError != null) {
|
|
onError!('开始识别失败: $e');
|
|
}
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// 停止语音识别
|
|
Future<void> stopRecognition() async {
|
|
try {
|
|
// 检查是否已初始化
|
|
if (!isInitialized.value) {
|
|
return;
|
|
}
|
|
|
|
// 调用原生方法停止识别
|
|
final result = await _methodChannel.invokeMethod<String>('stopRecognition');
|
|
debugPrint('停止识别结果: $result');
|
|
|
|
// 状态会通过事件通道更新
|
|
} catch (e) {
|
|
debugPrint('停止语音识别失败: $e');
|
|
if (onError != null) {
|
|
onError!('停止识别失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
// 取消事件订阅
|
|
_eventSubscription?.cancel();
|
|
|
|
// 停止识别
|
|
stopRecognition();
|
|
|
|
super.onClose();
|
|
}
|
|
}
|