RhythmBox/lib/providers/audio_player.dart

210 lines
5.6 KiB
Dart
Raw Normal View History

2024-08-26 18:22:47 +00:00
import 'dart:async';
2024-08-27 06:35:16 +00:00
import 'dart:convert';
2024-08-26 17:49:05 +00:00
import 'dart:math';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:rhythm_box/services/audio_player/state.dart';
import 'package:rhythm_box/services/local_track.dart';
2024-08-26 18:22:47 +00:00
import 'package:rhythm_box/services/server/sourced_track.dart';
2024-08-26 17:49:05 +00:00
import 'package:spotify/spotify.dart' hide Playlist;
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AudioPlayerProvider extends GetxController {
late final SharedPreferences _prefs;
2024-08-27 06:35:16 +00:00
RxBool isPlaying = false.obs;
2024-08-26 17:49:05 +00:00
Rx<AudioPlayerState> state = Rx(AudioPlayerState(
playing: false,
shuffled: false,
loopMode: PlaylistMode.none,
playlist: const Playlist([]),
collections: [],
));
2024-08-26 18:22:47 +00:00
List<StreamSubscription<Object>>? _subscriptions;
@override
void onInit() {
2024-08-27 06:35:16 +00:00
SharedPreferences.getInstance().then((ins) async {
2024-08-26 17:49:05 +00:00
_prefs = ins;
2024-08-27 06:35:16 +00:00
final res = await _readSavedState();
if (res != null) {
state.value = res;
} else {
state.value = AudioPlayerState(
loopMode: audioPlayer.loopMode,
playing: audioPlayer.isPlaying,
playlist: audioPlayer.playlist,
shuffled: audioPlayer.isShuffled,
collections: [],
);
}
2024-08-26 17:49:05 +00:00
});
2024-08-26 18:22:47 +00:00
_subscriptions = [
audioPlayer.playingStream.listen((playing) async {
state.value = state.value.copyWith(playing: playing);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}),
audioPlayer.loopModeStream.listen((loopMode) async {
state.value = state.value.copyWith(loopMode: loopMode);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}),
audioPlayer.shuffledStream.listen((shuffled) async {
state.value = state.value.copyWith(shuffled: shuffled);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}),
audioPlayer.playlistStream.listen((playlist) async {
state.value = state.value.copyWith(playlist: playlist);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}),
];
2024-08-27 06:35:16 +00:00
audioPlayer.playingStream.listen((playing) {
isPlaying.value = playing;
});
2024-08-26 18:22:47 +00:00
super.onInit();
}
@override
void dispose() {
if (_subscriptions != null) {
for (final subscription in _subscriptions!) {
subscription.cancel();
}
}
super.dispose();
2024-08-26 17:49:05 +00:00
}
2024-08-27 06:35:16 +00:00
Future<AudioPlayerState?> _readSavedState() async {
2024-08-27 06:48:31 +00:00
final data = _prefs.getString('player_state');
2024-08-27 06:35:16 +00:00
if (data == null) return null;
2024-08-26 17:49:05 +00:00
2024-08-27 06:35:16 +00:00
return AudioPlayerState.fromJson(jsonDecode(data));
}
2024-08-26 17:49:05 +00:00
2024-08-27 06:35:16 +00:00
Future<void> _updateSavedState() async {
final out = jsonEncode(state.value.toJson());
2024-08-27 06:48:31 +00:00
await _prefs.setString('player_state', out);
2024-08-26 17:49:05 +00:00
}
2024-08-26 18:22:47 +00:00
Future<void> addCollections(List<String> collectionIds) async {
state.value = state.value.copyWith(collections: [
...state.value.collections,
...collectionIds,
]);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}
Future<void> addCollection(String collectionId) async {
await addCollections([collectionId]);
}
Future<void> removeCollections(List<String> collectionIds) async {
state.value = state.value.copyWith(
collections: state.value.collections
.where((element) => !collectionIds.contains(element))
.toList(),
);
2024-08-27 06:35:16 +00:00
await _updateSavedState();
2024-08-26 18:22:47 +00:00
}
Future<void> removeCollection(String collectionId) async {
await removeCollections([collectionId]);
}
2024-08-26 17:49:05 +00:00
Future<void> load(
List<Track> tracks, {
int initialIndex = 0,
bool autoPlay = false,
}) async {
final medias = tracks.map((x) => RhythmMedia(x)).toList();
// Giving the initial track a boost so MediaKit won't skip
// because of timeout
final intendedActiveTrack = medias.elementAt(initialIndex);
if (intendedActiveTrack.track is! LocalTrack) {
2024-08-26 18:22:47 +00:00
await Get.find<SourcedTrackProvider>()
.fetch(RhythmMedia(intendedActiveTrack.track));
2024-08-26 17:49:05 +00:00
}
if (medias.isEmpty) return;
2024-08-26 18:22:47 +00:00
await removeCollections(state.value.collections);
2024-08-26 17:49:05 +00:00
await audioPlayer.openPlaylist(
medias.map((s) => s as Media).toList(),
initialIndex: initialIndex,
autoPlay: autoPlay,
);
}
Future<void> addTracksAtFirst(Iterable<Track> tracks) async {
if (state.value.tracks.length == 1) {
return addTracks(tracks);
}
for (int i = 0; i < tracks.length; i++) {
final track = tracks.elementAt(i);
await audioPlayer.addTrackAt(
RhythmMedia(track),
max(state.value.playlist.index, 0) + i + 1,
);
}
}
Future<void> addTrack(Track track) async {
await audioPlayer.addTrack(RhythmMedia(track));
}
Future<void> addTracks(Iterable<Track> tracks) async {
for (final track in tracks) {
await audioPlayer.addTrack(RhythmMedia(track));
}
}
Future<void> removeTrack(String trackId) async {
final index =
state.value.tracks.indexWhere((element) => element.id == trackId);
if (index == -1) return;
await audioPlayer.removeTrack(index);
}
Future<void> removeTracks(Iterable<String> trackIds) async {
for (final trackId in trackIds) {
await removeTrack(trackId);
}
}
Future<void> jumpToTrack(Track track) async {
final index = state.value.tracks
.toList()
.indexWhere((element) => element.id == track.id);
if (index == -1) return;
await audioPlayer.jumpTo(index);
}
Future<void> moveTrack(int oldIndex, int newIndex) async {
if (oldIndex == newIndex ||
newIndex < 0 ||
oldIndex < 0 ||
newIndex > state.value.tracks.length - 1 ||
oldIndex > state.value.tracks.length - 1) return;
await audioPlayer.moveTrack(oldIndex, newIndex);
}
Future<void> stop() async {
await audioPlayer.stop();
}
}