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.
470 lines
13 KiB
470 lines
13 KiB
import 'dart:async';
|
|
import 'package:get/get.dart';
|
|
import 'package:qq_music/qq_music.dart';
|
|
import '../models/song_model.dart';
|
|
|
|
/// QQ音乐服务适配器 - 全局状态管理
|
|
class QqMusocService extends GetxService {
|
|
static QqMusocService get to => Get.find<QqMusocService>();
|
|
|
|
var isInitialized = false;
|
|
// 音乐服务实例
|
|
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 = [];
|
|
|
|
/// 初始化插件并尝试自动登录
|
|
Future<void> initializePlugin() async {
|
|
try {
|
|
// 先触发插件注册
|
|
// await _qqMusic.getPlatformVersion();
|
|
|
|
// 尝试自动登录
|
|
final result = await _qqMusic.login();
|
|
if (result['success'] == true) {
|
|
print('QQ音乐自动登录成功: ${result['openId']}');
|
|
syncCurrentPlayInfo();
|
|
} else {
|
|
print('QQ音乐自动登录失败: ${result['error']}');
|
|
}
|
|
} catch (e) {
|
|
print('QQ音乐插件初始化失败: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
print('QQ音乐 全局音乐服务初始化');
|
|
// 立即初始化插件
|
|
initializePlugin();
|
|
}
|
|
|
|
@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();
|
|
|
|
// 改为每2-3秒同步一次,减少频率
|
|
_stateMonitorTimer = Timer.periodic(const Duration(seconds: 2), (timer) {
|
|
_syncPlaybackInfo();
|
|
});
|
|
|
|
print('🎵 [QQMusicService] 全局状态监控已启动');
|
|
}
|
|
|
|
/// 停止播放状态监控
|
|
void stopPlaybackStateMonitor() {
|
|
_stateMonitorTimer?.cancel();
|
|
_stateMonitorTimer = null;
|
|
print('⏹️ [QQMusicService] 全局状态监控已停止');
|
|
}
|
|
|
|
/// 统一同步播放信息
|
|
bool _isSyncing = false;
|
|
|
|
Future<void> _syncPlaybackInfo() async {
|
|
if (_isSyncing) {
|
|
print('🔄 [QQMusicService] 上次同步未完成,跳过本次');
|
|
return;
|
|
}
|
|
|
|
_isSyncing = true;
|
|
try {
|
|
final result = await _qqMusic
|
|
.syncCurrentPlayInfo()
|
|
.timeout(const Duration(milliseconds: 500)); // 500ms超时
|
|
|
|
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();
|
|
}
|
|
} on TimeoutException {
|
|
print('⏰ [QQMusicService] 同步超时,跳过本次更新');
|
|
} catch (e) {
|
|
print('💥 [QQMusicService] 全局同步异常: $e');
|
|
stopPlaybackStateMonitor();
|
|
} finally {
|
|
_isSyncing = false;
|
|
}
|
|
}
|
|
|
|
// 后台执行的同步方法
|
|
static Future<Map<String, dynamic>> _performSync(dynamic _) async {
|
|
final qqMusic = QqMusic();
|
|
return await qqMusic.syncCurrentPlayInfo();
|
|
}
|
|
|
|
/// 格式化时间
|
|
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音乐
|
|
Future<Map<String, dynamic>> login() async {
|
|
try {
|
|
final result = await _qqMusic.login();
|
|
if (result['success'] == true) {
|
|
isInitialized = true;
|
|
return result;
|
|
} else {
|
|
return {'success': false, 'error': result['error'] ?? '登录失败'};
|
|
}
|
|
} catch (e) {
|
|
return {'success': false, 'error': '登录异常: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 搜索歌曲
|
|
Future<Map<String, dynamic>> searchSongs({
|
|
required String keyword,
|
|
int searchType = 0,
|
|
}) async {
|
|
try {
|
|
final result = await _qqMusic.searchSongs(
|
|
keyword: keyword,
|
|
searchType: searchType,
|
|
);
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '搜索失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 播放歌曲列表 - 更新全局状态
|
|
Future<Map<String, dynamic>> playSongs({
|
|
required List<String> songMids,
|
|
int index = 0,
|
|
List<SongModel>? songList,
|
|
SongModel? currentSongModel,
|
|
}) async {
|
|
try {
|
|
final result = await _qqMusic.playSongs(
|
|
songMids: songMids,
|
|
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;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 播放音乐
|
|
Future<Map<String, dynamic>> playMusic() async {
|
|
try {
|
|
final result = await _qqMusic.playMusic();
|
|
if (result['success'] == true) {
|
|
isPlaying.value = true;
|
|
startPlaybackStateMonitor();
|
|
_notifyListeners();
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '播放失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 暂停音乐
|
|
Future<Map<String, dynamic>> pauseMusic() async {
|
|
try {
|
|
final result = await _qqMusic.pauseMusic();
|
|
if (result['success'] == true) {
|
|
isPlaying.value = false;
|
|
_notifyListeners();
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '暂停失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 继续播放
|
|
Future<Map<String, dynamic>> resumeMusic() async {
|
|
try {
|
|
final result = await _qqMusic.resumeMusic();
|
|
if (result['success'] == true) {
|
|
isPlaying.value = true;
|
|
startPlaybackStateMonitor();
|
|
_notifyListeners();
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '继续播放失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 停止音乐
|
|
Future<Map<String, dynamic>> stopMusic() async {
|
|
try {
|
|
final result = await _qqMusic.stopMusic();
|
|
if (result['success'] == true) {
|
|
isPlaying.value = false;
|
|
currentPosition.value = 0;
|
|
stopPlaybackStateMonitor();
|
|
_notifyListeners();
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '停止失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 下一首
|
|
Future<Map<String, dynamic>> skipToNext() async {
|
|
try {
|
|
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;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '下一首失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 上一首
|
|
Future<Map<String, dynamic>> skipToPrevious() async {
|
|
try {
|
|
final result = await _qqMusic.skipToPrevious();
|
|
if (result['success'] == true) {
|
|
// 更新当前索引
|
|
if (currentIndex.value > 0) {
|
|
currentIndex.value--;
|
|
currentSong.value = playlist[currentIndex.value];
|
|
}
|
|
_notifyListeners();
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '上一首失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 获取播放状态
|
|
Future<Map<String, dynamic>> getPlaybackState() async {
|
|
try {
|
|
final result = await _qqMusic.getPlaybackState();
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '获取播放状态失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 获取当前播放歌曲
|
|
Future<Map<String, dynamic>> getCurrentSong() async {
|
|
try {
|
|
final result = await _qqMusic.getCurrentSong();
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '获取当前歌曲失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 获取播放时间
|
|
Future<Map<String, dynamic>> getPlayTime() async {
|
|
try {
|
|
final result = await _qqMusic.getPlayTime();
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '获取播放时间失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
|
|
/// 同步当前播放信息 - 统一接口
|
|
Future<Map<String, dynamic>> syncCurrentPlayInfo() async {
|
|
try {
|
|
final result = await _qqMusic.syncCurrentPlayInfo();
|
|
|
|
// 如果同步成功,更新全局状态
|
|
if (result['success'] == true && result['data'] != null) {
|
|
final data = result['data'];
|
|
|
|
// 更新播放状态
|
|
final newIsPlaying = data['isPlaying'] ?? false;
|
|
if (isPlaying.value != newIsPlaying) {
|
|
isPlaying.value = newIsPlaying;
|
|
}
|
|
|
|
// 更新播放时间(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) {
|
|
currentSong.value = newSong;
|
|
}
|
|
}
|
|
|
|
// 通知所有监听器
|
|
_notifyListeners();
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
return {'success': false, 'error': '同步播放信息失败: ${e.toString()}'};
|
|
}
|
|
}
|
|
}
|
|
|