ToneSnap_FSX_Flutter/lib/modules/sideb/controllers/music_player_controller.dart
fengshengxiong 063e3d7c91 1.添加通知栏媒体控制
2.下一首上一首增加防抖
3.集成fb
4.首页单曲增加下载
5.增加后台播放(iOS仍有问题)
6.修改系统状态栏颜色
2024-08-19 13:51:10 +08:00

407 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Author: fengshengxiong
// Date: 2024/5/30
// Description: 音乐播放器控制器
import 'dart:async';
import 'dart:io';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:tone_snap/components/base_easyloading.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/data/storage/offline_box.dart';
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/music_bar/music_bar.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';
import 'package:tone_snap/utils/obj_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 状态
var _isCompletedHandled = false;
/// 拖动开始时是否是播放状态
var _seekFrontPlaying = true;
/// 播放模式,默认列表循环
var playMode = MusicBox().getPlayMode().obs;
var _playModeIndex = 0;
/// 播放历史 videoId 集合
/// 随机播放切换到上一首时,从历史记录中取出上一个播放的 videoId
final List<String> _playHistory = [];
/// 播放列表
var playlist = <MusicModel>[].obs;
/// 当前播放的歌曲索引
var currentIndex = 0.obs;
/// 播放成功打点时的videoId防止同一首歌播放状态监听重复打点
String? _logVideoId;
@override
void onInit() {
super.onInit();
AudioUtil.configAudioSession();
}
@override
void onClose() {
MusicBar().hide();
_cancelListening();
_player.dispose();
super.onClose();
}
void playMusic(String? videoId, {List<MusicModel>? playList, bool isCurrentPlaylist = false}) {
if (ObjUtil.isEmpty(videoId)) return;
if (!isCurrentPlaylist) {
if (playList == null || playList.isEmpty) return;
_playHistory.clear();
playlist.value = playList.map((e) => e).toList();
}
MusicModel? model = playlist.firstWhereOrNull((e) => e.videoId == videoId);
currentIndex.value = model != null ? playlist.indexOf(model) : 0;
_startPlay();
}
MusicModel? getMusicModel() {
return playlist.isNotEmpty ? playlist[currentIndex.value] : null;
}
/// 播放歌曲
Future<void> _startPlay() async {
if (playlist.isNotEmpty) {
FirebaseAnalyticsManager.logPlayerBpv(getMusicModel()?.videoId, getMusicModel()?.title, getMusicModel()?.subtitle);
resetPlaybackStatus();
// Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
try {
final mediaItem = MediaItem(
id: ObjUtil.getStr(getMusicModel()?.videoId),
artist: ObjUtil.getStr(getMusicModel()?.subtitle),
title: ObjUtil.getStr(getMusicModel()?.title),
artUri: Uri.parse(ObjUtil.getStr(getMusicModel()?.coverUrl)),
);
DateTime startLoadDateTime = DateTime.now();
var model = OfflineBox().getList().firstWhereOrNull((e) => e.videoId == getMusicModel()?.videoId);
if (model != null && ObjUtil.isNotEmpty(model.localPath)) {
// 有下载
LogUtil.d('读取下载路径=${model.localPath}');
if (!await File(model.localPath!).exists()) {
BaseEasyLoading.toast('file does not exist');
FirebaseAnalyticsManager.logPlayerBFailAction('本地文件不存在');
return;
}
await _player.setFilePath(model.localPath!, tag: mediaItem);
} else {
// 无下载
var fileInfo = await MusicCacheManager.checkCache(getMusicModel()?.videoId);
if (fileInfo != null) {
// 有缓存
LogUtil.d('读取缓存路径=${fileInfo.file.path}');
// 如果有缓存,使用缓存文件
await _player.setFilePath(fileInfo.file.path, tag: mediaItem);
} else {
// 无缓存
final url = await _getMusicUrl();
if (ObjUtil.isEmpty(url)) {
return;
}
await _player.setUrl(url!, tag: mediaItem);
// 同时启动缓存下载
_cacheManager.downloadFile(url, key: MusicCacheManager.getCacheKey(getMusicModel()!.videoId!)).then((fileInfo) {
LogUtil.d('缓存下载路径=${fileInfo.file.path}');
});
}
}
_cancelListening();
_addListening();
_player.play();
DateTime endLoadDateTime = DateTime.now();
// 获取毫秒差
int millisecondsDifference = endLoadDateTime.difference(startLoadDateTime).inMilliseconds;
FirebaseAnalyticsManager.logPlayerBDelayAction(
getMusicModel()?.videoId,
getMusicModel()?.title,
getMusicModel()?.subtitle,
'${millisecondsDifference}ms',
);
} catch (e) {
LogUtil.e('Play failed: $e');
BaseEasyLoading.toast('Play failed');
FirebaseAnalyticsManager.logPlayerBFailAction(e.toString());
}
}
}
/// 获取当前歌曲的播放 url
Future<String?> _getMusicUrl() async {
PlayerModel? model = await MusicApi.player(videoId: playlist[currentIndex.value].videoId);
if (model != null && model.streamingData != null) {
var formats = model.streamingData?.formats;
if (formats != null && playlist.isNotEmpty) {
return formats[0].url;
}
}
return null;
}
/// 监听
void _addListening() {
_player.playbackEventStream.listen((event) {},
onError: (Object e, StackTrace stackTrace) {
LogUtil.e('A stream error occurred: $e');
});
_durationSubscription = _player.durationStream.listen((duration) {
totalDuration.value = duration ?? Duration.zero;
});
_positionSubscription = _player.positionStream.listen((position) {
int comparison = position.compareTo(totalDuration.value);
if (comparison > 0) {
positionDuration.value = totalDuration.value;
} else {
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(manualSwitch: false);
}
break;
}
if (isPlaying.value) {
if (_logVideoId != getMusicModel()?.videoId) {
_logVideoId = getMusicModel()?.videoId;
FirebaseAnalyticsManager.logPlayerBSuccessAction(
getMusicModel()?.videoId,
getMusicModel()?.title,
getMusicModel()?.subtitle,
);
}
}
});
}
/// 取消监听
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 (value == 0) {
if (processingState.value == ProcessingState.ready) {
_seekFrontPlaying = _player.playing;
if (_player.playing) {
_player.pause();
}
}
} else {
// 拖动前如果是播放状态,拖动完成后恢复播放
if (_seekFrontPlaying) {
_player.play();
}
}
}
/// 播放/暂停
void playPause() {
if (_player.playing) {
pause();
} else {
play();
}
}
/// 播放
void play() {
if (processingState.value == ProcessingState.ready) {
_player.play();
}
}
/// 暂停
void pause() {
_player.pause();
}
/// 上一首
Future<void> previousTrack() async {
// 通过防抖防止在音频源之间快速切换会导致PlatformException
EasyDebounce.debounce(
'cutTheSong',
const Duration(milliseconds: 300),
() {
if (playlist.length > 1) {
switch(playMode.value) {
case PlayMode.listLoop:
currentIndex.value = (currentIndex.value - 1 + playlist.length) % playlist.length;
_startPlay();
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.value = playlist.indexOf(model);
_playHistory.remove(history);
historyExist = true;
break;
} else {
_playHistory.remove(history);
}
}
if (!historyExist) {
currentIndex.value = _getRandomNumber();
}
_startPlay();
break;
case PlayMode.singleCycle:
currentIndex.value = (currentIndex.value - 1 + playlist.length) % playlist.length;
_startPlay();
break;
}
}
},
);
}
/// 下一首
Future<void> nextTrack({bool manualSwitch = true}) async {
// 通过防抖防止在音频源之间快速切换会导致PlatformException
EasyDebounce.debounce(
'cutTheSong',
const Duration(milliseconds: 300),
() {
if (playlist.length > 1) {
switch(playMode.value) {
case PlayMode.listLoop:
currentIndex.value = (currentIndex.value + 1) % playlist.length;
_startPlay();
break;
case PlayMode.random:
// 记录当前播放的索引
_playHistory.add(getMusicModel()!.videoId!);
currentIndex.value = _getRandomNumber();
_startPlay();
break;
case PlayMode.singleCycle:
if (manualSwitch) {
currentIndex.value = (currentIndex.value + 1) % playlist.length;
_startPlay();
}
break;
}
}
},
);
}
/// 在列表范围内生成一个不包括当前索引的随机数
int _getRandomNumber() {
return NumUtil.getRandomNumberExcludingCurrent(0, playlist.length, currentIndex.value);
}
/// 切换播放模式
void switchPlayMode() {
if (_playModeIndex >= PlayMode.values.length - 1) {
_playModeIndex = 0;
} else {
_playModeIndex++;
}
playMode.value = PlayMode.values[_playModeIndex];
MusicBox().putPlayMode(playMode.value);
if (playMode.value == PlayMode.listLoop) {
BaseEasyLoading.toast('List loop');
}
if (playMode.value == PlayMode.random) {
BaseEasyLoading.toast('Shuffle Playback');
}
if (playMode.value == PlayMode.singleCycle) {
BaseEasyLoading.toast('Single cycle');
}
}
/// 重置播放状态
Future<void> resetPlaybackStatus() async {
await _player.stop();
_player.seek(Duration.zero);
totalDuration.value = Duration.zero;
positionDuration.value = Duration.zero;
bufferedDuration.value = Duration.zero;
}
}