From 63eb4625f9354d93a45b46175f9ae5b462e49bf9 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Thu, 3 Jul 2025 14:22:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9F=B3=E4=B9=90=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E5=99=A8=E5=8D=A1=E7=89=87=E4=B8=8D=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/bindings/initial_binding.dart | 6 +- lib/data/services/music_manager.dart | 2 + .../agent/controllers/agent_controller.dart | 534 +++++++++++++++++- lib/modules/agent/views/music_card.dart | 36 +- lib/modules/music/bindings/music_binding.dart | 15 - .../music/controllers/music_controller.dart | 493 ---------------- .../music/services/music_service_facade.dart | 125 ---- .../music/views/music_playlist_view.dart | 108 ++-- .../music_service/MusicService.kt | 129 +++-- .../music_service/MusicServicePlugin.kt | 8 +- .../music_service/lib/music_service.dart | 13 +- 11 files changed, 701 insertions(+), 768 deletions(-) delete mode 100644 lib/modules/music/bindings/music_binding.dart delete mode 100644 lib/modules/music/controllers/music_controller.dart delete mode 100644 lib/modules/music/services/music_service_facade.dart diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index 01564b26a..b916bc88d 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -10,8 +10,6 @@ import '../../data/services/bluetooth_manager.dart'; import '../../data/services/ble_manager.dart'; import '../../data/services/account_service.dart'; import '../../data/services/version_update_service.dart'; -import '../../modules/music/controllers/music_controller.dart'; -import '../../modules/music/services/music_service_facade.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @@ -37,10 +35,8 @@ class InitialBinding extends Bindings { Get.lazyPut(() => BleManager(), fenix: true); // MusicManager Get.lazyPut(() => MusicManager(), fenix: true); - + // 音乐模块 - Get.lazyPut(() => MusicController(), fenix: true); - Get.lazyPut(() => MusicServiceFacade(), fenix: true); Get.lazyPut(() => NavigationManager(), fenix: true); Get.lazyPut(() => LocationManager(), fenix: true); // 火山AI服务 diff --git a/lib/data/services/music_manager.dart b/lib/data/services/music_manager.dart index 9bfae0e7a..51975424f 100644 --- a/lib/data/services/music_manager.dart +++ b/lib/data/services/music_manager.dart @@ -105,6 +105,7 @@ class MusicManager extends GetxService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, + required Function(int, int, int, double) onStop1, }) async { try { await _musicService.setMediaSessionCallbacks( @@ -114,6 +115,7 @@ class MusicManager extends GetxService { onPrevious: onPrevious, onSeekTo: onSeekTo, onStop: onStop, + onStop1: onStop1, ); } catch (e) { Logger.d('MusicManager', '设置媒体会话回调失败: $e'); diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index d1ae83ce3..4fd37f1cd 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -25,23 +25,35 @@ import '../../../data/models/message_model.dart'; // 语音输入模式枚举 enum VoiceInputMode { - text, // 文字输入模式 - pushToTalk, // 按住说话模式 - phoneCall, // 打电话模式 + text, // 文字输入模式 + pushToTalk, // 按住说话模式 + phoneCall, // 打电话模式 } class AgentController extends GetxController { static AgentController get to => Get.find(); //单例访问模式 - // 聊天消息列表 final messages = [].obs; // UI控制器 final TextEditingController textController = TextEditingController(); final ScrollController scrollController = ScrollController(); + //音乐播放管理 + final _musiceManager = Get.find(); // 导航管理 final _navigationManager = Get.find(); + RxBool isPlaying = false.obs; + RxInt currentId = 0.obs; + + // 播放进度相关状态 + RxInt currentPosition = 0.obs; // 当前播放位置(毫秒) + RxInt duration = 0.obs; // 总时长(毫秒) + RxDouble progress = 0.0.obs; // 播放进度(0.0-1.0) + + // 当前播放列表管理 + final currentPlaylist = >[].obs; // 当前播放列表 + final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引 // 各种状态标志 final isInitialized = false.obs; @@ -98,6 +110,8 @@ class AgentController extends GetxController { // BLE事件订阅 StreamSubscription? _bleEventSubscription; + // 进度更新定时器 + Timer? _progressTimer; // 用于跟踪AI回复状态 bool _isNewAssistantResponse = true; String _currentResponseId = ''; @@ -110,17 +124,54 @@ class AgentController extends GetxController { // 是否已加载聊天记录 final isHistoryLoaded = false.obs; + // 添加加载状态跟踪 + final RxBool isLoadingMusic = false.obs; + final RxString loadingMusicId = ''.obs; + + // 设置加载状态的方法 + void setLoadingMusic(String id, bool loading) { + loadingMusicId.value = loading ? id : ''; + isLoadingMusic.value = loading; + } + @override void onInit() { super.onInit(); _loadTtsPreference(); //加载TTS偏好设置 _loadChatHistory(); //加载聊天历史记录 _subscribeToEvents(); //订阅代理服务事件 - _subscribeToBleEvents(); //订阅BLE事件 + _initMusicStatus(); //初始化音乐状态 + _setupMediaSessionCallbacks(); //设置媒体会话回调 _setupTextInputListener(); //设置文本输入监听器 } + // 初始化音乐播放状态 + Future _initMusicStatus() async { + try { + await musicStatus(); + Logger.d('AgentController', '音乐状态初始化完成'); + } catch (e) { + Logger.e('AgentController', '音乐状态初始化失败: $e'); + } + } + // 设置媒体会话回调 + void _setupMediaSessionCallbacks() async { + try { + await _musiceManager.setMediaSessionCallbacks( + onPlay: onMediaSessionPlay, + onPause: onMediaSessionPause, + onNext: onMediaSessionNext, + onPrevious: onMediaSessionPrevious, + onSeekTo: onMediaSessionSeekTo, + onStop: onMediaSessionStop, + onStop1: onMediaSessionSeekTo1, + ); + Logger.d('AgentController', '媒体会话回调设置完成'); + } catch (e) { + Logger.e('AgentController', '媒体会话回调设置失败: $e'); + } + } @override void onClose() { @@ -994,7 +1045,8 @@ class AgentController extends GetxController { _pendingPlusOptionsExpansion = false; // 添加小延迟确保UI稳定 Future.delayed(const Duration(milliseconds: 50), () { - if (!isKeyboardVisible.value) { // 再次确认键盘状态 + if (!isKeyboardVisible.value) { + // 再次确认键盘状态 isPlusOptionsExpanded.value = true; } }); @@ -1194,7 +1246,8 @@ class AgentController extends GetxController { } // 检查文字模式与语音模式的一致性 - if (isTextInputMode.value && (isPushToTalkMode.value || isPhoneCallMode.value)) { + if (isTextInputMode.value && + (isPushToTalkMode.value || isPhoneCallMode.value)) { Logger.w(TAG, '状态冲突:文字模式与语音模式同时启用,优先保持语音模式'); isTextInputMode.value = false; } @@ -1268,12 +1321,298 @@ class AgentController extends GetxController { await stopVoiceInput(); } + // 获取音乐播放状态 + Future musicStatus() async { + try { + var status = await _musiceManager.getStatus(); + isPlaying.value = status['isPlaying'] ?? false; + currentId.value = status['id'] ?? 0; + Logger.d('AgentController', + '音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}'); + print("音乐状态更新: playing=${isPlaying.value}, url=${currentId.value}"); + } catch (e) { + Logger.e('AgentController', '获取音乐状态失败: $e'); + } + } + // 播放音乐 + Future musicPlay(Map song) async { + try { + Logger.d(TAG, '开始播放音乐: ${song['title'] ?? song['name']}'); + + // 立即更新UI状态,提供即时反馈 + isPlaying.value = true; + // 修复:确保currentId接收int类型 + final songId = song['id']; + currentId.value = songId is int + ? songId + : (int.tryParse(songId?.toString() ?? '0') ?? 0); + + // 重置进度状态 + currentPosition.value = 0; + duration.value = 0; + progress.value = 0.0; + + final id = song['id'] ?? ""; + final timestampMs = song['ts'] ?? 0; + + // 检查URL是否需要刷新(只有当有时间戳时才检查) + if (timestampMs > 0) { + final now = DateTime.now(); + final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( + const Duration(minutes: 10), // 转换为北京时间 + ); + + // 判断是否过期 + if (tsTime.isBefore(now)) { + Logger.d(TAG, '音乐URL已过期,正在获取新URL...'); + try { + var response = await Api.getMusicUrl({ + "ids": [id] + }); + print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 + final urldata = response['urls'][0] as Map; + song["url"] = urldata['url'] ?? ''; + // 修复:确保currentId接收int类型 + final newSongId = song['id']; + currentId.value = newSongId is int + ? newSongId + : (int.tryParse(newSongId?.toString() ?? '0') ?? 0); + Logger.d(TAG, '获取新URL成功'); + } catch (e) { + Logger.e(TAG, '获取新URL失败: $e'); + // 如果获取新URL失败,尝试使用原URL + } + } + } + + // 转换为MusicManager期望的格式 + final musicData = { + 'id': song['id']?.toString() ?? '', + 'url': song['url']?.toString() ?? '', + 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', + 'artist': + song['artist']?.toString() ?? song['sgener']?.toString() ?? '', + 'coverUrl': + song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', + }; + + // 调用音乐管理器播放 + await _musiceManager.play(musicData); + + // 更新媒体会话元数据和播放状态 + await _updateMediaSessionMetadata(song); + await _updateMediaSessionPlaybackState(true); + // 启动进度定时器 + _startProgressTimer(); + Logger.d(TAG, '音乐播放启动成功'); + } catch (e) { + Logger.e(TAG, '播放音乐失败: $e'); + // 发生错误时重置状态 + isPlaying.value = false; + currentId.value = 0; + } + } + // 暂停音乐 + Future musicPause() async { + try { + Logger.d('AgentController', '暂停音乐播放'); + await _musiceManager.pause(); + isPlaying.value = false; + _stopProgressTimer(); + // 更新媒体会话播放状态 + await _updateMediaSessionPlaybackState(false); + } catch (e) { + Logger.e('AgentController', '暂停音乐失败: $e'); + } + } + + // 恢复音乐播放 + Future musicResume() async { + try { + Logger.d('AgentController', '恢复播放音乐'); + await _musiceManager.resume(); + isPlaying.value = true; + _startProgressTimer(); + // 更新媒体会话播放状态 + await _updateMediaSessionPlaybackState(true); + + Logger.d('AgentController', '音乐恢复播放成功'); + } catch (e) { + Logger.e('AgentController', '恢复播放音乐失败: $e'); + isPlaying.value = false; + } + } + + // 启动进度更新定时器 + void _startProgressTimer() { + _stopProgressTimer(); // 先停止现有定时器 + + Logger.d('AgentController', '启动播放进度定时器'); + _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { + if (isPlaying.value && currentId.value != 0) { + updateMusicProgress(); + } else if (!isPlaying.value) { + // 如果不在播放状态,停止定时器 + timer.cancel(); + _progressTimer = null; + Logger.d('AgentController', '播放已停止,取消进度定时器'); + } + }); + } + + // 停止进度更新定时器 + void _stopProgressTimer() { + if (_progressTimer != null) { + Logger.d('AgentController', '停止播放进度定时器'); + _progressTimer?.cancel(); + _progressTimer = null; + } + } + + // 获取播放进度 + Future updateMusicProgress() async { + try { + if (!isPlaying.value || currentId.value == 0) { + return; // 如果没有在播放,直接返回 + } + + // var progressInfo = await _musiceManager.getProgress(); + // final newPosition = progressInfo['currentPosition'] ?? 0; + // final newDuration = progressInfo['duration'] ?? 0; + // final newProgress = progressInfo['progress'] ?? 0.0; + + // bool hasUpdate = false; + + // // 只有当值发生变化时才更新,避免不必要的UI刷新 + // if (currentPosition.value != newPosition) { + // currentPosition.value = newPosition; + // hasUpdate = true; + // } + // if (duration.value != newDuration) { + // duration.value = newDuration; + // hasUpdate = true; + // } + // if ((progress.value - newProgress).abs() > 0.001) { + // // 避免浮点数精度问题 + // progress.value = newProgress; + // hasUpdate = true; + // } + + // 如果有进度更新,通知系统媒体会话更新进度信息 + // if (hasUpdate) { + // await _updateMediaSessionProgress(); + // } + } catch (e) { + Logger.e('AgentController', '获取播放进度失败: $e'); + } + } + +// 获取播放进度 + Future updateMusicProgress1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + try { + if (!isPlaying.value || currentId.value == 0) { + return; // 如果没有在播放,直接返回 + } + + // // var progressInfo = await _musiceManager.getProgress(); + // final newPosition = progressInfo['currentPosition'] ?? 0; + // final newDuration = progressInfo['duration'] ?? 0; + // final newProgress = progressInfo['progress'] ?? 0.0; + print( + "updateMusicProgress1:currentIdInternal:$currentIdInternal newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); + bool hasUpdate = false; + if (currentId.value != currentIdInternal) { + currentId.value = currentIdInternal; + hasUpdate = true; + } + // 只有当值发生变化时才更新,避免不必要的UI刷新 + if (currentPosition.value != newPosition) { + currentPosition.value = newPosition; + hasUpdate = true; + } + if (duration.value != newDuration) { + duration.value = newDuration; + hasUpdate = true; + } + if ((progress.value - newProgress).abs() > 0.001) { + // 避免浮点数精度问题 + progress.value = newProgress; + hasUpdate = true; + } + + // // 如果有进度更新,通知系统媒体会话更新进度信息 + // if (hasUpdate) { + // await _updateMediaSessionProgress(); + // } + } catch (e) { + Logger.e('AgentController', '获取播放进度失败: $e'); + } + } + + /// 更新系统媒体会话的进度信息 + Future _updateMediaSessionProgress() async { + try { + // 这里将调用music_service插件的方法来更新系统媒体会话进度 + // 具体实现将在music_service插件中添加 + await _musiceManager.updateMediaSessionProgress( + currentPosition.value, + duration.value, + progress.value, + ); + } catch (e) { + // 忽略错误,避免影响正常播放 + Logger.d('AgentController', '更新媒体会话进度失败: $e'); + } + } + + /// 更新系统媒体会话的元数据 + Future _updateMediaSessionMetadata(Map song) async { + try { + await _musiceManager.updateMediaSessionMetadata(song); + } catch (e) { + Logger.d('AgentController', '更新媒体会话元数据失败: $e'); + } + } + + /// 更新系统媒体会话的播放状态 + Future _updateMediaSessionPlaybackState(bool isPlaying) async { + try { + await _musiceManager.updateMediaSessionPlaybackState(isPlaying); + } catch (e) { + Logger.d('AgentController', '更新媒体会话播放状态失败: $e'); + } + } + + // 跳转到指定位置 + Future seekToPosition(int positionMs) async { + try { + await _musiceManager.seekTo(positionMs); + // 立即更新进度状态 + // await updateMusicProgress(); + } catch (e) { + Logger.e('AgentController', '跳转播放位置失败: $e'); + } + } + + // 跳转到指定位置 + Future seekToPosition1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + try { + //await _musiceManager.seekTo(positionMs); + // 立即更新进度状态 + await updateMusicProgress1( + currentIdInternal, newPosition, newDuration, newProgress); + } catch (e) { + Logger.e('AgentController', '跳转播放位置失败: $e'); + } + } // 修改自动停止处理,遵循TTS设置 void _handleAutoStop(int index) { @@ -1313,7 +1652,188 @@ class AgentController extends GetxController { ); } + /// 播放音乐列表 + Future playPlaylist(List> playlist) async { + try { + if (playlist.isEmpty) { + Logger.e(TAG, '播放列表为空,无法播放'); + return; + } + + Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲'); + + // 保存当前播放列表 + currentPlaylist.assignAll(playlist); + currentPlaylistIndex.value = 0; + + // 设置加载状态 + isLoadingMusic.value = true; + loadingMusicId.value = playlist[0]['id']?.toString() ?? ''; + + // 重置播放状态 + isPlaying.value = true; + currentPosition.value = 0; + duration.value = 0; + progress.value = 0.0; + + // 格式化歌曲列表数据 + final formattedPlaylist = playlist.map((song) { + return { + 'id': song['id']?.toString() ?? '', + 'url': song['url']?.toString() ?? '', + 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', + 'artist': + song['artist']?.toString() ?? song['sgener']?.toString() ?? '', + 'coverUrl': + song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', + }; + }).toList(); + + // 调用音乐管理器播放列表 + await _musiceManager.playPlaylist(formattedPlaylist); + + // 更新当前播放歌曲ID + if (formattedPlaylist.isNotEmpty) { + currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0; + } + + // 启动进度定时器 + _startProgressTimer(); + + Logger.d(TAG, '音乐列表播放启动成功'); + } catch (e) { + Logger.e(TAG, '播放音乐列表失败: $e'); + isPlaying.value = false; + currentId.value = 0; + + // 显示错误提示 + Get.snackbar( + '播放失败', + '无法播放音乐列表,请稍后重试', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red[400], + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } finally { + // 清除加载状态 + isLoadingMusic.value = false; + loadingMusicId.value = ''; + } + } + + /// 播放上一曲 + Future playPrevious() async { + if (currentPlaylist.isEmpty) { + Logger.w(TAG, '当前播放列表为空,无法播放上一曲'); + return; + } + + // 计算上一曲的索引 + int previousIndex = currentPlaylistIndex.value - 1; + if (previousIndex < 0) { + previousIndex = currentPlaylist.length - 1; // 循环到最后一首 + } + + currentPlaylistIndex.value = previousIndex; + final previousSong = currentPlaylist[previousIndex]; + + Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}'); + await musicPlay(previousSong); + } + + /// 播放下一曲 + Future playNext() async { + if (currentPlaylist.isEmpty) { + Logger.w(TAG, '当前播放列表为空,无法播放下一曲'); + return; + } + + // 计算下一曲的索引 + int nextIndex = currentPlaylistIndex.value + 1; + if (nextIndex >= currentPlaylist.length) { + nextIndex = 0; // 循环到第一首 + } + + currentPlaylistIndex.value = nextIndex; + final nextSong = currentPlaylist[nextIndex]; + + Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}'); + await musicPlay(nextSong); + } + + /// 更新当前播放列表和索引(当单独播放某首歌曲时调用) + void updateCurrentPlaylistContext( + List> playlist, int songId) { + currentPlaylist.assignAll(playlist); + + // 查找当前歌曲在列表中的索引 + int index = -1; + for (int i = 0; i < playlist.length; i++) { + if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) { + index = i; + break; + } + } + + if (index != -1) { + currentPlaylistIndex.value = index; + Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index'); + } + } + + // ========== 媒体会话回调处理方法 ========== + + /// 处理来自系统通知栏的播放操作 + Future onMediaSessionPlay() async { + Logger.d(TAG, '收到媒体会话播放回调'); + if (!isPlaying.value) { + await musicResume(); + } + } + + /// 处理来自系统通知栏的暂停操作 + Future onMediaSessionPause() async { + Logger.d(TAG, '收到媒体会话暂停回调'); + if (isPlaying.value) { + await musicPause(); + } + } + /// 处理来自系统通知栏的下一曲操作 + Future onMediaSessionNext() async { + Logger.d(TAG, '收到媒体会话下一曲回调'); + await playNext(); + } + /// 处理来自系统通知栏的上一曲操作 + Future onMediaSessionPrevious() async { + Logger.d(TAG, '收到媒体会话上一曲回调'); + await playPrevious(); + } + /// 处理来自系统通知栏的跳转操作 + Future onMediaSessionSeekTo(int positionMs) async { + Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms'); + await seekToPosition(positionMs); + } + + /// 处理来自系统通知栏的停止操作 + Future onMediaSessionStop() async { + Logger.d(TAG, '收到媒体会话停止回调'); + await musicPause(); + // 重置播放状态 + currentPosition.value = 0; + progress.value = 0.0; + _stopProgressTimer(); + } + + /// 处理来自系统通知栏的跳转操作 + Future onMediaSessionSeekTo1(int currentIdInternal, int newPosition, + int newDuration, double newProgress) async { + print( + "onMediaSessionSeekTo1 newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress"); + await seekToPosition1( + currentIdInternal, newPosition, newDuration, newProgress); + } } diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart index f64564c1d..ecc1e0fe1 100644 --- a/lib/modules/agent/views/music_card.dart +++ b/lib/modules/agent/views/music_card.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../music/services/music_service_facade.dart'; +import '../../../routes/app_routes.dart'; +import '../controllers/agent_controller.dart'; import 'circular_progress_button.dart'; /// 构建音乐项目 Widget buildMusicItem(BuildContext context, int id, String name, String url, String singer, String image) { - final musicFacade = Get.find(); // 获取音乐服务门面 + final controller = Get.find(); // 获取控制器 final isDarkMode = Theme.of(context).brightness == Brightness.dark; - Map song = { 'id': id, 'url': url, @@ -16,7 +16,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, 'artist': singer, 'coverUrl': image, }; - // 音乐状态由MusicController自动管理,无需手动刷新 + + controller.musicStatus(); //刷新状态 return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( @@ -82,11 +83,11 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, ), Obx(() { final isThisPlaying = - musicFacade.isPlaying.value && musicFacade.currentId.value == id; + controller.isPlaying.value && controller.currentId.value == id; // 只有当前播放的歌曲才显示进度 final currentProgress = - isThisPlaying ? musicFacade.progress.value : 0.0; + isThisPlaying ? controller.progress.value : 0.0; return CircularProgressButton( isPlaying: isThisPlaying, @@ -97,21 +98,21 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, strokeWidth: 2.0, onPlayPause: () async { try { - if (musicFacade.isPlaying.value) { - if (musicFacade.currentId.value == id) { + if (controller.isPlaying.value) { + if (controller.currentId.value == id) { // 当前歌曲正在播放,暂停它 - await musicFacade.musicPause(); + await controller.musicPause(); } else { // 其他歌曲正在播放,播放这首歌 - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } else { - if (musicFacade.currentId.value == id) { + if (controller.currentId.value == id) { // 当前歌曲已暂停,恢复播放 - await musicFacade.musicResume(); + await controller.musicResume(); } else { // 播放新歌曲 - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } } catch (e) { @@ -122,8 +123,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, ? (seekProgress) { // 只有当前播放的歌曲才允许跳转 final targetPosition = - (musicFacade.duration.value * seekProgress).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * seekProgress).round(); + controller.seekToPosition(targetPosition); } : null, ); @@ -137,7 +138,8 @@ Widget buildMusicItem(BuildContext context, int id, String name, String url, Widget buildMusicContent( BuildContext context, List> playlist) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; - + final controller = Get.find(); // 获取控制器 + controller.updateCurrentPlaylistContext(playlist, controller.currentId.value); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -221,7 +223,7 @@ Widget buildMusicContent( child: TextButton( onPressed: () { // 跳转到完整歌单页面,传递完整的歌单数据 - Get.toNamed('/music/playlist', arguments: playlist); + Get.toNamed(Routes.musicPlaylist, arguments: playlist); }, style: TextButton.styleFrom( minimumSize: Size.zero, diff --git a/lib/modules/music/bindings/music_binding.dart b/lib/modules/music/bindings/music_binding.dart deleted file mode 100644 index 231934cfa..000000000 --- a/lib/modules/music/bindings/music_binding.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/music_controller.dart'; - -/// 音乐模块绑定 -/// 负责注册音乐相关的控制器和服务 -class MusicBinding extends Bindings { - @override - void dependencies() { - // 注册音乐控制器为单例 - Get.lazyPut( - () => MusicController(), - fenix: true, // 允许重新创建 - ); - } -} \ No newline at end of file diff --git a/lib/modules/music/controllers/music_controller.dart b/lib/modules/music/controllers/music_controller.dart deleted file mode 100644 index a092d69f7..000000000 --- a/lib/modules/music/controllers/music_controller.dart +++ /dev/null @@ -1,493 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../../../core/utils/logger.dart'; -import '../../../data/services/music_manager.dart'; -import '../../../data/services/network/api.dart'; - -/// 音乐播放控制器 -/// 负责管理音乐播放状态、播放列表和播放控制 -class MusicController extends GetxController { - static const String TAG = 'MusicController'; - - // 音乐播放管理器 - final _musicManager = Get.find(); - - // 播放状态 - final isPlaying = false.obs; - final currentId = 0.obs; - - // 播放进度相关状态 - final currentPosition = 0.obs; // 当前播放位置(毫秒) - final duration = 0.obs; // 总时长(毫秒) - final progress = 0.0.obs; // 播放进度(0.0-1.0) - - // 当前播放列表管理 - final currentPlaylist = >[].obs; // 当前播放列表 - final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引 - - // 加载状态跟踪 - final isLoadingMusic = false.obs; - final loadingMusicId = ''.obs; - - // 进度更新定时器 - Timer? _progressTimer; - - @override - void onInit() { - super.onInit(); - _initMusicStatus(); - _setupMediaSessionCallbacks(); - } - - @override - void onClose() { - _stopProgressTimer(); - super.onClose(); - } - - // 初始化音乐播放状态 - Future _initMusicStatus() async { - try { - await musicStatus(); - Logger.d(TAG, '音乐状态初始化完成'); - } catch (e) { - Logger.e(TAG, '音乐状态初始化失败: $e'); - } - } - - // 设置媒体会话回调 - void _setupMediaSessionCallbacks() async { - try { - await _musicManager.setMediaSessionCallbacks( - onPlay: onMediaSessionPlay, - onPause: onMediaSessionPause, - onNext: onMediaSessionNext, - onPrevious: onMediaSessionPrevious, - onSeekTo: onMediaSessionSeekTo, - onStop: onMediaSessionStop, - ); - Logger.d(TAG, '媒体会话回调设置完成'); - } catch (e) { - Logger.e(TAG, '媒体会话回调设置失败: $e'); - } - } - - // 设置加载状态的方法 - void setLoadingMusic(String id, bool loading) { - loadingMusicId.value = loading ? id : ''; - isLoadingMusic.value = loading; - } - - // 获取音乐播放状态 - Future musicStatus() async { - try { - var status = await _musicManager.getStatus(); - isPlaying.value = status['isPlaying'] ?? false; - currentId.value = status['id'] ?? 0; - Logger.d(TAG, '音乐状态更新: playing=${isPlaying.value}, id=${currentId.value}'); - } catch (e) { - Logger.e(TAG, '获取音乐状态失败: $e'); - } - } - - // 播放音乐 - Future musicPlay(Map song) async { - try { - Logger.d(TAG, '开始播放音乐: ${song['title'] ?? song['name']}'); - - // 立即更新UI状态,提供即时反馈 - isPlaying.value = true; - // 修复:确保currentId接收int类型 - final songId = song['id']; - currentId.value = songId is int - ? songId - : (int.tryParse(songId?.toString() ?? '0') ?? 0); - - // 重置进度状态 - currentPosition.value = 0; - duration.value = 0; - progress.value = 0.0; - - final id = song['id'] ?? ""; - final timestampMs = song['ts'] ?? 0; - - // 检查URL是否需要刷新(只有当有时间戳时才检查) - if (timestampMs > 0) { - final now = DateTime.now(); - final tsTime = DateTime.fromMillisecondsSinceEpoch(timestampMs).add( - const Duration(minutes: 10), // 转换为北京时间 - ); - - // 判断是否过期 - if (tsTime.isBefore(now)) { - Logger.d(TAG, '音乐URL已过期,正在获取新URL...'); - try { - var response = await Api.getMusicUrl({ - "ids": [id] - }); - print('getMusicUrl 响应数据: ${response.toString()}'); // 调试输出原始数据 - final urldata = response['urls'][0] as Map; - song["url"] = urldata['url'] ?? ''; - // 修复:确保currentId接收int类型 - final newSongId = song['id']; - currentId.value = newSongId is int - ? newSongId - : (int.tryParse(newSongId?.toString() ?? '0') ?? 0); - Logger.d(TAG, '获取新URL成功'); - } catch (e) { - Logger.e(TAG, '获取新URL失败: $e'); - // 如果获取新URL失败,尝试使用原URL - } - } - } - - // 转换为MusicManager期望的格式 - final musicData = { - 'id': song['id']?.toString() ?? '', - 'url': song['url']?.toString() ?? '', - 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', - 'artist': - song['artist']?.toString() ?? song['sgener']?.toString() ?? '', - 'coverUrl': - song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', - }; - - // 调用音乐管理器播放 - await _musicManager.play(musicData); - - // 更新媒体会话元数据和播放状态 - await _updateMediaSessionMetadata(song); - await _updateMediaSessionPlaybackState(true); - - // 启动进度定时器 - _startProgressTimer(); - - Logger.d(TAG, '音乐播放启动成功'); - } catch (e) { - Logger.e(TAG, '播放音乐失败: $e'); - // 发生错误时重置状态 - isPlaying.value = false; - currentId.value = 0; - } - } - - // 暂停音乐 - Future musicPause() async { - try { - Logger.d(TAG, '暂停音乐播放'); - await _musicManager.pause(); - isPlaying.value = false; - _stopProgressTimer(); - - // 更新媒体会话播放状态 - await _updateMediaSessionPlaybackState(false); - } catch (e) { - Logger.e(TAG, '暂停音乐失败: $e'); - } - } - - // 恢复音乐播放 - Future musicResume() async { - try { - Logger.d(TAG, '恢复播放音乐'); - await _musicManager.resume(); - isPlaying.value = true; - _startProgressTimer(); - - // 更新媒体会话播放状态 - await _updateMediaSessionPlaybackState(true); - - Logger.d(TAG, '音乐恢复播放成功'); - } catch (e) { - Logger.e(TAG, '恢复播放音乐失败: $e'); - isPlaying.value = false; - } - } - - // 启动进度更新定时器 - void _startProgressTimer() { - _stopProgressTimer(); // 先停止现有定时器 - - Logger.d(TAG, '启动播放进度定时器'); - _progressTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { - if (isPlaying.value && currentId.value != 0) { - updateMusicProgress(); - } else if (!isPlaying.value) { - // 如果不在播放状态,停止定时器 - timer.cancel(); - _progressTimer = null; - Logger.d(TAG, '播放已停止,取消进度定时器'); - } - }); - } - - // 停止进度更新定时器 - void _stopProgressTimer() { - if (_progressTimer != null) { - Logger.d(TAG, '停止播放进度定时器'); - _progressTimer?.cancel(); - _progressTimer = null; - } - } - - // 获取播放进度 - Future updateMusicProgress() async { - try { - if (!isPlaying.value || currentId.value == 0) { - return; // 如果没有在播放,直接返回 - } - - var progressInfo = await _musicManager.getProgress(); - final newPosition = progressInfo['currentPosition'] ?? 0; - final newDuration = progressInfo['duration'] ?? 0; - final newProgress = progressInfo['progress'] ?? 0.0; - - bool hasUpdate = false; - - // 只有当值发生变化时才更新,避免不必要的UI刷新 - if (currentPosition.value != newPosition) { - currentPosition.value = newPosition; - hasUpdate = true; - } - if (duration.value != newDuration) { - duration.value = newDuration; - hasUpdate = true; - } - if ((progress.value - newProgress).abs() > 0.001) { - // 避免浮点数精度问题 - progress.value = newProgress; - hasUpdate = true; - } - - // 如果有进度更新,通知系统媒体会话更新进度信息 - if (hasUpdate) { - await _updateMediaSessionProgress(); - } - } catch (e) { - Logger.e(TAG, '获取播放进度失败: $e'); - } - } - - // 跳转到指定位置 - Future seekToPosition(int positionMs) async { - try { - await _musicManager.seekTo(positionMs); - // 立即更新进度状态 - await updateMusicProgress(); - } catch (e) { - Logger.e(TAG, '跳转播放位置失败: $e'); - } - } - - /// 播放音乐列表 - Future playPlaylist(List> playlist) async { - try { - if (playlist.isEmpty) { - Logger.e(TAG, '播放列表为空,无法播放'); - return; - } - - Logger.d(TAG, '开始播放音乐列表,共${playlist.length}首歌曲'); - - // 保存当前播放列表 - currentPlaylist.assignAll(playlist); - currentPlaylistIndex.value = 0; - - // 设置加载状态 - isLoadingMusic.value = true; - loadingMusicId.value = playlist[0]['id']?.toString() ?? ''; - - // 重置播放状态 - isPlaying.value = true; - currentPosition.value = 0; - duration.value = 0; - progress.value = 0.0; - - // 格式化歌曲列表数据 - final formattedPlaylist = playlist.map((song) { - return { - 'id': song['id']?.toString() ?? '', - 'url': song['url']?.toString() ?? '', - 'title': song['title']?.toString() ?? song['name']?.toString() ?? '', - 'artist': - song['artist']?.toString() ?? song['sgener']?.toString() ?? '', - 'coverUrl': - song['coverUrl']?.toString() ?? song['image']?.toString() ?? '', - }; - }).toList(); - - // 调用音乐管理器播放列表 - await _musicManager.playPlaylist(formattedPlaylist); - - // 更新当前播放歌曲ID - if (formattedPlaylist.isNotEmpty) { - currentId.value = int.tryParse(formattedPlaylist[0]['id'] ?? '0') ?? 0; - } - - // 启动进度定时器 - _startProgressTimer(); - - Logger.d(TAG, '音乐列表播放启动成功'); - } catch (e) { - Logger.e(TAG, '播放音乐列表失败: $e'); - isPlaying.value = false; - currentId.value = 0; - - // 显示错误提示 - Get.snackbar( - '播放失败', - '无法播放音乐列表,请稍后重试', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[400], - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); - } finally { - // 清除加载状态 - isLoadingMusic.value = false; - loadingMusicId.value = ''; - } - } - - /// 播放上一曲 - Future playPrevious() async { - if (currentPlaylist.isEmpty) { - Logger.w(TAG, '当前播放列表为空,无法播放上一曲'); - return; - } - - // 计算上一曲的索引 - int previousIndex = currentPlaylistIndex.value - 1; - if (previousIndex < 0) { - previousIndex = currentPlaylist.length - 1; // 循环到最后一首 - } - - currentPlaylistIndex.value = previousIndex; - final previousSong = currentPlaylist[previousIndex]; - - Logger.d(TAG, '播放上一曲: ${previousSong['name'] ?? previousSong['title']}'); - await musicPlay(previousSong); - } - - /// 播放下一曲 - Future playNext() async { - if (currentPlaylist.isEmpty) { - Logger.w(TAG, '当前播放列表为空,无法播放下一曲'); - return; - } - - // 计算下一曲的索引 - int nextIndex = currentPlaylistIndex.value + 1; - if (nextIndex >= currentPlaylist.length) { - nextIndex = 0; // 循环到第一首 - } - - currentPlaylistIndex.value = nextIndex; - final nextSong = currentPlaylist[nextIndex]; - - Logger.d(TAG, '播放下一曲: ${nextSong['name'] ?? nextSong['title']}'); - await musicPlay(nextSong); - } - - /// 更新当前播放列表和索引(当单独播放某首歌曲时调用) - void updateCurrentPlaylistContext( - List> playlist, int songId) { - currentPlaylist.assignAll(playlist); - - // 查找当前歌曲在列表中的索引 - int index = -1; - for (int i = 0; i < playlist.length; i++) { - if (int.tryParse(playlist[i]['id']?.toString() ?? '0') == songId) { - index = i; - break; - } - } - - if (index != -1) { - currentPlaylistIndex.value = index; - Logger.d(TAG, '更新播放列表上下文,当前歌曲索引: $index'); - } - } - - // ========== 媒体会话回调处理方法 ========== - - /// 处理来自系统通知栏的播放操作 - Future onMediaSessionPlay() async { - Logger.d(TAG, '收到媒体会话播放回调'); - if (!isPlaying.value) { - await musicResume(); - } - } - - /// 处理来自系统通知栏的暂停操作 - Future onMediaSessionPause() async { - Logger.d(TAG, '收到媒体会话暂停回调'); - if (isPlaying.value) { - await musicPause(); - } - } - - /// 处理来自系统通知栏的下一曲操作 - Future onMediaSessionNext() async { - Logger.d(TAG, '收到媒体会话下一曲回调'); - await playNext(); - } - - /// 处理来自系统通知栏的上一曲操作 - Future onMediaSessionPrevious() async { - Logger.d(TAG, '收到媒体会话上一曲回调'); - await playPrevious(); - } - - /// 处理来自系统通知栏的跳转操作 - Future onMediaSessionSeekTo(int positionMs) async { - Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms'); - await seekToPosition(positionMs); - } - - /// 处理来自系统通知栏的停止操作 - Future onMediaSessionStop() async { - Logger.d(TAG, '收到媒体会话停止回调'); - await musicPause(); - // 重置播放状态 - currentPosition.value = 0; - progress.value = 0.0; - _stopProgressTimer(); - } - - // ========== 私有辅助方法 ========== - - /// 更新系统媒体会话的进度信息 - Future _updateMediaSessionProgress() async { - try { - // 具体实现将在music_service插件中添加 - await _musicManager.updateMediaSessionProgress( - currentPosition.value, - duration.value, - progress.value, - ); - } catch (e) { - // 忽略错误,避免影响正常播放 - Logger.d(TAG, '更新媒体会话进度失败: $e'); - } - } - - /// 更新系统媒体会话的元数据 - Future _updateMediaSessionMetadata(Map song) async { - try { - await _musicManager.updateMediaSessionMetadata(song); - } catch (e) { - Logger.d(TAG, '更新媒体会话元数据失败: $e'); - } - } - - /// 更新系统媒体会话的播放状态 - Future _updateMediaSessionPlaybackState(bool isPlaying) async { - try { - await _musicManager.updateMediaSessionPlaybackState(isPlaying); - } catch (e) { - Logger.d(TAG, '更新媒体会话播放状态失败: $e'); - } - } -} \ No newline at end of file diff --git a/lib/modules/music/services/music_service_facade.dart b/lib/modules/music/services/music_service_facade.dart deleted file mode 100644 index 689d249f7..000000000 --- a/lib/modules/music/services/music_service_facade.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:get/get.dart'; -import '../controllers/music_controller.dart'; -import '../../../data/services/music_manager.dart'; - -/// 音乐服务门面 -/// 作为音乐功能的统一入口,简化模块间的交互 -class MusicServiceFacade { - // 单例实现 - static final MusicServiceFacade _instance = MusicServiceFacade._internal(); - - factory MusicServiceFacade() => _instance; - - MusicServiceFacade._internal(); - - // 获取音乐控制器实例 - MusicController get musicController => Get.find(); - - // 获取音乐管理器实例 - MusicManager get musicManager => Get.find(); - - /// 播放音乐 - Future musicPlay(Map song) async { - await musicController.musicPlay(song); - } - - /// 暂停音乐 - Future musicPause() async { - await musicController.musicPause(); - } - - /// 恢复播放 - Future musicResume() async { - await musicController.musicResume(); - } - - /// 播放音乐(兼容旧接口) - Future playMusic(Map song) async { - await musicPlay(song); - } - - /// 暂停音乐(兼容旧接口) - Future pauseMusic() async { - await musicPause(); - } - - /// 恢复播放(兼容旧接口) - Future resumeMusic() async { - await musicResume(); - } - - /// 播放/暂停切换 - Future togglePlayPause() async { - if (musicController.isPlaying.value) { - await musicPause(); - } else { - await musicResume(); - } - } - - /// 播放上一曲 - Future playPrevious() async { - await musicController.playPrevious(); - } - - /// 播放下一曲 - Future playNext() async { - await musicController.playNext(); - } - - /// 播放音乐列表 - Future playPlaylist(List> playlist) async { - await musicController.playPlaylist(playlist); - } - - /// 跳转到指定位置 - Future seekToPosition(int positionMs) async { - await musicController.seekToPosition(positionMs); - } - - /// 获取当前播放状态 - bool get isPlayingValue => musicController.isPlaying.value; - - /// 获取当前播放歌曲ID - int get currentIdValue => musicController.currentId.value; - - /// 获取当前播放位置(毫秒) - int get currentPositionValue => musicController.currentPosition.value; - - /// 获取当前歌曲总时长(毫秒) - int get durationValue => musicController.duration.value; - - /// 获取当前播放进度(0.0-1.0) - double get progressValue => musicController.progress.value; - - /// 获取响应式播放状态(用于Obx) - get isPlaying => musicController.isPlaying; - - /// 获取响应式当前播放歌曲ID(用于Obx) - get currentId => musicController.currentId; - - /// 获取响应式当前播放位置(用于Obx) - get currentPosition => musicController.currentPosition; - - /// 获取响应式当前歌曲总时长(用于Obx) - get duration => musicController.duration; - - /// 获取响应式当前播放进度(用于Obx) - get progress => musicController.progress; - - /// 获取当前播放列表 - List> get currentPlaylist => musicController.currentPlaylist; - - /// 获取当前播放索引 - int get currentPlaylistIndex => musicController.currentPlaylistIndex.value; - - /// 设置加载状态 - void setLoadingMusic(String id, bool loading) { - musicController.setLoadingMusic(id, loading); - } - - /// 更新当前播放列表上下文 - void updateCurrentPlaylistContext(List> playlist, int songId) { - musicController.updateCurrentPlaylistContext(playlist, songId); - } -} \ No newline at end of file diff --git a/lib/modules/music/views/music_playlist_view.dart b/lib/modules/music/views/music_playlist_view.dart index bda2fb878..69fa15a85 100644 --- a/lib/modules/music/views/music_playlist_view.dart +++ b/lib/modules/music/views/music_playlist_view.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter/services.dart'; -import '../services/music_service_facade.dart'; import '../../../core/utils/logger.dart'; +import '../../agent/controllers/agent_controller.dart'; import '../../agent/views/circular_progress_button.dart'; class MusicPlaylistView extends StatelessWidget { @@ -13,7 +13,7 @@ class MusicPlaylistView extends StatelessWidget { @override Widget build(BuildContext context) { ScreenUtil.init(context, designSize: const Size(375, 812)); // 添加设计尺寸参考 - final musicFacade = Get.find(); + final controller = Get.find(); // 获取控制器 final List> playlist = Get.arguments ?? []; final isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -55,14 +55,14 @@ class MusicPlaylistView extends StatelessWidget { Container( height: MediaQuery.of(context).size.height * 0.38, // 增大高度比例 child: _buildCurrentPlayingSection( - musicFacade, playlist, isDarkMode), + controller, playlist, isDarkMode), ), // 中部:播放全部按钮 - _buildPlayAllSection(musicFacade, playlist, isDarkMode), + _buildPlayAllSection(controller, playlist, isDarkMode), // 下部:歌单列表 - 使用剩余空间 Expanded( child: - _buildPlaylistSection(musicFacade, playlist, isDarkMode), + _buildPlaylistSection(controller, playlist, isDarkMode), ), ], ), @@ -70,19 +70,19 @@ class MusicPlaylistView extends StatelessWidget { } // 构建当前播放音乐详情部分 - Widget _buildCurrentPlayingSection(MusicServiceFacade musicFacade, + Widget _buildCurrentPlayingSection(AgentController controller, List> playlist, bool isDarkMode) { return SingleChildScrollView( // 添加滚动支持 child: Obx(() { // 查找当前播放的歌曲 Map? currentSong; - if (musicFacade.currentId.value != 0) { + if (controller.currentId.value != 0) { try { currentSong = playlist.firstWhere( (song) => int.tryParse(song['id']?.toString() ?? '0') == - musicFacade.currentId.value, + controller.currentId.value, ); } catch (e) { // 如果没找到,使用第一首歌曲 @@ -137,10 +137,10 @@ class MusicPlaylistView extends StatelessWidget { _buildSongInfo(title, artist, isDarkMode), SizedBox(height: 16.h), // 进度条 - _buildProgressBar(musicFacade, isDarkMode), + _buildProgressBar(controller, isDarkMode), SizedBox(height: 15.h), // 播放控制按钮 - _buildPlayControls(musicFacade, playlist, isDarkMode), + _buildPlayControls(controller, playlist, isDarkMode), ], ), ); @@ -149,7 +149,7 @@ class MusicPlaylistView extends StatelessWidget { } // 构建播放全部按钮部分 - Widget _buildPlayAllSection(MusicServiceFacade musicFacade, + Widget _buildPlayAllSection(AgentController controller, List> playlist, bool isDarkMode) { return Container( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), @@ -159,7 +159,7 @@ class MusicPlaylistView extends StatelessWidget { child: ElevatedButton.icon( onPressed: () async { if (playlist.isNotEmpty) { - await musicFacade.playPlaylist(playlist); + await controller.playPlaylist(playlist); } }, icon: Icon(Icons.play_arrow, color: Colors.white, size: 20.w), @@ -185,7 +185,7 @@ class MusicPlaylistView extends StatelessWidget { } // 构建歌单列表部分 - Widget _buildPlaylistSection(MusicServiceFacade musicFacade, + Widget _buildPlaylistSection(AgentController controller, List> playlist, bool isDarkMode) { return ListView.builder( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h), @@ -200,7 +200,7 @@ class MusicPlaylistView extends StatelessWidget { return _buildMusicItem( context, - musicFacade, + controller, id, name, url, @@ -214,7 +214,7 @@ class MusicPlaylistView extends StatelessWidget { Widget _buildMusicItem( BuildContext context, - MusicServiceFacade musicFacade, + AgentController controller, String id, String name, String url, @@ -232,7 +232,7 @@ class MusicPlaylistView extends StatelessWidget { return GestureDetector( onTap: () { - _playSelectedSong(musicFacade, song, Get.arguments ?? []); + _playSelectedSong(controller, song, Get.arguments ?? []); }, child: Container( margin: EdgeInsets.only(bottom: 10.h), @@ -324,10 +324,10 @@ class MusicPlaylistView extends StatelessWidget { // 播放按钮 Obx(() { final currentIdInt = int.tryParse(id) ?? 0; - final isThisPlaying = musicFacade.isPlaying.value && - musicFacade.currentId.value == currentIdInt; + final isThisPlaying = controller.isPlaying.value && + controller.currentId.value == currentIdInt; final currentProgress = - isThisPlaying ? musicFacade.progress.value : 0.0; + isThisPlaying ? controller.progress.value : 0.0; return CircularProgressButton( isPlaying: isThisPlaying, @@ -339,20 +339,20 @@ class MusicPlaylistView extends StatelessWidget { onPlayPause: () async { try { Logger.d('MusicPlaylistView', - '播放按钮被点击,当前播放状态: ${musicFacade.isPlaying.value}'); + '播放按钮被点击,当前播放状态: ${controller.isPlaying.value}'); Logger.d('MusicPlaylistView', - '当前歌曲ID: ${musicFacade.currentId.value}, 点击歌曲ID: $currentIdInt'); - if (musicFacade.isPlaying.value) { - if (musicFacade.currentId.value == currentIdInt) { + '当前歌曲ID: ${controller.currentId.value}, 点击歌曲ID: $currentIdInt'); + if (controller.isPlaying.value) { + if (controller.currentId.value == currentIdInt) { Logger.d('MusicPlaylistView', '暂停当前播放的歌曲'); - await musicFacade.musicPause(); + await controller.musicPause(); } else { Logger.d('MusicPlaylistView', '播放新歌曲'); - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } else { Logger.d('MusicPlaylistView', '开始播放歌曲'); - await musicFacade.musicPlay(song); + await controller.musicPlay(song); } } catch (e) { Logger.e('MusicPlaylistView', '播放音乐时发生错误: $e'); @@ -362,8 +362,8 @@ class MusicPlaylistView extends StatelessWidget { ? (seekProgress) { // 只有当前播放的歌曲才允许跳转 final targetPosition = - (musicFacade.duration.value * seekProgress).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * seekProgress).round(); + controller.seekToPosition(targetPosition); } : null, ); @@ -449,14 +449,14 @@ class MusicPlaylistView extends StatelessWidget { } // 构建进度条 - Widget _buildProgressBar(MusicServiceFacade musicFacade, bool isDarkMode) { + Widget _buildProgressBar(AgentController controller, bool isDarkMode) { return Obx(() { return Row( children: [ SizedBox( width: 40.w, child: Text( - _formatDuration(musicFacade.currentPosition.value), + _formatDuration(controller.currentPosition.value), style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -480,11 +480,11 @@ class MusicPlaylistView extends StatelessWidget { overlayShape: const RoundSliderOverlayShape(overlayRadius: 14), ), child: Slider( - value: musicFacade.progress.value.clamp(0.0, 1.0), + value: controller.progress.value.clamp(0.0, 1.0), onChanged: (value) { final targetPosition = - (musicFacade.duration.value * value).round(); - musicFacade.seekToPosition(targetPosition); + (controller.duration.value * value).round(); + controller.seekToPosition(targetPosition); }, ), ), @@ -493,7 +493,7 @@ class MusicPlaylistView extends StatelessWidget { SizedBox( width: 40.w, child: Text( - _formatDuration(musicFacade.duration.value), + _formatDuration(controller.duration.value), style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], @@ -508,14 +508,14 @@ class MusicPlaylistView extends StatelessWidget { } // 构建播放控制按钮 - Widget _buildPlayControls(MusicServiceFacade musicFacade, + Widget _buildPlayControls(AgentController controller, List> playlist, bool isDarkMode) { return Obx(() { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( - onPressed: () => _playPrevious(musicFacade, playlist), + onPressed: () => _playPrevious(controller, playlist), iconSize: 32.w, padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( @@ -540,10 +540,10 @@ class MusicPlaylistView extends StatelessWidget { ], ), child: IconButton( - onPressed: () => _togglePlayPause(musicFacade), + onPressed: () => _togglePlayPause(controller), padding: EdgeInsets.zero, icon: Icon( - musicFacade.isPlaying.value ? Icons.pause : Icons.play_arrow, + controller.isPlaying.value ? Icons.pause : Icons.play_arrow, size: 28.w, color: Colors.white, ), @@ -551,7 +551,7 @@ class MusicPlaylistView extends StatelessWidget { ), SizedBox(width: 16.w), IconButton( - onPressed: () => _playNext(musicFacade, playlist), + onPressed: () => _playNext(controller, playlist), iconSize: 32.w, padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( @@ -574,35 +574,35 @@ class MusicPlaylistView extends StatelessWidget { } // 播放/暂停切换 - Future _togglePlayPause(MusicServiceFacade musicFacade) async { - if (musicFacade.isPlaying.value) { - await musicFacade.musicPause(); + Future _togglePlayPause(AgentController controller) async { + if (controller.isPlaying.value) { + await controller.musicPause(); } else { - await musicFacade.musicResume(); + await controller.musicResume(); } } // 播放上一曲 void _playPrevious( - MusicServiceFacade musicFacade, List> playlist) { - musicFacade.updateCurrentPlaylistContext( - playlist, musicFacade.currentId.value); - musicFacade.playPrevious(); + AgentController controller, List> playlist) { + controller.updateCurrentPlaylistContext( + playlist, controller.currentId.value); + controller.playPrevious(); } // 播放下一曲 void _playNext( - MusicServiceFacade musicFacade, List> playlist) { - musicFacade.updateCurrentPlaylistContext( - playlist, musicFacade.currentId.value); - musicFacade.playNext(); + AgentController controller, List> playlist) { + controller.updateCurrentPlaylistContext( + playlist, controller.currentId.value); + controller.playNext(); } // 播放选中的歌曲 - void _playSelectedSong(MusicServiceFacade musicFacade, Map song, + void _playSelectedSong(AgentController controller, Map song, List> playlist) { final songId = int.tryParse(song['id']?.toString() ?? '0') ?? 0; - musicFacade.updateCurrentPlaylistContext(playlist, songId); - musicFacade.musicPlay(song); + controller.updateCurrentPlaylistContext(playlist, songId); + controller.musicPlay(song); } } diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt index e1d38f7e4..84964300a 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt @@ -1,5 +1,6 @@ package com.yunqiinnovation.music_service +import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -13,7 +14,9 @@ import android.media.AudioManager import android.media.MediaPlayer import android.os.Build import android.os.Bundle +import android.os.Handler import android.os.IBinder +import android.os.Looper import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat @@ -47,36 +50,36 @@ class MusicService : MediaBrowserServiceCompat() { fun isPlaying() = currentStatus fun getCurrentId(): Int? = currentIdInternal - // 获取播放进度信息 - fun getProgress(): Map { - return if (instance != null && instance!!.mediaPlayer != null) { - val player = instance!!.mediaPlayer!! - try { - val currentPosition = if (player.isPlaying || currentStatus) player.currentPosition else 0 - val duration = player.duration - val progress = if (duration > 0) currentPosition.toDouble() / duration.toDouble() else 0.0 - - mapOf( - "currentPosition" to currentPosition, - "duration" to duration, - "progress" to progress - ) - } catch (e: Exception) { - Log.e(TAG, "获取播放进度失败: ${e.message}") - mapOf( - "currentPosition" to 0, - "duration" to 0, - "progress" to 0.0 - ) - } - } else { - mapOf( - "currentPosition" to 0, - "duration" to 0, - "progress" to 0.0 - ) - } - } + // // 获取播放进度信息 + // fun getProgress(): Map { + // return if (instance != null && instance!!.mediaPlayer != null) { + // val player = instance!!.mediaPlayer!! + // try { + // val currentPosition = if (player.isPlaying || currentStatus) player.currentPosition else 0 + // val duration = player.duration + // val progress = if (duration > 0) currentPosition.toDouble() / duration.toDouble() else 0.0 + + // mapOf( + // "currentPosition" to currentPosition, + // "duration" to duration, + // "progress" to progress + // ) + // } catch (e: Exception) { + // Log.e(TAG, "获取播放进度失败: ${e.message}") + // mapOf( + // "currentPosition" to 0, + // "duration" to 0, + // "progress" to 0.0 + // ) + // } + // } else { + // mapOf( + // "currentPosition" to 0, + // "duration" to 0, + // "progress" to 0.0 + // ) + // } + // } private var instance: MusicService? = null private var flutterChannel: io.flutter.plugin.common.MethodChannel? = null @@ -118,7 +121,7 @@ class MusicService : MediaBrowserServiceCompat() { val title = song["title"] as? String ?: song["name"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: song["sgener"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as? String ?: song["image"] as? String ?: "" - +currentIdInternal = song["id"] as? Int ?: song["id"] as? Int ?: 0 val metadata = android.support.v4.media.MediaMetadataCompat.Builder() .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(android.support.v4.media.MediaMetadataCompat.METADATA_KEY_ARTIST, artist) @@ -166,6 +169,8 @@ class MusicService : MediaBrowserServiceCompat() { private lateinit var mediaSession: MediaSessionCompat private lateinit var audioManager: AudioManager private var mediaPlayer: MediaPlayer? = null + private val progressHandler = Handler(Looper.getMainLooper()) + private lateinit var progressUpdater: Runnable override fun onCreate() { super.onCreate() @@ -191,11 +196,35 @@ class MusicService : MediaBrowserServiceCompat() { registerReceiver(controlReceiver, filter, Context.RECEIVER_NOT_EXPORTED) } else { registerReceiver(controlReceiver, filter) + } + // 初始化进度更新任务 + progressUpdater = Runnable { + updateProgress() + progressHandler.postDelayed(progressUpdater, 1000) // 每秒更新一次 } isReceiverRegistered = true Log.i(TAG, "MediaService 启动完成") } - +@SuppressLint("SuspiciousIndentation") +private fun updateProgress() { + mediaPlayer?.let { player -> + if (player.isPlaying || currentStatus) { + val currentPosition = player.currentPosition + val duration = player.duration + val progress = if (duration > 0) currentPosition.toFloat() / duration else 0f + // 更新媒体会话进度 + + // 通知Flutter层 + flutterChannel?.invokeMethod("onMediaSessionSeekTo1", + mapOf( + "currentIdInternal" to currentIdInternal, + "currentPosition" to currentPosition, + "duration" to duration, + "progress" to progress + )) + } + } + } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.i(TAG, "onStartCommand: $intent") intent?.action?.takeIf { it == Intent.ACTION_MEDIA_BUTTON }?.let { @@ -210,7 +239,7 @@ class MusicService : MediaBrowserServiceCompat() { val songMap = intent.getSerializableExtra("song") as? Map songMap?.let { song -> val songId = song["id"] ?: return@let - + // currentIdInternal = songId val existingIndex = playlist.indexOfFirst { it["id"] == songId } if (existingIndex == -1) { playlist.add(song) @@ -323,9 +352,9 @@ class MusicService : MediaBrowserServiceCompat() { } override fun onSeekTo(pos: Long) { Log.i(TAG, "onSeekTo 通知: ${pos}ms") - seekTo(pos.toInt()) - // 通知Flutter层 - flutterChannel?.invokeMethod("onMediaSessionSeekTo", mapOf("position" to pos.toInt())) + // seekTo(pos.toInt()) + // // 通知Flutter层 + // flutterChannel?.invokeMethod("onMediaSessionSeekTo", mapOf("position" to pos.toInt())) } override fun onStop() { Log.i(TAG, "onStop 通知!") @@ -531,9 +560,13 @@ class MusicService : MediaBrowserServiceCompat() { setOnPreparedListener { start() updateNotification(title,artist,coverUrl) + // 启动进度更新 + progressHandler.post(progressUpdater) Log.i(TAG, "开始播放: $url") } - setOnCompletionListener { playNextOrStop() } + setOnCompletionListener { // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) + playNextOrStop() } setOnErrorListener { _, what, extra -> Log.e(TAG, "播放错误 what=$what extra=$extra") true @@ -547,17 +580,17 @@ class MusicService : MediaBrowserServiceCompat() { mediaPlayer?.takeIf { it.isPlaying }?.apply { pause() currentStatus = false - mediaSession.setPlaybackState( - PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_PAUSED, currentPosition.toLong(), 1.0f) - .build() - ) + val song = playlist[currentIndex] val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String + val id = song["id"] as? Int ?: 0 + currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PAUSED); + // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) Log.i(TAG, "已暂停") } @@ -567,17 +600,17 @@ class MusicService : MediaBrowserServiceCompat() { mediaPlayer?.takeIf { !it.isPlaying }?.apply { start() currentStatus = true - mediaSession.setPlaybackState( - PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_PLAYING, currentPosition.toLong(), 1.0f) - .build() - ) + val song = playlist[currentIndex] val title = song["title"] as? String ?: "未知标题" val artist = song["artist"] as? String ?: "未知歌手" val coverUrl = song["coverUrl"] as String + val id = song["id"] as? Int ?: 0 + currentIdInternal = id updateNotification(title,artist,coverUrl) setPlaybackState(PlaybackStateCompat.STATE_PLAYING); + // 重启进度更新 + progressHandler.post(progressUpdater) // Log.i(TAG, "继续播放") } } @@ -635,6 +668,8 @@ class MusicService : MediaBrowserServiceCompat() { unregisterReceiver(controlReceiver) isReceiverRegistered = false } + // 停止进度更新 + progressHandler.removeCallbacks(progressUpdater) mediaPlayer?.release() stopForeground(true) isRunning.set(false) diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt index 0c2fe1256..9d5f1043b 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -70,10 +70,10 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { ) result.success(status) } - "getProgress" -> { - val progressInfo = MusicService.getProgress() - result.success(progressInfo) - } + // "getProgress" -> { + // // val progressInfo = MusicService.getProgress() + // result.success(progressInfo) + // } "seekTo" -> { val position = call.argument("position") if (position != null) { diff --git a/local_plugins/music_service/lib/music_service.dart b/local_plugins/music_service/lib/music_service.dart index b9d43552e..4c59cd5c0 100644 --- a/local_plugins/music_service/lib/music_service.dart +++ b/local_plugins/music_service/lib/music_service.dart @@ -82,7 +82,10 @@ class MusicService { /// 更新媒体会话进度信息 Future updateMediaSessionProgress( - int currentPosition, int duration, double progress) async { + int currentPosition, + int duration, + double progress, + ) async { await _methodChannel.invokeMethod('updateMediaSessionProgress', { 'currentPosition': currentPosition, 'duration': duration, @@ -98,6 +101,7 @@ class MusicService { required Function() onPrevious, required Function(int) onSeekTo, required Function() onStop, + required Function(int, int, int, double) onStop1, }) async { // 设置方法调用处理器来处理来自原生平台的回调 _methodChannel.setMethodCallHandler((call) async { @@ -121,6 +125,13 @@ class MusicService { case 'onMediaSessionStop': onStop(); break; + case 'onMediaSessionSeekTo1': + final currentIdInternal = call.arguments['currentIdInternal'] as int; + final currentPosition = call.arguments['currentPosition'] as int; + final duration = call.arguments['duration'] as int; + final progress = call.arguments['progress'] as double; + onStop1(currentIdInternal, currentPosition, duration, progress); + break; } });