refactor: replace playAt with playTrack for safe element access and property based track looping

This commit is contained in:
Kingkor Roy Tirtho 2023-02-03 14:32:58 +06:00
parent 1d82bb0987
commit 24d7b5b851
9 changed files with 131 additions and 62 deletions

View File

@ -148,9 +148,7 @@ class UserLocalTracks extends HookConsumerWidget {
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
currentTrack.id != playback.state?.activeTrack.id) {
await playback.playAt(
tracks.indexWhere((s) => s.id == currentTrack?.id),
);
await playback.playTrack(currentTrack);
}
}

View File

@ -30,6 +30,7 @@ class PlayerActions extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final playlist = ref.watch(PlaylistQueueNotifier.provider);
final playlistNotifier = ref.watch(PlaylistQueueNotifier.provider.notifier);
final isLocalTrack = playlist?.activeTrack is LocalTrack;
final downloader = ref.watch(downloaderProvider);
final isInQueue = downloader.inQueue

View File

@ -146,19 +146,19 @@ class PlayerControls extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
PlatformIconButton(
tooltip: playlistNotifier.isShuffled
tooltip: playlist?.isShuffled == true
? "Unshuffle playlist"
: "Shuffle playlist",
icon: Icon(
SpotubeIcons.shuffle,
color: playlistNotifier.isShuffled
color: playlist?.isShuffled == true
? PlatformTheme.of(context).primaryColor
: null,
),
onPressed: playlist == null
? null
: () {
if (playlistNotifier.isShuffled) {
if (playlist.isShuffled == true) {
playlistNotifier.unshuffle();
} else {
playlistNotifier.shuffle();
@ -206,21 +206,28 @@ class PlayerControls extends HookConsumerWidget {
),
onPressed: playlist != null ? playlistNotifier.stop : null,
),
// PlatformIconButton(
// tooltip:
// !playlist.isLoop ? "Loop Track" : "Repeat playlist",
// icon: Icon(
// playlist.isLoop
// ? SpotubeIcons.repeatOne
// : SpotubeIcons.repeat,
// ),
// onPressed:
// playlist.track == null || playlist.playlist == null
// ? null
// : () {
// playlist.setIsLoop(!playlist.isLoop);
// },
// ),
PlatformIconButton(
tooltip: playlist?.isLooping != true
? "Loop Track"
: "Repeat playlist",
icon: Icon(
playlist?.isLooping == true
? SpotubeIcons.repeatOne
: SpotubeIcons.repeat,
color: playlist?.isLooping == true
? PlatformTheme.of(context).primaryColor
: null,
),
onPressed: playlist == null || playlist.isLoading
? null
: () {
if (playlist.isLooping == true) {
playlistNotifier.unloop();
} else {
playlistNotifier.loop();
}
},
),
],
),
const SizedBox(height: 5)

View File

@ -102,7 +102,7 @@ class PlayerQueue extends HookConsumerWidget {
if (playlist?.activeTrack.id == track.value.id) {
return;
}
await playlistNotifier.playAt(i);
await playlistNotifier.playTrack(currentTrack);
},
),
),

View File

@ -38,9 +38,7 @@ class AlbumPage extends HookConsumerWidget {
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
currentTrack.id != playlist?.activeTrack.id) {
await playback.playAt(
sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
);
await playback.playTrack(currentTrack);
}
}

View File

@ -309,9 +309,7 @@ class ArtistPage extends HookConsumerWidget {
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
currentTrack.id != playlist?.activeTrack.id) {
await playlistNotifier.playAt(
tracks.indexWhere((s) => s.id == currentTrack?.id),
);
await playlistNotifier.playTrack(currentTrack);
}
}

View File

@ -39,10 +39,7 @@ class PlaylistView extends HookConsumerWidget {
} else if (isPlaylistPlaying &&
currentTrack.id != null &&
currentTrack.id != playlistNotifier.state?.activeTrack.id) {
await playlistNotifier.loadAndPlay(
sortedTracks,
active: sortedTracks.indexWhere((s) => s.id == currentTrack?.id),
);
await playlistNotifier.playTrack(currentTrack);
}
}

View File

@ -21,6 +21,8 @@ final youtube = YoutubeExplode();
class PlaylistQueue {
final Set<Track> tracks;
final Set<Track> tempTracks;
final bool loop;
final int active;
Track get activeTrack => tracks.elementAt(active);
@ -28,6 +30,7 @@ class PlaylistQueue {
static Future<PlaylistQueue> fromJson(
Map<String, dynamic> json, UserPreferences preferences) async {
final List? tracks = json['tracks'];
final List? tempTracks = json['tempTracks'];
return PlaylistQueue(
Set.from(
await Future.wait(
@ -52,6 +55,28 @@ class PlaylistQueue {
),
),
active: json['active'],
tempTracks: Set.from(
await Future.wait(
tempTracks?.mapIndexed(
(i, e) async {
final jsonTrack =
Map.castFrom<dynamic, dynamic, String, dynamic>(e);
if (e["path"] != null) {
return LocalTrack.fromJson(jsonTrack);
} else if (i == json["active"] && !json.containsKey("path")) {
return await SpotubeTrack.fromFetchTrack(
Track.fromJson(jsonTrack),
preferences,
);
} else {
return Track.fromJson(jsonTrack);
}
},
) ??
[],
),
),
);
}
@ -69,24 +94,43 @@ class PlaylistQueue {
},
).toList(),
'active': active,
'tempTracks': tempTracks.map(
(e) {
if (e is SpotubeTrack) {
return e.toJson();
} else if (e is LocalTrack) {
return e.toJson();
} else {
return e.toJson();
}
},
).toList(),
};
}
bool get isLoading =>
activeTrack is LocalTrack ? false : activeTrack is! SpotubeTrack;
bool get isShuffled => tempTracks.isNotEmpty;
bool get isLooping => loop;
PlaylistQueue(
this.tracks, {
required this.tempTracks,
this.active = 0,
});
this.loop = false,
}) : assert(active < tracks.length && active >= 0, "Invalid active index");
PlaylistQueue copyWith({
Set<Track>? tracks,
Set<Track>? tempTracks,
int? active,
bool? loop,
}) {
return PlaylistQueue(
tracks ?? this.tracks,
active: active ?? this.active,
tempTracks: tempTracks ?? this.tempTracks,
loop: loop ?? this.loop,
);
}
}
@ -129,7 +173,15 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
linuxService?.player.updateProperties();
});
audioPlayer.onPlayerComplete.listen((event) => next());
audioPlayer.onPlayerComplete.listen((event) async {
if (!isLoaded) return;
if (state!.isLooping) {
await audioPlayer.seek(Duration.zero);
await audioPlayer.resume();
} else {
await next();
}
});
bool isPreSearching = false;
@ -158,15 +210,12 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
// properties
Set<Track> _tempTracks = {};
// getters
UserPreferences get preferences => ref.read(userPreferencesProvider);
BlackListNotifier get blacklist =>
ref.read(BlackListNotifier.provider.notifier);
bool get isLoaded => state != null;
bool get isShuffled => _tempTracks.isNotEmpty;
// redirectors
static bool get isPlaying => audioPlayer.state == PlayerState.playing;
@ -199,12 +248,12 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
}
void shuffle() {
if (isShuffled || !isLoaded) return;
_tempTracks = state?.tracks ?? _tempTracks;
if (!isLoaded || state!.isShuffled) return;
state = state?.copyWith(
tempTracks: state!.tracks,
tracks: {
state!.activeTrack,
..._tempTracks.toList()
...state!.tracks.toList()
..removeAt(state!.active)
..shuffle()
},
@ -213,12 +262,28 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
}
void unshuffle() {
if (!isShuffled || !isLoaded) return;
if (!isLoaded || !state!.isShuffled) return;
state = state?.copyWith(
tracks: _tempTracks,
active: _tempTracks.toList().indexOf(state!.activeTrack),
tracks: state!.tempTracks,
active: state!.tempTracks
.toList()
.indexWhere((element) => element.id == state!.activeTrack.id),
tempTracks: {},
);
}
void loop() {
if (!isLoaded || state!.isLooping) return;
state = state?.copyWith(
loop: true,
);
}
void unloop() {
if (!isLoaded || !state!.isLooping) return;
state = state?.copyWith(
loop: false,
);
_tempTracks = {};
}
Future<void> swapSibling(Video video) async {
@ -274,7 +339,15 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
state!.activeTrack,
preferences,
);
state = state!.copyWith(tracks: Set.from(tracks));
final tempTracks = state!.tempTracks
.map((e) =>
e.id == tracks[state!.active].id ? tracks[state!.active] : e)
.toList();
state = state!.copyWith(
tracks: Set.from(tracks),
tempTracks: Set.from(tempTracks),
);
}
mobileService?.addItem(mediaItem.copyWith(
@ -296,18 +369,19 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
}
}
Future<void> playAt(int index) async {
Future<void> playTrack(Track track) async {
if (!isLoaded) return;
state = PlaylistQueue(
state!.tracks,
active: index,
);
final active =
state!.tracks.toList().indexWhere((element) => element.id == track.id);
if (active == -1) return;
state = state!.copyWith(active: active);
return play();
}
void load(Iterable<Track> tracks, {int active = 0}) {
state = PlaylistQueue(
Set.from(blacklist.filter(tracks)),
tempTracks: {},
active: active,
);
}
@ -328,20 +402,18 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
Future<void> stop() async {
(mobileService)?.session?.setActive(false);
state = null;
_tempTracks = {};
return audioPlayer.stop();
}
Future<void> next() async {
if (!isLoaded) return;
if (state!.active == state!.tracks.length - 1) {
state = PlaylistQueue(
state!.tracks,
state = state!.copyWith(
active: 0,
);
} else {
state = PlaylistQueue(
state!.tracks,
state = state!.copyWith(
active: state!.active + 1,
);
}
@ -351,13 +423,11 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
Future<void> previous() async {
if (!isLoaded) return;
if (state!.active == 0) {
state = PlaylistQueue(
state!.tracks,
state = state!.copyWith(
active: state!.tracks.length - 1,
);
} else {
state = PlaylistQueue(
state!.tracks,
state = state!.copyWith(
active: state!.active - 1,
);
}
@ -373,8 +443,8 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
// utility
bool isPlayingPlaylist(Iterable<TrackSimple> playlist) {
if (!isLoaded || playlist.isEmpty) return false;
if (isShuffled) {
final trackIds = _tempTracks.map((track) => track.id!);
if (state!.isShuffled) {
final trackIds = state!.tempTracks.map((track) => track.id!);
return blacklist
.filter(playlist)
.every((track) => trackIds.contains(track.id!));

View File

@ -283,7 +283,7 @@ class _MprisMediaPlayer2Player extends DBusObject {
/// Gets value of property org.mpris.MediaPlayer2.Player.Shuffle
Future<DBusMethodResponse> getShuffle() async {
return DBusMethodSuccessResponse(
[DBusBoolean(playlistNotifier.isShuffled)]);
[DBusBoolean(playlist?.isShuffled ?? false)]);
}
/// Sets property org.mpris.MediaPlayer2.Player.Shuffle