Browse Source

修复音乐播放器卡片不同步问题

newdev_shunjiawei
fdp 1 year ago
parent
commit
63cfcccede
  1. 6
      lib/core/bindings/initial_binding.dart
  2. 2
      lib/data/services/music_manager.dart
  3. 534
      lib/modules/agent/controllers/agent_controller.dart
  4. 36
      lib/modules/agent/views/music_card.dart
  5. 15
      lib/modules/music/bindings/music_binding.dart
  6. 493
      lib/modules/music/controllers/music_controller.dart
  7. 125
      lib/modules/music/services/music_service_facade.dart
  8. 108
      lib/modules/music/views/music_playlist_view.dart
  9. 129
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt
  10. 8
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt
  11. 13
      local_plugins/music_service/lib/music_service.dart

6
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>(() => BleManager(), fenix: true);
// MusicManager
Get.lazyPut<MusicManager>(() => MusicManager(), fenix: true);
// 音乐模块
Get.lazyPut<MusicController>(() => MusicController(), fenix: true);
Get.lazyPut<MusicServiceFacade>(() => MusicServiceFacade(), fenix: true);
Get.lazyPut<NavigationManager>(() => NavigationManager(), fenix: true);
Get.lazyPut<LocationManager>(() => LocationManager(), fenix: true);
// 火山AI服务

2
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');

534
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 = <Message>[].obs;
// UI控制器
final TextEditingController textController = TextEditingController();
final ScrollController scrollController = ScrollController();
//音乐播放管理
final _musiceManager = Get.find<MusicManager>();
// 导航管理
final _navigationManager = Get.find<NavigationManager>();
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 = <Map<String, dynamic>>[].obs; // 当前播放列表
final currentPlaylistIndex = 0.obs; // 当前播放歌曲在列表中的索引
// 各种状态标志
final isInitialized = false.obs;
@ -98,6 +110,8 @@ class AgentController extends GetxController {
// BLE事件订阅
StreamSubscription<dynamic>? _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<void> _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<void> 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<void> musicPlay(Map<String, dynamic> 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<String, dynamic>;
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 = <String, String>{
'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<void> musicPause() async {
try {
Logger.d('AgentController', '暂停音乐播放');
await _musiceManager.pause();
isPlaying.value = false;
_stopProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(false);
} catch (e) {
Logger.e('AgentController', '暂停音乐失败: $e');
}
}
// 恢复音乐播放
Future<void> 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<void> 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<void> 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<void> _updateMediaSessionProgress() async {
try {
// 这里将调用music_service插件的方法来更新系统媒体会话进度
// 具体实现将在music_service插件中添加
await _musiceManager.updateMediaSessionProgress(
currentPosition.value,
duration.value,
progress.value,
);
} catch (e) {
// 忽略错误,避免影响正常播放
Logger.d('AgentController', '更新媒体会话进度失败: $e');
}
}
/// 更新系统媒体会话的元数据
Future<void> _updateMediaSessionMetadata(Map<String, dynamic> song) async {
try {
await _musiceManager.updateMediaSessionMetadata(song);
} catch (e) {
Logger.d('AgentController', '更新媒体会话元数据失败: $e');
}
}
/// 更新系统媒体会话的播放状态
Future<void> _updateMediaSessionPlaybackState(bool isPlaying) async {
try {
await _musiceManager.updateMediaSessionPlaybackState(isPlaying);
} catch (e) {
Logger.d('AgentController', '更新媒体会话播放状态失败: $e');
}
}
// 跳转到指定位置
Future<void> seekToPosition(int positionMs) async {
try {
await _musiceManager.seekTo(positionMs);
// 立即更新进度状态
// await updateMusicProgress();
} catch (e) {
Logger.e('AgentController', '跳转播放位置失败: $e');
}
}
// 跳转到指定位置
Future<void> 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<void> playPlaylist(List<Map<String, dynamic>> 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<void> 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<void> 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<Map<String, dynamic>> 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<void> onMediaSessionPlay() async {
Logger.d(TAG, '收到媒体会话播放回调');
if (!isPlaying.value) {
await musicResume();
}
}
/// 处理来自系统通知栏的暂停操作
Future<void> onMediaSessionPause() async {
Logger.d(TAG, '收到媒体会话暂停回调');
if (isPlaying.value) {
await musicPause();
}
}
/// 处理来自系统通知栏的下一曲操作
Future<void> onMediaSessionNext() async {
Logger.d(TAG, '收到媒体会话下一曲回调');
await playNext();
}
/// 处理来自系统通知栏的上一曲操作
Future<void> onMediaSessionPrevious() async {
Logger.d(TAG, '收到媒体会话上一曲回调');
await playPrevious();
}
/// 处理来自系统通知栏的跳转操作
Future<void> onMediaSessionSeekTo(int positionMs) async {
Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms');
await seekToPosition(positionMs);
}
/// 处理来自系统通知栏的停止操作
Future<void> onMediaSessionStop() async {
Logger.d(TAG, '收到媒体会话停止回调');
await musicPause();
// 重置播放状态
currentPosition.value = 0;
progress.value = 0.0;
_stopProgressTimer();
}
/// 处理来自系统通知栏的跳转操作
Future<void> onMediaSessionSeekTo1(int currentIdInternal, int newPosition,
int newDuration, double newProgress) async {
print(
"onMediaSessionSeekTo1 newPosition:$newPosition newDuration:$newDuration newProgress:$newProgress");
await seekToPosition1(
currentIdInternal, newPosition, newDuration, newProgress);
}
}

36
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<MusicServiceFacade>(); // 获取音乐服务门面
final controller = Get.find<AgentController>(); // 获取控制器
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
Map<String, dynamic> 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<Map<String, dynamic>> playlist) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final controller = Get.find<AgentController>(); // 获取控制器
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,

15
lib/modules/music/bindings/music_binding.dart

@ -1,15 +0,0 @@
import 'package:get/get.dart';
import '../controllers/music_controller.dart';
/// 音乐模块绑定
/// 负责注册音乐相关的控制器和服务
class MusicBinding extends Bindings {
@override
void dependencies() {
// 注册音乐控制器为单例
Get.lazyPut<MusicController>(
() => MusicController(),
fenix: true, // 允许重新创建
);
}
}

493
lib/modules/music/controllers/music_controller.dart

@ -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<MusicManager>();
// 播放状态
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 = <Map<String, dynamic>>[].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<void> _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<void> 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<void> musicPlay(Map<String, dynamic> 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<String, dynamic>;
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 = <String, String>{
'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<void> musicPause() async {
try {
Logger.d(TAG, '暂停音乐播放');
await _musicManager.pause();
isPlaying.value = false;
_stopProgressTimer();
// 更新媒体会话播放状态
await _updateMediaSessionPlaybackState(false);
} catch (e) {
Logger.e(TAG, '暂停音乐失败: $e');
}
}
// 恢复音乐播放
Future<void> 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<void> 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<void> seekToPosition(int positionMs) async {
try {
await _musicManager.seekTo(positionMs);
// 立即更新进度状态
await updateMusicProgress();
} catch (e) {
Logger.e(TAG, '跳转播放位置失败: $e');
}
}
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> 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<void> 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<void> 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<Map<String, dynamic>> 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<void> onMediaSessionPlay() async {
Logger.d(TAG, '收到媒体会话播放回调');
if (!isPlaying.value) {
await musicResume();
}
}
/// 处理来自系统通知栏的暂停操作
Future<void> onMediaSessionPause() async {
Logger.d(TAG, '收到媒体会话暂停回调');
if (isPlaying.value) {
await musicPause();
}
}
/// 处理来自系统通知栏的下一曲操作
Future<void> onMediaSessionNext() async {
Logger.d(TAG, '收到媒体会话下一曲回调');
await playNext();
}
/// 处理来自系统通知栏的上一曲操作
Future<void> onMediaSessionPrevious() async {
Logger.d(TAG, '收到媒体会话上一曲回调');
await playPrevious();
}
/// 处理来自系统通知栏的跳转操作
Future<void> onMediaSessionSeekTo(int positionMs) async {
Logger.d(TAG, '收到媒体会话跳转回调: ${positionMs}ms');
await seekToPosition(positionMs);
}
/// 处理来自系统通知栏的停止操作
Future<void> onMediaSessionStop() async {
Logger.d(TAG, '收到媒体会话停止回调');
await musicPause();
// 重置播放状态
currentPosition.value = 0;
progress.value = 0.0;
_stopProgressTimer();
}
// ========== 私有辅助方法 ==========
/// 更新系统媒体会话的进度信息
Future<void> _updateMediaSessionProgress() async {
try {
// 具体实现将在music_service插件中添加
await _musicManager.updateMediaSessionProgress(
currentPosition.value,
duration.value,
progress.value,
);
} catch (e) {
// 忽略错误,避免影响正常播放
Logger.d(TAG, '更新媒体会话进度失败: $e');
}
}
/// 更新系统媒体会话的元数据
Future<void> _updateMediaSessionMetadata(Map<String, dynamic> song) async {
try {
await _musicManager.updateMediaSessionMetadata(song);
} catch (e) {
Logger.d(TAG, '更新媒体会话元数据失败: $e');
}
}
/// 更新系统媒体会话的播放状态
Future<void> _updateMediaSessionPlaybackState(bool isPlaying) async {
try {
await _musicManager.updateMediaSessionPlaybackState(isPlaying);
} catch (e) {
Logger.d(TAG, '更新媒体会话播放状态失败: $e');
}
}
}

125
lib/modules/music/services/music_service_facade.dart

@ -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<MusicController>();
// 获取音乐管理器实例
MusicManager get musicManager => Get.find<MusicManager>();
/// 播放音乐
Future<void> musicPlay(Map<String, dynamic> song) async {
await musicController.musicPlay(song);
}
/// 暂停音乐
Future<void> musicPause() async {
await musicController.musicPause();
}
/// 恢复播放
Future<void> musicResume() async {
await musicController.musicResume();
}
/// 播放音乐(兼容旧接口)
Future<void> playMusic(Map<String, dynamic> song) async {
await musicPlay(song);
}
/// 暂停音乐(兼容旧接口)
Future<void> pauseMusic() async {
await musicPause();
}
/// 恢复播放(兼容旧接口)
Future<void> resumeMusic() async {
await musicResume();
}
/// 播放/暂停切换
Future<void> togglePlayPause() async {
if (musicController.isPlaying.value) {
await musicPause();
} else {
await musicResume();
}
}
/// 播放上一曲
Future<void> playPrevious() async {
await musicController.playPrevious();
}
/// 播放下一曲
Future<void> playNext() async {
await musicController.playNext();
}
/// 播放音乐列表
Future<void> playPlaylist(List<Map<String, dynamic>> playlist) async {
await musicController.playPlaylist(playlist);
}
/// 跳转到指定位置
Future<void> 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<Map<String, dynamic>> get currentPlaylist => musicController.currentPlaylist;
/// 获取当前播放索引
int get currentPlaylistIndex => musicController.currentPlaylistIndex.value;
/// 设置加载状态
void setLoadingMusic(String id, bool loading) {
musicController.setLoadingMusic(id, loading);
}
/// 更新当前播放列表上下文
void updateCurrentPlaylistContext(List<Map<String, dynamic>> playlist, int songId) {
musicController.updateCurrentPlaylistContext(playlist, songId);
}
}

108
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<MusicServiceFacade>();
final controller = Get.find<AgentController>(); // 获取控制器
final List<Map<String, dynamic>> 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<Map<String, dynamic>> playlist, bool isDarkMode) {
return SingleChildScrollView(
// 添加滚动支持
child: Obx(() {
// 查找当前播放的歌曲
Map<String, dynamic>? 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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<void> _togglePlayPause(MusicServiceFacade musicFacade) async {
if (musicFacade.isPlaying.value) {
await musicFacade.musicPause();
Future<void> _togglePlayPause(AgentController controller) async {
if (controller.isPlaying.value) {
await controller.musicPause();
} else {
await musicFacade.musicResume();
await controller.musicResume();
}
}
// 播放上一曲
void _playPrevious(
MusicServiceFacade musicFacade, List<Map<String, dynamic>> playlist) {
musicFacade.updateCurrentPlaylistContext(
playlist, musicFacade.currentId.value);
musicFacade.playPrevious();
AgentController controller, List<Map<String, dynamic>> playlist) {
controller.updateCurrentPlaylistContext(
playlist, controller.currentId.value);
controller.playPrevious();
}
// 播放下一曲
void _playNext(
MusicServiceFacade musicFacade, List<Map<String, dynamic>> playlist) {
musicFacade.updateCurrentPlaylistContext(
playlist, musicFacade.currentId.value);
musicFacade.playNext();
AgentController controller, List<Map<String, dynamic>> playlist) {
controller.updateCurrentPlaylistContext(
playlist, controller.currentId.value);
controller.playNext();
}
// 播放选中的歌曲
void _playSelectedSong(MusicServiceFacade musicFacade, Map<String, dynamic> song,
void _playSelectedSong(AgentController controller, Map<String, dynamic> song,
List<Map<String, dynamic>> playlist) {
final songId = int.tryParse(song['id']?.toString() ?? '0') ?? 0;
musicFacade.updateCurrentPlaylistContext(playlist, songId);
musicFacade.musicPlay(song);
controller.updateCurrentPlaylistContext(playlist, songId);
controller.musicPlay(song);
}
}

129
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<String, Any> {
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<String, Any> {
// 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<String, Any>
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)

8
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<Int>("position")
if (position != null) {

13
local_plugins/music_service/lib/music_service.dart

@ -82,7 +82,10 @@ class MusicService {
/// 更新媒体会话进度信息
Future<void> 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;
}
});

Loading…
Cancel
Save