|
|
@ -1,7 +1,9 @@ |
|
|
|
|
|
import 'dart:async'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:qq_music/qq_music.dart'; |
|
|
import 'package:qq_music/qq_music.dart'; |
|
|
|
|
|
import '../models/song_model.dart'; |
|
|
|
|
|
|
|
|
/// QQ音乐服务适配器 |
|
|
/// QQ音乐服务适配器 - 全局状态管理 |
|
|
class QqMusocService extends GetxService { |
|
|
class QqMusocService extends GetxService { |
|
|
static QqMusocService get to => Get.find<QqMusocService>(); |
|
|
static QqMusocService get to => Get.find<QqMusocService>(); |
|
|
|
|
|
|
|
|
@ -9,19 +11,173 @@ class QqMusocService extends GetxService { |
|
|
// 音乐服务实例 |
|
|
// 音乐服务实例 |
|
|
final QqMusic _qqMusic = QqMusic(); |
|
|
final QqMusic _qqMusic = QqMusic(); |
|
|
|
|
|
|
|
|
|
|
|
// 全局音乐状态 |
|
|
|
|
|
final isPlaying = false.obs; |
|
|
|
|
|
final currentPosition = 0.obs; // 毫秒 |
|
|
|
|
|
final totalDuration = 0.obs; // 毫秒 |
|
|
|
|
|
final currentSong = Rxn<SongModel>(); |
|
|
|
|
|
final playlist = <SongModel>[].obs; |
|
|
|
|
|
final currentIndex = 0.obs; |
|
|
|
|
|
|
|
|
|
|
|
// 状态监听定时器 |
|
|
|
|
|
Timer? _stateMonitorTimer; |
|
|
|
|
|
|
|
|
|
|
|
// 监听器列表 - 用于通知页面状态变化 |
|
|
|
|
|
final List<Function()> _listeners = []; |
|
|
|
|
|
|
|
|
/// 初始化 |
|
|
/// 初始化 |
|
|
@override |
|
|
@override |
|
|
void onInit() { |
|
|
void onInit() { |
|
|
super.onInit(); |
|
|
super.onInit(); |
|
|
|
|
|
print('🎵 [QQMusicService] 全局音乐服务初始化'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
|
void onClose() { |
|
|
|
|
|
_stateMonitorTimer?.cancel(); |
|
|
|
|
|
_listeners.clear(); |
|
|
|
|
|
print('🎵 [QQMusicService] 全局音乐服务销毁'); |
|
|
|
|
|
super.onClose(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 添加状态监听器 |
|
|
|
|
|
void addListener(Function() listener) { |
|
|
|
|
|
_listeners.add(listener); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 移除状态监听器 |
|
|
|
|
|
void removeListener(Function() listener) { |
|
|
|
|
|
_listeners.remove(listener); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 通知所有监听器 |
|
|
|
|
|
void _notifyListeners() { |
|
|
|
|
|
for (final listener in _listeners) { |
|
|
|
|
|
try { |
|
|
|
|
|
listener(); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
print('🔥 [QQMusicService] 监听器回调异常: $e'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 启动播放状态监控 |
|
|
|
|
|
void startPlaybackStateMonitor() { |
|
|
|
|
|
// 取消之前的定时器 |
|
|
|
|
|
_stateMonitorTimer?.cancel(); |
|
|
|
|
|
|
|
|
|
|
|
print('🎵 [QQMusicService] 启动全局状态监控'); |
|
|
|
|
|
|
|
|
|
|
|
// 立即执行一次同步 |
|
|
|
|
|
_syncPlaybackInfo(); |
|
|
|
|
|
|
|
|
|
|
|
// 启动定时器,每秒同步一次 |
|
|
|
|
|
_stateMonitorTimer = Timer.periodic(const Duration(seconds: 1), (timer) { |
|
|
|
|
|
_syncPlaybackInfo(); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
print('🎵 [QQMusicService] 全局状态监控已启动'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 停止播放状态监控 |
|
|
|
|
|
void stopPlaybackStateMonitor() { |
|
|
|
|
|
_stateMonitorTimer?.cancel(); |
|
|
|
|
|
_stateMonitorTimer = null; |
|
|
|
|
|
print('⏹️ [QQMusicService] 全局状态监控已停止'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 统一同步播放信息 |
|
|
|
|
|
Future<void> _syncPlaybackInfo() async { |
|
|
|
|
|
try { |
|
|
|
|
|
final result = await _qqMusic.syncCurrentPlayInfo(); |
|
|
|
|
|
|
|
|
|
|
|
if (result['success'] == true && result['data'] != null) { |
|
|
|
|
|
final data = result['data']; |
|
|
|
|
|
|
|
|
|
|
|
// 更新播放状态 |
|
|
|
|
|
final newIsPlaying = data['isPlaying'] ?? false; |
|
|
|
|
|
final wasPlaying = isPlaying.value; |
|
|
|
|
|
|
|
|
|
|
|
if (isPlaying.value != newIsPlaying) { |
|
|
|
|
|
print( |
|
|
|
|
|
'🎮 [QQMusicService] 全局播放状态变化: ${isPlaying.value} -> $newIsPlaying'); |
|
|
|
|
|
isPlaying.value = newIsPlaying; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果从播放状态变为停止状态,自动停止监听 |
|
|
|
|
|
if (wasPlaying && !newIsPlaying) { |
|
|
|
|
|
print('⏹️ [QQMusicService] 检测到播放停止,自动停止全局监听'); |
|
|
|
|
|
stopPlaybackStateMonitor(); |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果当前不在播放状态,也停止监听 |
|
|
|
|
|
if (!newIsPlaying) { |
|
|
|
|
|
print('⏸️ [QQMusicService] 当前未在播放,停止全局监听以节省资源'); |
|
|
|
|
|
stopPlaybackStateMonitor(); |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 更新播放时间(Android端返回秒,转换为毫秒) |
|
|
|
|
|
final currentTimeSec = data['currentTime'] ?? 0; |
|
|
|
|
|
final totalTimeSec = data['totalTime'] ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
final newCurrentPosition = (currentTimeSec * 1000).round(); |
|
|
|
|
|
final newTotalDuration = (totalTimeSec * 1000).round(); |
|
|
|
|
|
|
|
|
|
|
|
if (currentPosition.value != newCurrentPosition) { |
|
|
|
|
|
currentPosition.value = newCurrentPosition; |
|
|
|
|
|
} |
|
|
|
|
|
if (totalDuration.value != newTotalDuration) { |
|
|
|
|
|
totalDuration.value = newTotalDuration; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 更新当前歌曲信息 |
|
|
|
|
|
final songData = data['currentSong']; |
|
|
|
|
|
if (songData != null) { |
|
|
|
|
|
final newSong = SongModel( |
|
|
|
|
|
mid: songData['mid'] ?? '', |
|
|
|
|
|
name: songData['name'] ?? '', |
|
|
|
|
|
artist: songData['singer'] ?? '', |
|
|
|
|
|
album: songData['album'] ?? '', |
|
|
|
|
|
albumPic: songData['coverUri'], |
|
|
|
|
|
duration: songData['duration'] ?? 0, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (currentSong.value?.mid != newSong.mid) { |
|
|
|
|
|
print( |
|
|
|
|
|
'🎵 [QQMusicService] 当前歌曲变化: ${currentSong.value?.name} -> ${newSong.name}'); |
|
|
|
|
|
currentSong.value = newSong; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 通知所有监听器 |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 同步失败时停止监听 |
|
|
|
|
|
print('❌ [QQMusicService] 全局同步失败,停止监听'); |
|
|
|
|
|
stopPlaybackStateMonitor(); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
print('💥 [QQMusicService] 全局同步异常: $e'); |
|
|
|
|
|
stopPlaybackStateMonitor(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 格式化时间 |
|
|
|
|
|
String formatDuration(int milliseconds) { |
|
|
|
|
|
final duration = Duration(milliseconds: milliseconds); |
|
|
|
|
|
final minutes = duration.inMinutes; |
|
|
|
|
|
final seconds = duration.inSeconds % 60; |
|
|
|
|
|
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 登录QQ音乐 |
|
|
/// 登录QQ音乐 |
|
|
Future<Map<String, dynamic>> login() async { |
|
|
Future<Map<String, dynamic>> login() async { |
|
|
try { |
|
|
try { |
|
|
// 直接调用QQ音乐插件的登录方法 |
|
|
|
|
|
final result = await _qqMusic.login(); |
|
|
final result = await _qqMusic.login(); |
|
|
|
|
|
|
|
|
// 检查登录结果 |
|
|
|
|
|
if (result['success'] == true) { |
|
|
if (result['success'] == true) { |
|
|
isInitialized = true; |
|
|
isInitialized = true; |
|
|
return result; |
|
|
return result; |
|
|
@ -36,7 +192,7 @@ class QqMusocService extends GetxService { |
|
|
/// 搜索歌曲 |
|
|
/// 搜索歌曲 |
|
|
Future<Map<String, dynamic>> searchSongs({ |
|
|
Future<Map<String, dynamic>> searchSongs({ |
|
|
required String keyword, |
|
|
required String keyword, |
|
|
int searchType = 0, // SEARCH_TYPE_SONG |
|
|
int searchType = 0, |
|
|
}) async { |
|
|
}) async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.searchSongs( |
|
|
final result = await _qqMusic.searchSongs( |
|
|
@ -49,16 +205,41 @@ class QqMusocService extends GetxService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 播放歌曲列表 |
|
|
/// 播放歌曲列表 - 更新全局状态 |
|
|
Future<Map<String, dynamic>> playSongs({ |
|
|
Future<Map<String, dynamic>> playSongs({ |
|
|
required List<String> songMids, |
|
|
required List<String> songMids, |
|
|
int index = 0, |
|
|
int index = 0, |
|
|
|
|
|
List<SongModel>? songList, |
|
|
|
|
|
SongModel? currentSongModel, |
|
|
}) async { |
|
|
}) async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.playSongs( |
|
|
final result = await _qqMusic.playSongs( |
|
|
songMids: songMids, |
|
|
songMids: songMids, |
|
|
index: index, |
|
|
index: index, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
// 更新全局状态 |
|
|
|
|
|
if (songList != null) { |
|
|
|
|
|
playlist.assignAll(songList); |
|
|
|
|
|
currentIndex.value = index; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (currentSongModel != null) { |
|
|
|
|
|
currentSong.value = currentSongModel; |
|
|
|
|
|
totalDuration.value = currentSongModel.duration; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isPlaying.value = true; |
|
|
|
|
|
currentPosition.value = 0; |
|
|
|
|
|
|
|
|
|
|
|
// 启动全局状态监听 |
|
|
|
|
|
print('🎵 [QQMusicService] 开始播放,启动全局状态监听'); |
|
|
|
|
|
startPlaybackStateMonitor(); |
|
|
|
|
|
|
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'}; |
|
|
@ -69,6 +250,11 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> playMusic() async { |
|
|
Future<Map<String, dynamic>> playMusic() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.playMusic(); |
|
|
final result = await _qqMusic.playMusic(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
isPlaying.value = true; |
|
|
|
|
|
startPlaybackStateMonitor(); |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'}; |
|
|
@ -79,6 +265,10 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> pauseMusic() async { |
|
|
Future<Map<String, dynamic>> pauseMusic() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.pauseMusic(); |
|
|
final result = await _qqMusic.pauseMusic(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
isPlaying.value = false; |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '暂停失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '暂停失败: ${e.toString()}'}; |
|
|
@ -89,6 +279,11 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> resumeMusic() async { |
|
|
Future<Map<String, dynamic>> resumeMusic() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.resumeMusic(); |
|
|
final result = await _qqMusic.resumeMusic(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
isPlaying.value = true; |
|
|
|
|
|
startPlaybackStateMonitor(); |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '继续播放失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '继续播放失败: ${e.toString()}'}; |
|
|
@ -99,6 +294,12 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> stopMusic() async { |
|
|
Future<Map<String, dynamic>> stopMusic() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.stopMusic(); |
|
|
final result = await _qqMusic.stopMusic(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
isPlaying.value = false; |
|
|
|
|
|
currentPosition.value = 0; |
|
|
|
|
|
stopPlaybackStateMonitor(); |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '停止失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '停止失败: ${e.toString()}'}; |
|
|
@ -109,6 +310,14 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> skipToNext() async { |
|
|
Future<Map<String, dynamic>> skipToNext() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.skipToNext(); |
|
|
final result = await _qqMusic.skipToNext(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
// 更新当前索引 |
|
|
|
|
|
if (currentIndex.value < playlist.length - 1) { |
|
|
|
|
|
currentIndex.value++; |
|
|
|
|
|
currentSong.value = playlist[currentIndex.value]; |
|
|
|
|
|
} |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '下一首失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '下一首失败: ${e.toString()}'}; |
|
|
@ -119,6 +328,14 @@ class QqMusocService extends GetxService { |
|
|
Future<Map<String, dynamic>> skipToPrevious() async { |
|
|
Future<Map<String, dynamic>> skipToPrevious() async { |
|
|
try { |
|
|
try { |
|
|
final result = await _qqMusic.skipToPrevious(); |
|
|
final result = await _qqMusic.skipToPrevious(); |
|
|
|
|
|
if (result['success'] == true) { |
|
|
|
|
|
// 更新当前索引 |
|
|
|
|
|
if (currentIndex.value > 0) { |
|
|
|
|
|
currentIndex.value--; |
|
|
|
|
|
currentSong.value = playlist[currentIndex.value]; |
|
|
|
|
|
} |
|
|
|
|
|
_notifyListeners(); |
|
|
|
|
|
} |
|
|
return result; |
|
|
return result; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
return {'success': false, 'error': '上一首失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '上一首失败: ${e.toString()}'}; |
|
|
@ -154,4 +371,14 @@ class QqMusocService extends GetxService { |
|
|
return {'success': false, 'error': '获取播放时间失败: ${e.toString()}'}; |
|
|
return {'success': false, 'error': '获取播放时间失败: ${e.toString()}'}; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 同步当前播放信息 - 统一接口 |
|
|
|
|
|
Future<Map<String, dynamic>> syncCurrentPlayInfo() async { |
|
|
|
|
|
try { |
|
|
|
|
|
final result = await _qqMusic.syncCurrentPlayInfo(); |
|
|
|
|
|
return result; |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
return {'success': false, 'error': '同步播放信息失败: ${e.toString()}'}; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|