333 lines
10 KiB
Dart
333 lines
10 KiB
Dart
// Author: fengshengxiong
|
|
// Date: 2024/5/30
|
|
// Description: 音乐播放器控制器
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:just_audio/just_audio.dart';
|
|
import 'package:tone_snap/components/base_easyloading.dart';
|
|
import 'package:tone_snap/components/music_bar.dart';
|
|
import 'package:tone_snap/data/api/music_api.dart';
|
|
import 'package:tone_snap/data/cache/music_cache_manager.dart';
|
|
import 'package:tone_snap/data/enum/play_mode.dart';
|
|
import 'package:tone_snap/data/models/music_model.dart';
|
|
import 'package:tone_snap/data/models/player_model.dart';
|
|
import 'package:tone_snap/data/storage/music_box.dart';
|
|
import 'package:tone_snap/routes/app_routes.dart';
|
|
import 'package:tone_snap/utils/audio_util.dart';
|
|
import 'package:tone_snap/utils/log_util.dart';
|
|
import 'package:tone_snap/utils/num_util.dart';
|
|
|
|
class MusicPlayerController extends GetxController {
|
|
static MusicPlayerController get to => Get.put(MusicPlayerController(), permanent: true);
|
|
final _player = AudioPlayer();
|
|
StreamSubscription<Duration>? _bufferedSubscription;
|
|
StreamSubscription<Duration?>? _durationSubscription;
|
|
StreamSubscription<Duration>? _positionSubscription;
|
|
StreamSubscription<PlayerState>? _playerStateSubscription;
|
|
|
|
/// 总时长、已播放的时长、缓冲时长
|
|
var totalDuration = Duration.zero.obs;
|
|
var positionDuration = Duration.zero.obs;
|
|
var bufferedDuration = Duration.zero.obs;
|
|
|
|
/// 缓存管理器
|
|
// final _cacheManager = DefaultCacheManager();
|
|
final _cacheManager = MusicCacheManager.instance;
|
|
|
|
/// 是否正在播放
|
|
var isPlaying = false.obs;
|
|
|
|
/// 播放器处理状态
|
|
var processingState = ProcessingState.idle.obs;
|
|
|
|
/// _player.seek(Duration.zero) 时,这可能会导致播放状态再次变为 completed。
|
|
/// 为了避免这种情况,在处理 completed 状态时添加一个标志位,确保在处理完成状态时不会重复触发
|
|
/// 是否已经处理过 completed 状态
|
|
bool _isCompletedHandled = false;
|
|
|
|
/// 拖动开始时是否是播放状态
|
|
var seekFrontPlaying = true;
|
|
|
|
/// 播放模式,默认列表循环
|
|
var playMode = MusicBox().getPlayMode().obs;
|
|
var _playModeIndex = 0;
|
|
|
|
/// 播放列表
|
|
var playList = <MusicModel>[].obs;
|
|
|
|
/// 播放历史 videoId 集合
|
|
/// 随机播放切换到上一首时,从历史记录中取出上一个播放的 videoId
|
|
final List<String> _playHistory = [];
|
|
|
|
/// 播放的歌曲模型
|
|
var musicModel = MusicModel().obs;
|
|
|
|
/// 当前播放的歌曲索引
|
|
int _currentIndex = 0;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
AudioUtil.configAudioSession();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
MusicBar().hide();
|
|
_cancelListening();
|
|
_player.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
/// 播放指定索引的歌曲
|
|
void playMusicSpecifyIndex(int index) {
|
|
_currentIndex = index;
|
|
playMusic();
|
|
}
|
|
|
|
/// 设置播放列表
|
|
void setPlayList(String videoId, List<MusicModel> list) {
|
|
_resetPlaybackStatus();
|
|
playList.value = list;
|
|
_playHistory.clear();
|
|
|
|
MusicModel? musicModel = playList.firstWhereOrNull((e) => e.videoId == videoId);
|
|
int index = musicModel != null ? playList.indexOf(musicModel) : 0;
|
|
_currentIndex = index;
|
|
}
|
|
|
|
/// 播放歌曲
|
|
Future<void> playMusic() async {
|
|
Get.currentRoute != AppRoutes.playPage ? MusicBar().show() : MusicBar().hide();
|
|
_resetPlaybackStatus();
|
|
if (playList.isNotEmpty) {
|
|
musicModel.update((e) {
|
|
e?.thumbnail = playList[_currentIndex].thumbnail;
|
|
e?.title = playList[_currentIndex].title;
|
|
e?.subTitle = playList[_currentIndex].subTitle;
|
|
});
|
|
if (playList[_currentIndex].url == null) {
|
|
await _getMusicUrl();
|
|
}
|
|
if (playList[_currentIndex].url == null) {
|
|
BaseEasyLoading.toast('Resource not obtained');
|
|
// nextTrack();
|
|
return;
|
|
}
|
|
musicModel.value = playList[_currentIndex].copyWith();
|
|
await _initializePlayer();
|
|
}
|
|
}
|
|
|
|
/// 获取当前歌曲的播放 url
|
|
Future<void> _getMusicUrl() async {
|
|
PlayerModel? model = await MusicApi.player(videoId: playList[_currentIndex].videoId);
|
|
if (model != null && model.streamingData != null) {
|
|
var formats = model.streamingData?.formats;
|
|
if (formats != null && playList.isNotEmpty) {
|
|
playList[_currentIndex].url = formats[0].url;
|
|
}
|
|
}
|
|
if (model != null && model.videoDetails?.thumbnail?.thumbnails != null) {
|
|
|
|
}
|
|
}
|
|
|
|
/// 设置 url 和监听器
|
|
Future<void> _initializePlayer() async {
|
|
try {
|
|
// 检查是否有缓存
|
|
FileInfo? fileInfo = await MusicCacheManager.checkCache(musicModel.value.videoId!);
|
|
if (fileInfo != null) {
|
|
LogUtil.d('读取缓存=${fileInfo.file.path}');
|
|
|
|
// 如果有缓存,使用缓存文件
|
|
await _player.setFilePath(fileInfo.file.path);
|
|
} else {
|
|
// 如果没有缓存,通过 url 加载音源并开始播放,
|
|
await _player.setUrl(musicModel.value.url!);
|
|
|
|
// 同时启动缓存下载
|
|
_cacheManager.downloadFile(musicModel.value.url!, key: MusicCacheManager.getCacheKey(musicModel.value.videoId!)).then((fileInfo) {
|
|
LogUtil.d('缓存下载成功=${fileInfo.file.path}');
|
|
});
|
|
}
|
|
_cancelListening();
|
|
_addListening();
|
|
_player.play();
|
|
} catch (e) {
|
|
LogUtil.e('Error initializing player: $e');
|
|
BaseEasyLoading.toast('Error initializing player');
|
|
}
|
|
}
|
|
|
|
/// 监听
|
|
void _addListening() {
|
|
_durationSubscription = _player.durationStream.listen((duration) {
|
|
totalDuration.value = duration ?? Duration.zero;
|
|
});
|
|
|
|
_positionSubscription = _player.positionStream.listen((position) {
|
|
positionDuration.value = position;
|
|
});
|
|
|
|
_bufferedSubscription = _player.bufferedPositionStream.listen((bufferedPosition) {
|
|
int comparison = bufferedPosition.compareTo(totalDuration.value);
|
|
if (comparison > 0) {
|
|
bufferedDuration.value = totalDuration.value;
|
|
} else {
|
|
bufferedDuration.value = bufferedPosition;
|
|
}
|
|
});
|
|
|
|
_playerStateSubscription = _player.playerStateStream.listen((playerState) {
|
|
isPlaying.value = _player.playing;
|
|
processingState.value = playerState.processingState;
|
|
switch (playerState.processingState) {
|
|
case ProcessingState.idle:
|
|
break;
|
|
case ProcessingState.loading:
|
|
break;
|
|
case ProcessingState.buffering:
|
|
break;
|
|
case ProcessingState.ready:
|
|
_isCompletedHandled = false;
|
|
break;
|
|
case ProcessingState.completed:
|
|
if (!_isCompletedHandled) {
|
|
_isCompletedHandled = true;
|
|
_player.seek(Duration.zero);
|
|
nextTrack();
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 取消监听
|
|
void _cancelListening() {
|
|
_bufferedSubscription?.cancel();
|
|
_durationSubscription?.cancel();
|
|
_positionSubscription?.cancel();
|
|
_playerStateSubscription?.cancel();
|
|
}
|
|
|
|
/// 将播放器的播放位置设置为指定的时间
|
|
void seekToPosition(double value) {
|
|
if (processingState.value == ProcessingState.ready) {
|
|
positionDuration.value = Duration(seconds: value.toInt());
|
|
_player.seek(positionDuration.value);
|
|
}
|
|
}
|
|
|
|
/// 开始/结束拖动进度条
|
|
Future<void> seekStartEnd(int value) async {
|
|
if (processingState.value == ProcessingState.ready) {
|
|
if (value == 0) {
|
|
seekFrontPlaying = _player.playing;
|
|
if (_player.playing) {
|
|
_player.pause();
|
|
}
|
|
} else {
|
|
// 拖动前如果是播放状态,拖动完成后恢复播放
|
|
if (seekFrontPlaying) {
|
|
_player.play();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 播放/暂停
|
|
Future<void> playPause() async {
|
|
if (_player.playing) {
|
|
_player.pause();
|
|
} else {
|
|
if (processingState.value == ProcessingState.ready) {
|
|
_player.play();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 上一首
|
|
void previousTrack() {
|
|
if (playList.isNotEmpty) {
|
|
switch(playMode.value) {
|
|
case PlayMode.listLoop:
|
|
_currentIndex = (_currentIndex - 1 + playList.length) % playList.length;
|
|
playMusic();
|
|
break;
|
|
case PlayMode.random:
|
|
bool historyExist = false;
|
|
for (var i = _playHistory.length - 1; i >= 0; --i) {
|
|
var history = _playHistory[i];
|
|
MusicModel? model = playList.firstWhereOrNull((e) => e.videoId == history);
|
|
if (model != null) {
|
|
_currentIndex = playList.indexOf(model);
|
|
_playHistory.remove(history);
|
|
historyExist = true;
|
|
break;
|
|
} else {
|
|
_playHistory.remove(history);
|
|
}
|
|
}
|
|
if (!historyExist) {
|
|
_getRandomNumber();
|
|
}
|
|
playMusic();
|
|
break;
|
|
case PlayMode.singleCycle:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 下一首
|
|
void nextTrack() {
|
|
if (playList.isNotEmpty) {
|
|
switch(playMode.value) {
|
|
case PlayMode.listLoop:
|
|
_currentIndex = (_currentIndex + 1) % playList.length;
|
|
playMusic();
|
|
break;
|
|
case PlayMode.random:
|
|
// 记录当前播放的索引
|
|
_playHistory.add(musicModel.value.videoId!);
|
|
_getRandomNumber();
|
|
playMusic();
|
|
break;
|
|
case PlayMode.singleCycle:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 在列表范围内生成一个不包括当前索引的随机数
|
|
void _getRandomNumber() {
|
|
_currentIndex = NumUtil.getRandomNumberExcludingCurrent(0, playList.length, _currentIndex);
|
|
}
|
|
|
|
/// 切换播放模式
|
|
void switchPlayMode() {
|
|
if (_playModeIndex == PlayMode.values.length - 1) {
|
|
_playModeIndex = 0;
|
|
} else {
|
|
_playModeIndex++;
|
|
}
|
|
playMode.value = PlayMode.values[_playModeIndex];
|
|
MusicBox().putPlayMode(playMode.value);
|
|
}
|
|
|
|
/// 重置播放状态
|
|
void _resetPlaybackStatus() {
|
|
_player.stop();
|
|
_player.seek(Duration.zero);
|
|
totalDuration.value = Duration.zero;
|
|
positionDuration.value = Duration.zero;
|
|
bufferedDuration.value = Duration.zero;
|
|
musicModel.update((model) => model = MusicModel());
|
|
}
|
|
}
|