You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
15 KiB
493 lines
15 KiB
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');
|
|
}
|
|
}
|
|
}
|