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.
334 lines
10 KiB
334 lines
10 KiB
import 'dart:async';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:get/get.dart';
|
|
import '../../core/utils/logger.dart';
|
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|
import '../../modules/chat/models/message_model.dart';
|
|
import 'chat_history_service.dart';
|
|
import 'speech_impl/azure_asr_service.dart';
|
|
import 'speech_impl/azure_tts_service.dart';
|
|
import 'asr_service.dart';
|
|
import 'tts_service.dart';
|
|
|
|
/// 语音交互服务
|
|
///
|
|
/// 该服务提供了与Android端的VoiceInteractionService.kt通信的接口,
|
|
/// 用于管理后台语音交互服务的生命周期和接收语音交互事件
|
|
class VoiceInteractionService extends GetxService {
|
|
static VoiceInteractionService get to => Get.find();
|
|
// 方法通道和事件通道
|
|
static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction');
|
|
static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events');
|
|
|
|
// 服务状态
|
|
final _isServiceRunning = false.obs;
|
|
bool get isServiceRunning => _isServiceRunning.value;
|
|
|
|
// 事件流控制器
|
|
StreamController<VoiceInteractionEvent>? _eventStreamController;
|
|
Stream<VoiceInteractionEvent>? _eventStream;
|
|
Stream<VoiceInteractionEvent>? get eventStream => _eventStream;
|
|
|
|
// 事件通道状态
|
|
bool _isEventChannelReady = false;
|
|
Completer<void>? _eventChannelReadyCompleter;
|
|
StreamSubscription? _eventSubscription;
|
|
|
|
// 配置信息
|
|
late String _azureSpeechKey;
|
|
late String _azureSpeechRegion;
|
|
late String _volcanoAiApiKey;
|
|
|
|
// 初始化状态标志
|
|
static bool _isInitialized = false;
|
|
|
|
/// 构造函数
|
|
VoiceInteractionService() {
|
|
|
|
}
|
|
|
|
/// 从环境变量加载配置
|
|
void _loadConfig() {
|
|
_azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? '';
|
|
_azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? '';
|
|
_volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? '';
|
|
|
|
if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) {
|
|
Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION');
|
|
}
|
|
|
|
if (_volcanoAiApiKey.isEmpty) {
|
|
Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY');
|
|
}
|
|
}
|
|
|
|
/// 设置事件通道
|
|
void _setupEventChannel() {
|
|
_eventChannelReadyCompleter = Completer<void>();
|
|
|
|
_eventSubscription = _eventChannel
|
|
.receiveBroadcastStream()
|
|
.listen((event) {
|
|
if (event is Map) {
|
|
final String eventType = event['type'] as String? ?? '';
|
|
|
|
// 处理通道准备好的事件
|
|
if (eventType == 'channelReady') {
|
|
_isEventChannelReady = true;
|
|
if (!_eventChannelReadyCompleter!.isCompleted) {
|
|
_eventChannelReadyCompleter!.complete();
|
|
}
|
|
return;
|
|
}
|
|
|
|
_handleVoiceInteractionEvent(event);
|
|
}
|
|
}, onError: (error) {
|
|
Logger.error('语音交互事件通道错误: $error');
|
|
});
|
|
}
|
|
|
|
/// 创建事件流
|
|
void _createEventStream() {
|
|
_eventStreamController = StreamController<VoiceInteractionEvent>.broadcast();
|
|
_eventStream = _eventStreamController?.stream;
|
|
}
|
|
|
|
/// 等待事件通道准备好
|
|
Future<bool> _waitForEventChannel({Duration timeout = const Duration(seconds: 5)}) async {
|
|
if (_isEventChannelReady) return true;
|
|
|
|
try {
|
|
await _eventChannelReadyCompleter!.future.timeout(timeout);
|
|
return true;
|
|
} on TimeoutException {
|
|
Logger.error('等待语音交互事件通道准备好超时');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 初始化服务
|
|
Future<VoiceInteractionService> initialize() async {
|
|
return this;
|
|
try {
|
|
if (_isInitialized) {
|
|
return this;
|
|
}
|
|
_isInitialized = true;
|
|
|
|
_loadConfig();
|
|
_setupEventChannel();
|
|
_createEventStream();
|
|
// 检查服务是否正在运行
|
|
await _checkServiceStatus();
|
|
|
|
// 等待事件通道准备好
|
|
await _waitForEventChannel();
|
|
|
|
Logger.info('语音交互服务初始化完成');
|
|
return this;
|
|
} catch (e) {
|
|
Logger.error('语音交互服务初始化失败: $e');
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/// 检查服务状态
|
|
Future<void> _checkServiceStatus() async {
|
|
try {
|
|
final bool isRunning = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false;
|
|
_isServiceRunning.value = isRunning;
|
|
Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
|
|
} catch (e) {
|
|
Logger.error('检查语音交互服务状态失败: $e');
|
|
_isServiceRunning.value = false;
|
|
}
|
|
}
|
|
|
|
/// 启动语音交互服务
|
|
Future<bool> startService() async {
|
|
return true;
|
|
if (_isServiceRunning.value) {
|
|
Logger.info('语音交互服务已经在运行');
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
final bool result = await _channel.invokeMethod('startVoiceInteractionService', {
|
|
'azure_speech_key': _azureSpeechKey,
|
|
'azure_speech_region': _azureSpeechRegion,
|
|
'volcano_ai_api_key': _volcanoAiApiKey,
|
|
}) ?? false;
|
|
|
|
if (result) {
|
|
_isServiceRunning.value = true;
|
|
Logger.info('语音交互服务启动成功');
|
|
} else {
|
|
Logger.error('语音交互服务启动失败');
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
Logger.error('启动语音交互服务失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 停止语音交互服务
|
|
Future<bool> stopService() async {
|
|
if (!_isServiceRunning.value) {
|
|
Logger.info('语音交互服务未运行');
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
final bool result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false;
|
|
|
|
if (result) {
|
|
_isServiceRunning.value = false;
|
|
Logger.info('语音交互服务停止成功');
|
|
} else {
|
|
Logger.error('语音交互服务停止失败');
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
Logger.error('停止语音交互服务失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 暂停语音交互(停止TTS和语音识别,但保持服务运行)
|
|
Future<bool> pauseVoiceInteraction() async {
|
|
return true;
|
|
if (!_isServiceRunning.value) {
|
|
Logger.info('语音交互服务未运行,无法暂停');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
final bool result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false;
|
|
|
|
if (result) {
|
|
Logger.info('语音交互暂停成功');
|
|
} else {
|
|
Logger.error('语音交互暂停失败');
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
Logger.error('暂停语音交互失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 处理来自原生端的语音交互事件
|
|
void _handleVoiceInteractionEvent(dynamic event) {
|
|
if (event is! Map || _eventStreamController == null) return;
|
|
|
|
final Map<dynamic, dynamic> eventMap = event;
|
|
final String eventType = eventMap['type'] as String? ?? '';
|
|
final int timestamp = eventMap['timestamp'] as int? ?? 0;
|
|
|
|
// 添加时间戳日志,帮助调试
|
|
Logger.info('收到原生端事件: $eventType, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
|
|
|
|
switch (eventType) {
|
|
case 'chatHistory':
|
|
final String agentId = eventMap['agentId'] as String? ?? '';
|
|
final String userMessage = eventMap['userMessage'] as String? ?? '';
|
|
final String assistantMessage = eventMap['assistantMessage'] as String? ?? '';
|
|
|
|
_eventStreamController?.add(ChatHistoryEvent(
|
|
agentId: agentId,
|
|
userMessage: userMessage,
|
|
assistantMessage: assistantMessage,
|
|
timestamp: timestamp,
|
|
));
|
|
|
|
// 保存聊天记录
|
|
_saveChatHistory(agentId, userMessage, assistantMessage, timestamp);
|
|
|
|
Logger.info('收到聊天历史事件: agentId=$agentId');
|
|
break;
|
|
|
|
case 'recognitionStarted':
|
|
_eventStreamController?.add(RecognitionStartedEvent(
|
|
timestamp: timestamp,
|
|
));
|
|
break;
|
|
|
|
default:
|
|
Logger.warning('收到未知类型的语音交互事件: $eventType');
|
|
break;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
// 清理资源
|
|
_eventSubscription?.cancel();
|
|
_eventStreamController?.close();
|
|
super.onClose();
|
|
}
|
|
|
|
/// 保存聊天记录
|
|
void _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) {
|
|
try {
|
|
// 获取ChatHistoryService实例
|
|
final chatHistoryService = Get.find<ChatHistoryService>();
|
|
|
|
// 创建用户消息和助手消息
|
|
final userMsg = Message(
|
|
role: 'user',
|
|
content: userMessage,
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp),
|
|
);
|
|
|
|
final assistantMsg = Message(
|
|
role: 'assistant',
|
|
content: assistantMessage,
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息
|
|
);
|
|
|
|
// 加载现有历史记录
|
|
final existingMessages = chatHistoryService.loadHistory(agentId);
|
|
|
|
// 添加新消息
|
|
existingMessages.addAll([userMsg, assistantMsg]);
|
|
|
|
// 保存更新后的历史记录
|
|
chatHistoryService.saveHistory(agentId, existingMessages);
|
|
} catch (e) {
|
|
Logger.error('保存聊天记录失败: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 语音交互事件基类
|
|
abstract class VoiceInteractionEvent {
|
|
final int timestamp;
|
|
|
|
VoiceInteractionEvent({required this.timestamp});
|
|
}
|
|
|
|
/// 聊天历史事件
|
|
class ChatHistoryEvent extends VoiceInteractionEvent {
|
|
final String agentId;
|
|
final String userMessage;
|
|
final String assistantMessage;
|
|
|
|
ChatHistoryEvent({
|
|
required this.agentId,
|
|
required this.userMessage,
|
|
required this.assistantMessage,
|
|
required int timestamp,
|
|
}) : super(timestamp: timestamp);
|
|
}
|
|
|
|
/// 语音识别开始事件
|
|
class RecognitionStartedEvent extends VoiceInteractionEvent {
|
|
RecognitionStartedEvent({
|
|
required int timestamp,
|
|
}) : super(timestamp: timestamp);
|
|
}
|