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.
368 lines
11 KiB
368 lines
11 KiB
import 'dart:async';
|
|
import 'package:audio_service/audio_service.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'background_agent_service.dart';
|
|
import '../../data/providers/agent_provider.dart';
|
|
import '../../core/utils/logger.dart';
|
|
|
|
/// 自定义 AudioHandler,用于捕获媒体按键事件(例如蓝牙耳机按钮)
|
|
class MyAudioHandler extends BaseAudioHandler {
|
|
// 定义一个 StreamController 用于向 UI 发送自定义事件
|
|
final StreamController<dynamic> _customEventController =
|
|
StreamController.broadcast();
|
|
bool _isDisposed = false;
|
|
BackgroundAgentService? _backgroundAgent;
|
|
|
|
// 添加一个标志,表示是否正在处理交互
|
|
bool _isHandlingInteraction = false;
|
|
|
|
// 添加一个计时器,用于定期更新播放状态
|
|
Timer? _statusUpdateTimer;
|
|
|
|
// 添加一个变量,记录最后一次媒体按钮事件的时间
|
|
DateTime? _lastMediaButtonEventTime;
|
|
|
|
Stream<dynamic> get customEventStream => _customEventController.stream;
|
|
|
|
MyAudioHandler() {
|
|
_initializeHandler();
|
|
_initializeBackgroundAgent();
|
|
_startStatusUpdateTimer();
|
|
Logger.info('MyAudioHandler 已创建,准备接收媒体按钮事件');
|
|
}
|
|
|
|
void _initializeBackgroundAgent() {
|
|
try {
|
|
if (Get.isRegistered<BackgroundAgentService>()) {
|
|
_backgroundAgent = Get.find<BackgroundAgentService>();
|
|
Logger.info('BackgroundAgent 初始化成功');
|
|
} else {
|
|
_backgroundAgent = Get.put(BackgroundAgentService(), permanent: true);
|
|
Logger.info('BackgroundAgent 已创建并初始化成功');
|
|
}
|
|
} catch (e) {
|
|
Logger.error('初始化 BackgroundAgent 失败', e);
|
|
_backgroundAgent = null;
|
|
}
|
|
}
|
|
|
|
void _initializeHandler() {
|
|
try {
|
|
// 设置初始播放状态
|
|
playbackState.add(PlaybackState(
|
|
controls: [
|
|
MediaControl.play,
|
|
MediaControl.pause,
|
|
MediaControl.stop,
|
|
MediaControl.skipToPrevious,
|
|
MediaControl.skipToNext,
|
|
],
|
|
systemActions: {
|
|
MediaAction.play,
|
|
MediaAction.pause,
|
|
MediaAction.stop,
|
|
MediaAction.skipToPrevious,
|
|
MediaAction.skipToNext,
|
|
MediaAction.seek,
|
|
},
|
|
androidCompactActionIndices: const [0, 1, 2],
|
|
processingState: AudioProcessingState.idle,
|
|
playing: false,
|
|
));
|
|
|
|
// 设置媒体元数据
|
|
final item = MediaItem(
|
|
id: 'deep_voice_audio',
|
|
title: 'Deep Voice Audio',
|
|
album: 'Deep Voice',
|
|
artist: 'Deep Voice',
|
|
genre: 'Speech',
|
|
duration: const Duration(seconds: 1),
|
|
artUri: null, // 移除图片URI
|
|
playable: true,
|
|
displayTitle: 'Deep Voice Audio',
|
|
displaySubtitle: 'Voice Assistant',
|
|
displayDescription: 'TTS Audio',
|
|
extras: const {'source': 'deep_voice'},
|
|
);
|
|
|
|
mediaItem.add(item);
|
|
Logger.info('AudioHandler 媒体项已设置');
|
|
|
|
// 立即更新播放状态,确保通知显示
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
} catch (e) {
|
|
Logger.error('初始化 AudioHandler 失败', e);
|
|
}
|
|
}
|
|
|
|
// 启动定期更新播放状态的计时器
|
|
void _startStatusUpdateTimer() {
|
|
_statusUpdateTimer?.cancel();
|
|
_statusUpdateTimer = Timer.periodic(Duration(minutes: 5), (timer) {
|
|
try {
|
|
// 定期更新播放状态,确保服务保持活跃
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
Logger.info('定期更新播放状态成功');
|
|
} catch (e) {
|
|
Logger.error('定期更新播放状态失败', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 更新播放状态
|
|
void _updatePlaybackState({
|
|
required bool playing,
|
|
required AudioProcessingState processingState,
|
|
}) {
|
|
if (_isDisposed) return;
|
|
|
|
try {
|
|
playbackState.add(playbackState.value.copyWith(
|
|
playing: playing,
|
|
processingState: processingState,
|
|
updatePosition: Duration.zero,
|
|
));
|
|
|
|
|
|
} catch (e) {
|
|
Logger.error('更新播放状态失败', e);
|
|
}
|
|
}
|
|
|
|
Future<void> _handleInteraction() async {
|
|
// Logger.info('处理媒体按钮交互');
|
|
|
|
// 防止重复处理
|
|
if (_isHandlingInteraction) {
|
|
Logger.info('已经在处理交互,忽略此次请求');
|
|
return;
|
|
}
|
|
|
|
_isHandlingInteraction = true;
|
|
|
|
try {
|
|
// 更新播放状态,确保通知显示
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
|
|
// 直接获取 personal_assistant agent
|
|
final agent = AgentProvider.getAgentById('personal_assistant');
|
|
if (agent == null) {
|
|
Logger.error('无法获取personal_assistant agent,无法激活语音助手');
|
|
_isHandlingInteraction = false;
|
|
return;
|
|
}
|
|
|
|
// 检查app是否在前台
|
|
final isInForeground = Get.context != null &&
|
|
WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed;
|
|
|
|
if (isInForeground) {
|
|
// 应用在前台,导航到聊天页面
|
|
Logger.info('应用在前台,导航到聊天页面');
|
|
|
|
// 如果BackgroundAgent正在运行,先停止它
|
|
if (_backgroundAgent != null && _backgroundAgent!.isProcessing.value) {
|
|
await _backgroundAgent!.stopInteraction();
|
|
Logger.info('已停止后台代理服务,切换到前台模式');
|
|
}
|
|
|
|
// 导航到聊天页面,并标记需要在进入时播放语音应答
|
|
Get.toNamed('/chat', arguments: {
|
|
'agentId': agent.id,
|
|
'agentName': agent.name,
|
|
'agentVoice': agent.voice,
|
|
'agentAvatar': agent.avatarUrl,
|
|
'agentSubtitle': agent.description,
|
|
'systemPrompt': agent.systemPrompt,
|
|
'welcomeMessage': agent.welcomeMessage,
|
|
'playVoiceOnEnter': true,
|
|
});
|
|
} else {
|
|
// 应用在后台,使用 BackgroundAgent 处理交互
|
|
Logger.info('应用在后台,使用 BackgroundAgent 处理交互');
|
|
if (_backgroundAgent != null) {
|
|
|
|
if (_backgroundAgent!.isProcessing.value) {
|
|
// 如果后台服务已在处理,则中断当前响应并回复"我在听"
|
|
Logger.info('检测到媒体按钮再次按下,中断当前AI回复');
|
|
await _backgroundAgent!.respondToNewMediaEvent();
|
|
} else {
|
|
// 首次启动后台服务
|
|
Logger.info('首次启动后台服务');
|
|
await _backgroundAgent!.startAgentInteraction(agent.id);
|
|
}
|
|
} else {
|
|
Logger.error('BackgroundAgent 未初始化,无法处理交互');
|
|
// 尝试重新初始化 BackgroundAgent
|
|
_initializeBackgroundAgent();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('处理媒体按钮交互失败', e);
|
|
} finally {
|
|
_isHandlingInteraction = false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> play() async {
|
|
Logger.info('收到播放命令');
|
|
try {
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
await _handleInteraction();
|
|
} catch (e) {
|
|
Logger.error('处理播放命令失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> pause() async {
|
|
Logger.info('收到暂停命令');
|
|
try {
|
|
_updatePlaybackState(
|
|
playing: false,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
await _handleInteraction();
|
|
} catch (e) {
|
|
Logger.error('处理暂停命令失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> stop() async {
|
|
Logger.info('停止 AudioHandler');
|
|
_isDisposed = true;
|
|
try {
|
|
_statusUpdateTimer?.cancel();
|
|
|
|
// 如果有正在运行的后台服务,停止它
|
|
if (_backgroundAgent != null && _backgroundAgent!.isProcessing.value) {
|
|
await _backgroundAgent!.stopInteraction();
|
|
}
|
|
|
|
await super.stop();
|
|
_customEventController.close();
|
|
_updatePlaybackState(
|
|
playing: false,
|
|
processingState: AudioProcessingState.idle,
|
|
);
|
|
} catch (e) {
|
|
Logger.error('停止 AudioHandler 时发生错误', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> skipToPrevious() async {
|
|
Logger.info('收到上一曲命令');
|
|
try {
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
await _handleInteraction();
|
|
} catch (e) {
|
|
Logger.error('处理上一曲命令失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> skipToNext() async {
|
|
Logger.info('收到下一曲命令');
|
|
try {
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
await _handleInteraction();
|
|
} catch (e) {
|
|
Logger.error('处理下一曲命令失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> seek(Duration position) async {
|
|
Logger.info('收到跳转命令: $position');
|
|
}
|
|
|
|
@override
|
|
Future<dynamic> customAction(String name,
|
|
[Map<String, dynamic>? extras]) async {
|
|
Logger.info('收到自定义命令: $name');
|
|
try {
|
|
if (name == 'media_button') {
|
|
Logger.info('收到媒体按钮事件');
|
|
|
|
// 检查是否是重复触发(防止短时间内多次触发)
|
|
final now = DateTime.now();
|
|
if (_lastMediaButtonEventTime != null) {
|
|
final difference = now.difference(_lastMediaButtonEventTime!);
|
|
if (difference.inMilliseconds < 500) {
|
|
Logger.info('媒体按钮事件触发过于频繁,忽略此次事件');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 更新最后一次事件时间
|
|
_lastMediaButtonEventTime = now;
|
|
|
|
_updatePlaybackState(
|
|
playing: true,
|
|
processingState: AudioProcessingState.ready,
|
|
);
|
|
await _handleInteraction();
|
|
} else if (name == 'ping') {
|
|
// 用于检查服务是否响应的测试命令
|
|
Logger.info('收到 ping 命令,服务正常运行');
|
|
return 'pong';
|
|
}
|
|
} catch (e) {
|
|
Logger.error('处理自定义命令失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> onTaskRemoved() async {
|
|
Logger.info('服务被系统移除');
|
|
try {
|
|
// 如果有正在运行的后台服务,停止它
|
|
if (_backgroundAgent != null && _backgroundAgent!.isProcessing.value) {
|
|
await _backgroundAgent!.stopInteraction();
|
|
}
|
|
|
|
await stop();
|
|
} catch (e) {
|
|
Logger.error('处理服务移除失败', e);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> onNotificationDeleted() async {
|
|
Logger.info('通知被用户移除');
|
|
try {
|
|
// 如果有正在运行的后台服务,停止它
|
|
if (_backgroundAgent != null && _backgroundAgent!.isProcessing.value) {
|
|
await _backgroundAgent!.stopInteraction();
|
|
}
|
|
|
|
await stop();
|
|
} catch (e) {
|
|
Logger.error('处理通知移除失败', e);
|
|
}
|
|
}
|
|
}
|
|
|