From a83dd64476486bdad88b0c0f2afb56e2b90e7f0f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 24 Jun 2024 20:52:40 +0600 Subject: [PATCH] refactor: replace all instances of proxy playlist --- lib/collections/intents.dart | 8 +- lib/components/track_tile/track_options.dart | 14 +- lib/components/track_tile/track_tile.dart | 11 +- .../sections/body/track_view_body.dart | 6 +- .../sections/body/track_view_options.dart | 4 +- .../sections/header/header_actions.dart | 8 +- .../sections/header/header_buttons.dart | 6 +- .../configurators/use_endless_playback.dart | 16 +- lib/main.dart | 4 +- lib/models/connect/connect.dart | 2 +- lib/models/connect/ws_event.dart | 6 +- lib/models/database/database.g.dart | 128 ++++++----- .../database/tables/audio_player_state.dart | 2 +- lib/modules/album/album_card.dart | 8 +- lib/modules/player/player.dart | 21 +- lib/modules/player/player_actions.dart | 8 +- lib/modules/player/player_controls.dart | 24 +- lib/modules/player/player_overlay.dart | 16 +- lib/modules/player/player_queue.dart | 15 +- lib/modules/player/player_track_details.dart | 4 +- lib/modules/player/sibling_tracks_sheet.dart | 17 +- lib/modules/playlist/playlist_card.dart | 10 +- lib/modules/root/bottom_player.dart | 6 +- lib/pages/artist/section/header.dart | 2 +- lib/pages/artist/section/top_tracks.dart | 6 +- lib/pages/library/local_folder.dart | 8 +- .../playlist_generate_result.dart | 13 +- lib/pages/lyrics/lyrics.dart | 6 +- lib/pages/lyrics/mini_lyrics.dart | 11 +- lib/pages/lyrics/plain_lyrics.dart | 4 +- lib/pages/lyrics/synced_lyrics.dart | 6 +- lib/pages/root/root_app.dart | 8 +- lib/pages/search/sections/tracks.dart | 6 +- lib/pages/track/track.dart | 6 +- lib/provider/audio_player/audio_player.dart | 137 +++++++++-- .../audio_player_streams.dart} | 87 ++++--- lib/provider/audio_player/state.dart | 66 +++++- lib/provider/connect/connect.dart | 13 +- lib/provider/discord_provider.dart | 4 +- .../proxy_playlist/proxy_playlist.dart | 101 -------- .../proxy_playlist_provider.dart | 215 ------------------ lib/provider/server/active_sourced_track.dart | 4 +- lib/provider/server/routes/connect.dart | 28 +-- lib/provider/server/routes/playback.dart | 6 +- lib/provider/server/sourced_track.dart | 7 +- .../skip_segments.dart | 0 lib/provider/tray_manager/tray_menu.dart | 10 +- .../user_preferences_provider.dart | 5 +- .../audio_services/audio_services.dart | 4 +- .../audio_services/mobile_audio_service.dart | 20 +- .../audio_services/windows_audio_service.dart | 12 +- 51 files changed, 515 insertions(+), 624 deletions(-) rename lib/provider/{proxy_playlist/player_listeners.dart => audio_player/audio_player_streams.dart} (51%) delete mode 100644 lib/provider/proxy_playlist/proxy_playlist.dart delete mode 100644 lib/provider/proxy_playlist/proxy_playlist_provider.dart rename lib/provider/{proxy_playlist => skip_segments}/skip_segments.dart (100%) diff --git a/lib/collections/intents.dart b/lib/collections/intents.dart index 6d6e643e..1a44a846 100644 --- a/lib/collections/intents.dart +++ b/lib/collections/intents.dart @@ -11,7 +11,7 @@ import 'package:spotube/pages/home/home.dart'; import 'package:spotube/pages/library/library.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; import 'package:spotube/pages/search/search.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; @@ -96,8 +96,8 @@ class SeekIntent extends Intent { class SeekAction extends Action { @override invoke(intent) async { - final playlist = intent.ref.read(proxyPlaylistProvider); - if (playlist.isFetching) { + final playlist = intent.ref.read(audioPlayerProvider.notifier); + if (playlist.isFetching()) { DirectionalFocusAction().invoke( DirectionalFocusIntent( intent.forward ? TraversalDirection.right : TraversalDirection.left, @@ -105,7 +105,7 @@ class SeekAction extends Action { ); return null; } - final position = (await audioPlayer.position ?? Duration.zero).inSeconds; + final position = audioPlayer.position.inSeconds; await audioPlayer.seek( Duration( seconds: intent.forward ? position + 5 : position - 5, diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index d54a0c15..c6cfdd35 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -24,7 +24,7 @@ import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/spotify_provider.dart'; @@ -96,8 +96,8 @@ class TrackOptions extends HookConsumerWidget { WidgetRef ref, Track track, ) async { - final playback = ref.read(proxyPlaylistProvider.notifier); - final playlist = ref.read(proxyPlaylistProvider); + final playback = ref.read(audioPlayerProvider.notifier); + final playlist = ref.read(audioPlayerProvider); final spotify = ref.read(spotifyProvider); final query = "${track.name} Radio"; final pages = @@ -160,8 +160,8 @@ class TrackOptions extends HookConsumerWidget { final router = GoRouter.of(context); final ThemeData(:colorScheme) = Theme.of(context); - final playlist = ref.watch(proxyPlaylistProvider); - final playback = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playback = ref.watch(audioPlayerProvider.notifier); final auth = ref.watch(authenticationProvider); ref.watch(downloadManagerProvider); final downloadManager = ref.watch(downloadManagerProvider.notifier); @@ -364,7 +364,7 @@ class TrackOptions extends HookConsumerWidget { : context.l10n.save_as_favorite, ), ), - if (auth != null && !isLocalTrack) ...[ + if (auth.asData?.value != null && !isLocalTrack) ...[ PopSheetEntry( value: TrackOptionValue.startRadio, leading: const Icon(SpotubeIcons.radio), @@ -376,7 +376,7 @@ class TrackOptions extends HookConsumerWidget { title: Text(context.l10n.add_to_playlist), ), ], - if (userPlaylist && auth != null && !isLocalTrack) + if (userPlaylist && auth.asData?.value != null && !isLocalTrack) PopSheetEntry( value: TrackOptionValue.removeFromPlaylist, leading: const Icon(SpotubeIcons.removeFilled), diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index e2e7e293..cdc18d9b 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -17,8 +17,9 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; class TrackTile extends HookConsumerWidget { /// [index] will not be shown if null @@ -30,7 +31,7 @@ class TrackTile extends HookConsumerWidget { final VoidCallback? onLongPress; final bool userPlaylist; final String? playlistId; - final ProxyPlaylist playlist; + final AudioPlayerState playlist; final List? leadingActions; @@ -160,7 +161,11 @@ class TrackTile extends HookConsumerWidget { child: Skeleton.ignore( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: (isPlaying && playlist.isFetching) || + child: (isPlaying && + ref + .watch(audioPlayerProvider + .notifier) + .isFetching()) || isLoading.value ? const SizedBox( width: 26, diff --git a/lib/components/tracks_view/sections/body/track_view_body.dart b/lib/components/tracks_view/sections/body/track_view_body.dart index 0c3cca4e..a6089cc3 100644 --- a/lib/components/tracks_view/sections/body/track_view_body.dart +++ b/lib/components/tracks_view/sections/body/track_view_body.dart @@ -18,7 +18,7 @@ import 'package:spotube/components/tracks_view/track_view_provider.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -27,8 +27,8 @@ class TrackViewBodySection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.watch(playbackHistoryProvider.notifier); final props = InheritedTrackView.of(context); final trackViewState = ref.watch(trackViewProvider(props.tracks)); diff --git a/lib/components/tracks_view/sections/body/track_view_options.dart b/lib/components/tracks_view/sections/body/track_view_options.dart index 1accba34..98ddca25 100644 --- a/lib/components/tracks_view/sections/body/track_view_options.dart +++ b/lib/components/tracks_view/sections/body/track_view_options.dart @@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; class TrackViewBodyOptions extends HookConsumerWidget { @@ -24,7 +24,7 @@ class TrackViewBodyOptions extends HookConsumerWidget { ref.watch(downloadManagerProvider); final downloader = ref.watch(downloadManagerProvider.notifier); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.watch(playbackHistoryProvider.notifier); final audioSource = ref.watch(userPreferencesProvider.select((s) => s.audioSource)); diff --git a/lib/components/tracks_view/sections/header/header_actions.dart b/lib/components/tracks_view/sections/header/header_actions.dart index f20cd553..6769ed52 100644 --- a/lib/components/tracks_view/sections/header/header_actions.dart +++ b/lib/components/tracks_view/sections/header/header_actions.dart @@ -11,7 +11,7 @@ import 'package:spotube/components/tracks_view/track_view_props.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; class TrackViewHeaderActions extends HookConsumerWidget { const TrackViewHeaderActions({super.key}); @@ -20,8 +20,8 @@ class TrackViewHeaderActions extends HookConsumerWidget { Widget build(BuildContext context, ref) { final props = InheritedTrackView.of(context); - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.watch(playbackHistoryProvider.notifier); final isActive = playlist.collections.contains(props.collectionId); @@ -73,7 +73,7 @@ class TrackViewHeaderActions extends HookConsumerWidget { } }, ), - if (props.onHeart != null && auth != null) + if (props.onHeart != null && auth.asData?.value != null) HeartButton( isLiked: props.isLiked, icon: isUserPlaylist ? SpotubeIcons.trash : null, diff --git a/lib/components/tracks_view/sections/header/header_buttons.dart b/lib/components/tracks_view/sections/header/header_buttons.dart index aa660f01..aabca20f 100644 --- a/lib/components/tracks_view/sections/header/header_buttons.dart +++ b/lib/components/tracks_view/sections/header/header_buttons.dart @@ -13,7 +13,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class TrackViewHeaderButtons extends HookConsumerWidget { @@ -28,8 +28,8 @@ class TrackViewHeaderButtons extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final props = InheritedTrackView.of(context); - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.watch(playbackHistoryProvider.notifier); final isActive = playlist.collections.contains(props.collectionId); diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index 9b90b23d..e2fb1e6e 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -3,15 +3,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; void useEndlessPlayback(WidgetRef ref) { final auth = ref.watch(authenticationProvider); - final playback = ref.watch(proxyPlaylistProvider.notifier); - final playlist = ref.watch(proxyPlaylistProvider); + final playback = ref.watch(audioPlayerProvider.notifier); + final playlist = ref.watch(audioPlayerProvider.select((s) => s.playlist)); final spotify = ref.watch(spotifyProvider); final endlessPlayback = ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback)); @@ -22,7 +22,7 @@ void useEndlessPlayback(WidgetRef ref) { void listener(int index) async { try { - final playlist = ref.read(proxyPlaylistProvider); + final playlist = ref.read(audioPlayerProvider); if (index != playlist.tracks.length - 1) return; final track = playlist.tracks.last; @@ -56,7 +56,7 @@ void useEndlessPlayback(WidgetRef ref) { await playback.addTracks( tracks.toList() ..removeWhere((e) { - final playlist = ref.read(proxyPlaylistProvider); + final playlist = ref.read(audioPlayerProvider); final isDuplicate = playlist.tracks.any((t) => t.id == e.id); return e.id == track.id || isDuplicate; }), @@ -69,9 +69,9 @@ void useEndlessPlayback(WidgetRef ref) { // Sometimes user can change settings for which the currentIndexChanged // might not be called. So we need to check if the current track is the // last track and if it is then we need to call the listener manually. - if (playlist.active == playlist.tracks.length - 1 && + if (playlist.index == playlist.medias.length - 1 && audioPlayer.isPlaying) { - listener(playlist.active!); + listener(playlist.index); } final subscription = @@ -82,7 +82,7 @@ void useEndlessPlayback(WidgetRef ref) { [ spotify, playback, - playlist.tracks, + playlist.medias, endlessPlayback, auth, ], diff --git a/lib/main.dart b/lib/main.dart index 09db495c..9b92a21d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import 'package:spotube/hooks/configurators/use_close_behavior.dart'; import 'package:spotube/hooks/configurators/use_deep_linking.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; import 'package:spotube/hooks/configurators/use_get_storage_perms.dart'; +import 'package:spotube/provider/audio_player/audio_player_streams.dart'; import 'package:spotube/provider/server/bonsoir.dart'; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/provider/tray_manager/tray_manager.dart'; @@ -113,9 +114,10 @@ class Spotube extends HookConsumerWidget { ref.watch(paletteProvider.select((s) => s?.dominantColor?.color)); final router = ref.watch(routerProvider); - ref.listen(serverProvider, (_, __) {}); + ref.listen(audioPlayerStreamListenersProvider, (_, __) {}); ref.listen(bonsoirProvider, (_, __) {}); ref.listen(connectClientsProvider, (_, __) {}); + ref.listen(serverProvider, (_, __) {}); ref.listen(trayManagerProvider, (_, __) {}); useDisableBatteryOptimizations(); diff --git a/lib/models/connect/connect.dart b/lib/models/connect/connect.dart index 0a06be32..a70520ad 100644 --- a/lib/models/connect/connect.dart +++ b/lib/models/connect/connect.dart @@ -6,7 +6,7 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotify/spotify.dart' hide Playlist; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; +import 'package:spotube/provider/audio_player/state.dart'; part 'connect.freezed.dart'; part 'connect.g.dart'; diff --git a/lib/models/connect/ws_event.dart b/lib/models/connect/ws_event.dart index c3c29e76..d1047646 100644 --- a/lib/models/connect/ws_event.dart +++ b/lib/models/connect/ws_event.dart @@ -325,12 +325,12 @@ class WebSocketErrorEvent extends WebSocketEvent { WebSocketErrorEvent(String data) : super(WsEvent.error, data); } -class WebSocketQueueEvent extends WebSocketEvent { - WebSocketQueueEvent(ProxyPlaylist data) : super(WsEvent.queue, data); +class WebSocketQueueEvent extends WebSocketEvent { + WebSocketQueueEvent(AudioPlayerState data) : super(WsEvent.queue, data); factory WebSocketQueueEvent.fromJson(Map json) => WebSocketQueueEvent( - ProxyPlaylist.fromJsonRaw(json), + AudioPlayerState.fromJson(json), ); } diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index ca9d6d97..37cc930c 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -2599,11 +2599,6 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); - static const VerificationMeta _volumeMeta = const VerificationMeta('volume'); - @override - late final GeneratedColumn volume = GeneratedColumn( - 'volume', aliasedName, false, - type: DriftSqlType.double, requiredDuringInsert: true); static const VerificationMeta _loopModeMeta = const VerificationMeta('loopMode'); @override @@ -2621,9 +2616,17 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); + static const VerificationMeta _collectionsMeta = + const VerificationMeta('collections'); + @override + late final GeneratedColumnWithTypeConverter, String> + collections = GeneratedColumn('collections', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $AudioPlayerStateTableTable.$convertercollections); @override List get $columns => - [id, playing, volume, loopMode, shuffled]; + [id, playing, loopMode, shuffled, collections]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -2644,12 +2647,6 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable } else if (isInserting) { context.missing(_playingMeta); } - if (data.containsKey('volume')) { - context.handle(_volumeMeta, - volume.isAcceptableOrUnknown(data['volume']!, _volumeMeta)); - } else if (isInserting) { - context.missing(_volumeMeta); - } context.handle(_loopModeMeta, const VerificationResult.success()); if (data.containsKey('shuffled')) { context.handle(_shuffledMeta, @@ -2657,6 +2654,7 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable } else if (isInserting) { context.missing(_shuffledMeta); } + context.handle(_collectionsMeta, const VerificationResult.success()); return context; } @@ -2671,13 +2669,14 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable .read(DriftSqlType.int, data['${effectivePrefix}id'])!, playing: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}playing'])!, - volume: attachedDatabase.typeMapping - .read(DriftSqlType.double, data['${effectivePrefix}volume'])!, loopMode: $AudioPlayerStateTableTable.$converterloopMode.fromSql( attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}loop_mode'])!), shuffled: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, + collections: $AudioPlayerStateTableTable.$convertercollections.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}collections'])!), ); } @@ -2688,32 +2687,37 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable static JsonTypeConverter2 $converterloopMode = const EnumNameConverter(PlaylistMode.values); + static TypeConverter, String> $convertercollections = + const StringListConverter(); } class AudioPlayerStateTableData extends DataClass implements Insertable { final int id; final bool playing; - final double volume; final PlaylistMode loopMode; final bool shuffled; + final List collections; const AudioPlayerStateTableData( {required this.id, required this.playing, - required this.volume, required this.loopMode, - required this.shuffled}); + required this.shuffled, + required this.collections}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); map['playing'] = Variable(playing); - map['volume'] = Variable(volume); { map['loop_mode'] = Variable( $AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode)); } map['shuffled'] = Variable(shuffled); + { + map['collections'] = Variable( + $AudioPlayerStateTableTable.$convertercollections.toSql(collections)); + } return map; } @@ -2721,9 +2725,9 @@ class AudioPlayerStateTableData extends DataClass return AudioPlayerStateTableCompanion( id: Value(id), playing: Value(playing), - volume: Value(volume), loopMode: Value(loopMode), shuffled: Value(shuffled), + collections: Value(collections), ); } @@ -2733,10 +2737,10 @@ class AudioPlayerStateTableData extends DataClass return AudioPlayerStateTableData( id: serializer.fromJson(json['id']), playing: serializer.fromJson(json['playing']), - volume: serializer.fromJson(json['volume']), loopMode: $AudioPlayerStateTableTable.$converterloopMode .fromJson(serializer.fromJson(json['loopMode'])), shuffled: serializer.fromJson(json['shuffled']), + collections: serializer.fromJson>(json['collections']), ); } @override @@ -2745,103 +2749,103 @@ class AudioPlayerStateTableData extends DataClass return { 'id': serializer.toJson(id), 'playing': serializer.toJson(playing), - 'volume': serializer.toJson(volume), 'loopMode': serializer.toJson( $AudioPlayerStateTableTable.$converterloopMode.toJson(loopMode)), 'shuffled': serializer.toJson(shuffled), + 'collections': serializer.toJson>(collections), }; } AudioPlayerStateTableData copyWith( {int? id, bool? playing, - double? volume, PlaylistMode? loopMode, - bool? shuffled}) => + bool? shuffled, + List? collections}) => AudioPlayerStateTableData( id: id ?? this.id, playing: playing ?? this.playing, - volume: volume ?? this.volume, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, ); @override String toString() { return (StringBuffer('AudioPlayerStateTableData(') ..write('id: $id, ') ..write('playing: $playing, ') - ..write('volume: $volume, ') ..write('loopMode: $loopMode, ') - ..write('shuffled: $shuffled') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, playing, volume, loopMode, shuffled); + int get hashCode => Object.hash(id, playing, loopMode, shuffled, collections); @override bool operator ==(Object other) => identical(this, other) || (other is AudioPlayerStateTableData && other.id == this.id && other.playing == this.playing && - other.volume == this.volume && other.loopMode == this.loopMode && - other.shuffled == this.shuffled); + other.shuffled == this.shuffled && + other.collections == this.collections); } class AudioPlayerStateTableCompanion extends UpdateCompanion { final Value id; final Value playing; - final Value volume; final Value loopMode; final Value shuffled; + final Value> collections; const AudioPlayerStateTableCompanion({ this.id = const Value.absent(), this.playing = const Value.absent(), - this.volume = const Value.absent(), this.loopMode = const Value.absent(), this.shuffled = const Value.absent(), + this.collections = const Value.absent(), }); AudioPlayerStateTableCompanion.insert({ this.id = const Value.absent(), required bool playing, - required double volume, required PlaylistMode loopMode, required bool shuffled, + required List collections, }) : playing = Value(playing), - volume = Value(volume), loopMode = Value(loopMode), - shuffled = Value(shuffled); + shuffled = Value(shuffled), + collections = Value(collections); static Insertable custom({ Expression? id, Expression? playing, - Expression? volume, Expression? loopMode, Expression? shuffled, + Expression? collections, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (playing != null) 'playing': playing, - if (volume != null) 'volume': volume, if (loopMode != null) 'loop_mode': loopMode, if (shuffled != null) 'shuffled': shuffled, + if (collections != null) 'collections': collections, }); } AudioPlayerStateTableCompanion copyWith( {Value? id, Value? playing, - Value? volume, Value? loopMode, - Value? shuffled}) { + Value? shuffled, + Value>? collections}) { return AudioPlayerStateTableCompanion( id: id ?? this.id, playing: playing ?? this.playing, - volume: volume ?? this.volume, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, ); } @@ -2854,9 +2858,6 @@ class AudioPlayerStateTableCompanion if (playing.present) { map['playing'] = Variable(playing.value); } - if (volume.present) { - map['volume'] = Variable(volume.value); - } if (loopMode.present) { map['loop_mode'] = Variable( $AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode.value)); @@ -2864,6 +2865,11 @@ class AudioPlayerStateTableCompanion if (shuffled.present) { map['shuffled'] = Variable(shuffled.value); } + if (collections.present) { + map['collections'] = Variable($AudioPlayerStateTableTable + .$convertercollections + .toSql(collections.value)); + } return map; } @@ -2872,9 +2878,9 @@ class AudioPlayerStateTableCompanion return (StringBuffer('AudioPlayerStateTableCompanion(') ..write('id: $id, ') ..write('playing: $playing, ') - ..write('volume: $volume, ') ..write('loopMode: $loopMode, ') - ..write('shuffled: $shuffled') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections') ..write(')')) .toString(); } @@ -4591,17 +4597,17 @@ typedef $$AudioPlayerStateTableTableInsertCompanionBuilder = AudioPlayerStateTableCompanion Function({ Value id, required bool playing, - required double volume, required PlaylistMode loopMode, required bool shuffled, + required List collections, }); typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder = AudioPlayerStateTableCompanion Function({ Value id, Value playing, - Value volume, Value loopMode, Value shuffled, + Value> collections, }); class $$AudioPlayerStateTableTableTableManager extends RootTableManager< @@ -4627,30 +4633,30 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< getUpdateCompanionBuilder: ({ Value id = const Value.absent(), Value playing = const Value.absent(), - Value volume = const Value.absent(), Value loopMode = const Value.absent(), Value shuffled = const Value.absent(), + Value> collections = const Value.absent(), }) => AudioPlayerStateTableCompanion( id: id, playing: playing, - volume: volume, loopMode: loopMode, shuffled: shuffled, + collections: collections, ), getInsertCompanionBuilder: ({ Value id = const Value.absent(), required bool playing, - required double volume, required PlaylistMode loopMode, required bool shuffled, + required List collections, }) => AudioPlayerStateTableCompanion.insert( id: id, playing: playing, - volume: volume, loopMode: loopMode, shuffled: shuffled, + collections: collections, ), )); } @@ -4681,11 +4687,6 @@ class $$AudioPlayerStateTableTableFilterComposer builder: (column, joinBuilders) => ColumnFilters(column, joinBuilders: joinBuilders)); - ColumnFilters get volume => $state.composableBuilder( - column: $state.table.volume, - builder: (column, joinBuilders) => - ColumnFilters(column, joinBuilders: joinBuilders)); - ColumnWithTypeConverterFilters get loopMode => $state.composableBuilder( column: $state.table.loopMode, @@ -4698,6 +4699,13 @@ class $$AudioPlayerStateTableTableFilterComposer builder: (column, joinBuilders) => ColumnFilters(column, joinBuilders: joinBuilders)); + ColumnWithTypeConverterFilters, List, String> + get collections => $state.composableBuilder( + column: $state.table.collections, + builder: (column, joinBuilders) => ColumnWithTypeConverterFilters( + column, + joinBuilders: joinBuilders)); + ComposableFilter playlistTableRefs( ComposableFilter Function($$PlaylistTableTableFilterComposer f) f) { final $$PlaylistTableTableFilterComposer composer = $state.composerBuilder( @@ -4725,11 +4733,6 @@ class $$AudioPlayerStateTableTableOrderingComposer builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); - ColumnOrderings get volume => $state.composableBuilder( - column: $state.table.volume, - builder: (column, joinBuilders) => - ColumnOrderings(column, joinBuilders: joinBuilders)); - ColumnOrderings get loopMode => $state.composableBuilder( column: $state.table.loopMode, builder: (column, joinBuilders) => @@ -4739,6 +4742,11 @@ class $$AudioPlayerStateTableTableOrderingComposer column: $state.table.shuffled, builder: (column, joinBuilders) => ColumnOrderings(column, joinBuilders: joinBuilders)); + + ColumnOrderings get collections => $state.composableBuilder( + column: $state.table.collections, + builder: (column, joinBuilders) => + ColumnOrderings(column, joinBuilders: joinBuilders)); } typedef $$PlaylistTableTableInsertCompanionBuilder = PlaylistTableCompanion diff --git a/lib/models/database/tables/audio_player_state.dart b/lib/models/database/tables/audio_player_state.dart index 45f5ffd9..3e49cf6f 100644 --- a/lib/models/database/tables/audio_player_state.dart +++ b/lib/models/database/tables/audio_player_state.dart @@ -3,9 +3,9 @@ part of '../database.dart'; class AudioPlayerStateTable extends Table { IntColumn get id => integer().autoIncrement()(); BoolColumn get playing => boolean()(); - RealColumn get volume => real()(); TextColumn get loopMode => textEnum()(); BoolColumn get shuffled => boolean()(); + TextColumn get collections => text().map(const StringListConverter())(); } class PlaylistTable extends Table { diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index a071ac04..f9f70c66 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -12,7 +12,7 @@ import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/pages/album/album.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -30,10 +30,10 @@ class AlbumCard extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.read(playbackHistoryProvider.notifier); bool isPlaylistPlaying = useMemoized( @@ -59,7 +59,7 @@ class AlbumCard extends HookConsumerWidget { ), margin: const EdgeInsets.symmetric(horizontal: 10), isPlaying: isPlaylistPlaying, - isLoading: (isPlaylistPlaying && playlist.isFetching == true) || + isLoading: (isPlaylistPlaying && playlistNotifier.isFetching()) || updating.value, title: album.name!, description: diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 66344792..d75df796 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -25,7 +25,7 @@ import 'package:spotube/hooks/utils/use_palette_color.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/pages/lyrics/lyrics.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; @@ -47,7 +47,7 @@ class PlayerView extends HookConsumerWidget { final auth = ref.watch(authenticationProvider); final sourcedCurrentTrack = ref.watch(activeSourcedTrackProvider); final currentActiveTrack = - ref.watch(proxyPlaylistProvider.select((s) => s.activeTrack)); + ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); final currentTrack = sourcedCurrentTrack ?? currentActiveTrack; final isLocalTrack = currentTrack is LocalTrack; final mediaQuery = MediaQuery.of(context); @@ -309,15 +309,13 @@ class PlayerView extends HookConsumerWidget { builder: (context) => Consumer( builder: (context, ref, _) { final playlist = ref.watch( - proxyPlaylistProvider, - ); - final playlistNotifier = - ref.read( - proxyPlaylistProvider - .notifier, + audioPlayerProvider, ); + final playlistNotifier = ref + .read(audioPlayerProvider + .notifier); return PlayerQueue - .fromProxyPlaylistNotifier( + .fromAudioPlayerNotifier( floating: false, playlist: playlist, notifier: playlistNotifier, @@ -328,8 +326,9 @@ class PlayerView extends HookConsumerWidget { } : null), ), - if (auth != null) const SizedBox(width: 10), - if (auth != null) + if (auth.asData?.value != null) + const SizedBox(width: 10), + if (auth.asData?.value != null) Expanded( child: OutlinedButton.icon( label: Text(context.l10n.lyrics), diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index 8fd434ad..8a7b3e83 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -14,7 +14,7 @@ import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/sleep_timer_provider.dart'; class PlayerActions extends HookConsumerWidget { @@ -33,7 +33,7 @@ class PlayerActions extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final isLocalTrack = playlist.activeTrack is LocalTrack; ref.watch(downloadManagerProvider); final downloader = ref.watch(downloadManagerProvider.notifier); @@ -129,7 +129,9 @@ class PlayerActions extends HookConsumerWidget { ? () => downloader.addToQueue(playlist.activeTrack!) : null, ), - if (playlist.activeTrack != null && !isLocalTrack && auth != null) + if (playlist.activeTrack != null && + !isLocalTrack && + auth.asData?.value != null) TrackHeartButton(track: playlist.activeTrack!), AdaptivePopSheetList( offset: Offset(0, -50 * (sleepTimerEntries.values.length + 2)), diff --git a/lib/modules/player/player_controls.dart b/lib/modules/player/player_controls.dart index a1a3ffcf..c5ef82d6 100644 --- a/lib/modules/player/player_controls.dart +++ b/lib/modules/player/player_controls.dart @@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/models/logger.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class PlayerControls extends HookConsumerWidget { @@ -43,8 +43,8 @@ class PlayerControls extends HookConsumerWidget { SeekIntent: SeekAction(), }, []); - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; @@ -132,7 +132,7 @@ class PlayerControls extends HookConsumerWidget { // than total duration. Keeping it resolved value: progress.value.toDouble(), secondaryTrackValue: bufferProgress, - onChanged: playlist.isFetching == true + onChanged: playlistNotifier.isFetching() ? null : (v) { progress.value = v; @@ -183,7 +183,7 @@ class PlayerControls extends HookConsumerWidget { : context.l10n.shuffle_playlist, icon: const Icon(SpotubeIcons.shuffle), style: shuffled ? activeButtonStyle : buttonStyle, - onPressed: playlist.isFetching == true + onPressed: playlistNotifier.isFetching() ? null : () { if (shuffled) { @@ -198,15 +198,15 @@ class PlayerControls extends HookConsumerWidget { tooltip: context.l10n.previous_track, icon: const Icon(SpotubeIcons.skipBack), style: buttonStyle, - onPressed: playlist.isFetching == true + onPressed: playlistNotifier.isFetching() ? null - : playlistNotifier.previous, + : audioPlayer.skipToPrevious, ), IconButton( tooltip: playing ? context.l10n.pause_playback : context.l10n.resume_playback, - icon: playlist.isFetching == true + icon: playlistNotifier.isFetching() ? SizedBox( height: 20, width: 20, @@ -219,7 +219,7 @@ class PlayerControls extends HookConsumerWidget { playing ? SpotubeIcons.pause : SpotubeIcons.play, ), style: resumePauseStyle, - onPressed: playlist.isFetching == true + onPressed: playlistNotifier.isFetching() ? null : Actions.handler( context, @@ -230,9 +230,9 @@ class PlayerControls extends HookConsumerWidget { tooltip: context.l10n.next_track, icon: const Icon(SpotubeIcons.skipForward), style: buttonStyle, - onPressed: playlist.isFetching == true + onPressed: playlistNotifier.isFetching() ? null - : playlistNotifier.next, + : audioPlayer.skipToNext, ), StreamBuilder( stream: audioPlayer.loopModeStream, @@ -253,7 +253,7 @@ class PlayerControls extends HookConsumerWidget { loopMode == PlaylistMode.loop ? activeButtonStyle : buttonStyle, - onPressed: playlist.isFetching == true + onPressed: playlistNotifier.isFetching() ? null : () async { await audioPlayer.setLoopMode(loopMode); diff --git a/lib/modules/player/player_overlay.dart b/lib/modules/player/player_overlay.dart index 084de425..c1b285ee 100644 --- a/lib/modules/player/player_overlay.dart +++ b/lib/modules/player/player_overlay.dart @@ -11,7 +11,7 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/modules/player/player.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class PlayerOverlay extends HookConsumerWidget { @@ -24,8 +24,8 @@ class PlayerOverlay extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); - final playlist = ref.watch(proxyPlaylistProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); final canShow = playlist.activeTrack != null; final playing = @@ -127,14 +127,14 @@ class PlayerOverlay extends HookConsumerWidget { SpotubeIcons.skipBack, color: textColor, ), - onPressed: playlist.isFetching + onPressed: playlistNotifier.isFetching() ? null - : playlistNotifier.previous, + : audioPlayer.skipToPrevious, ), Consumer( builder: (context, ref, _) { return IconButton( - icon: playlist.isFetching + icon: playlistNotifier.isFetching() ? const SizedBox( height: 20, width: 20, @@ -158,9 +158,9 @@ class PlayerOverlay extends HookConsumerWidget { SpotubeIcons.skipForward, color: textColor, ), - onPressed: playlist.isFetching + onPressed: playlistNotifier.isFetching() ? null - : playlistNotifier.next, + : audioPlayer.skipToNext, ), ], ), diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index cf16e9a3..2431d82e 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -18,12 +18,12 @@ import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/state.dart'; class PlayerQueue extends HookConsumerWidget { final bool floating; - final ProxyPlaylist playlist; + final AudioPlayerState playlist; final Future Function(Track track) onJump; final Future Function(String trackId) onRemove; @@ -40,10 +40,10 @@ class PlayerQueue extends HookConsumerWidget { super.key, }); - PlayerQueue.fromProxyPlaylistNotifier({ + PlayerQueue.fromAudioPlayerNotifier({ this.floating = true, required this.playlist, - required ProxyPlaylistNotifier notifier, + required AudioPlayerNotifier notifier, super.key, }) : onJump = notifier.jumpToTrack, onRemove = notifier.removeTrack, @@ -93,11 +93,10 @@ class PlayerQueue extends HookConsumerWidget { ); useEffect(() { - if (playlist.active == null) return null; + if (playlist.activeTrack == null) return null; - if (playlist.active! < 0) return; controller.scrollToIndex( - playlist.active!, + playlist.playlist.index, preferPosition: AutoScrollPosition.middle, ); return null; diff --git a/lib/modules/player/player_track_details.dart b/lib/modules/player/player_track_details.dart index da58e3b1..d722830e 100644 --- a/lib/modules/player/player_track_details.dart +++ b/lib/modules/player/player_track_details.dart @@ -9,7 +9,7 @@ import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; class PlayerTrackDetails extends HookConsumerWidget { @@ -21,7 +21,7 @@ class PlayerTrackDetails extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); - final playback = ref.watch(proxyPlaylistProvider); + final playback = ref.watch(audioPlayerProvider); return Row( children: [ diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index a6136e62..8592f1e3 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -15,7 +15,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -53,7 +53,8 @@ class SiblingTracksSheet extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final preferences = ref.watch(userPreferencesProvider); final isSearching = useState(false); @@ -129,13 +130,13 @@ class SiblingTracksSheet extends HookConsumerWidget { ]); final siblings = useMemoized( - () => playlist.isFetching == false + () => playlistNotifier.isFetching() ? [ (activeTrack as SourcedTrack).sourceInfo, ...activeTrack.siblings, ] : [], - [playlist.isFetching, activeTrack], + [activeTrack], ); final borderRadius = floating @@ -175,12 +176,12 @@ class SiblingTracksSheet extends HookConsumerWidget { Text(" • ${sourceInfo.artist}"), ], ), - enabled: playlist.isFetching != true, - selected: playlist.isFetching != true && + enabled: !playlistNotifier.isFetching(), + selected: !playlistNotifier.isFetching() && sourceInfo.id == (activeTrack as SourcedTrack).sourceInfo.id, selectedTileColor: theme.popupMenuTheme.color, onTap: () { - if (playlist.isFetching == false && + if (!playlistNotifier.isFetching() && sourceInfo.id != (activeTrack as SourcedTrack).sourceInfo.id) { activeTrackNotifier.swapSibling(sourceInfo); Navigator.of(context).pop(); @@ -188,7 +189,7 @@ class SiblingTracksSheet extends HookConsumerWidget { }, ); }, - [playlist.isFetching, activeTrack, siblings], + [activeTrack, siblings], ); final mediaQuery = MediaQuery.of(context); diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index 7c11eca6..c4164701 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -9,7 +9,7 @@ import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -22,8 +22,8 @@ class PlaylistCard extends HookConsumerWidget { }); @override Widget build(BuildContext context, ref) { - final playlistQueue = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlistQueue = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.read(playbackHistoryProvider.notifier); final playing = @@ -65,8 +65,8 @@ class PlaylistCard extends HookConsumerWidget { placeholder: ImagePlaceholder.collection, ), isPlaying: isPlaylistPlaying, - isLoading: - (isPlaylistPlaying && playlistQueue.isFetching) || updating.value, + isLoading: (isPlaylistPlaying && playlistNotifier.isFetching()) || + updating.value, isOwner: playlist.owner?.id == me.asData?.value.id && me.asData?.value.id != null, onTap: () { diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index a77ab6fe..e7dbacd2 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -19,7 +19,7 @@ import 'package:spotube/hooks/utils/use_brightness_value.dart'; import 'package:spotube/models/logger.dart'; import 'package:flutter/material.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/volume_provider.dart'; @@ -33,7 +33,7 @@ class BottomPlayer extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final auth = ref.watch(authenticationProvider); - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); @@ -91,7 +91,7 @@ class BottomPlayer extends HookConsumerWidget { children: [ PlayerActions( extraActions: [ - if (auth != null) + if (auth.asData?.value != null) IconButton( tooltip: context.l10n.mini_player, icon: const Icon(SpotubeIcons.miniPlayer), diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 7d7fa8ef..713e0d26 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -135,7 +135,7 @@ class ArtistPageHeader extends HookConsumerWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (auth != null) + if (auth.asData?.value != null) Consumer( builder: (context, ref, _) { final isFollowingQuery = ref diff --git a/lib/pages/artist/section/top_tracks.dart b/lib/pages/artist/section/top_tracks.dart index c9397c7b..d52ed470 100644 --- a/lib/pages/artist/section/top_tracks.dart +++ b/lib/pages/artist/section/top_tracks.dart @@ -9,7 +9,7 @@ import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class ArtistPageTopTracks extends HookConsumerWidget { @@ -21,8 +21,8 @@ class ArtistPageTopTracks extends HookConsumerWidget { final theme = Theme.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context); - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final topTracksQuery = ref.watch(artistTopTracksProvider(artistId)); final isPlaylistPlaying = playlist.containsTracks( diff --git a/lib/pages/library/local_folder.dart b/lib/pages/library/local_folder.dart index 830e8a5d..16891bc1 100644 --- a/lib/pages/library/local_folder.dart +++ b/lib/pages/library/local_folder.dart @@ -17,7 +17,7 @@ import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/service_utils.dart'; class LocalLibraryPage extends HookConsumerWidget { @@ -32,8 +32,8 @@ class LocalLibraryPage extends HookConsumerWidget { List tracks, { LocalTrack? currentTrack, }) async { - final playlist = ref.read(proxyPlaylistProvider); - final playback = ref.read(proxyPlaylistProvider.notifier); + final playlist = ref.read(audioPlayerProvider); + final playback = ref.read(audioPlayerProvider.notifier); currentTrack ??= tracks.first; final isPlaylistPlaying = playlist.containsTracks(tracks); if (!isPlaylistPlaying) { @@ -52,7 +52,7 @@ class LocalLibraryPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final sortBy = useState(SortBy.none); - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final trackSnapshot = ref.watch(localTracksProvider); final isPlaylistPlaying = playlist.containsTracks( trackSnapshot.asData?.value.values.flattened.toList() ?? []); diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 90838300..3bdc3b52 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -11,7 +11,7 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/spotify/recommendation_seeds.dart'; import 'package:spotube/pages/playlist/playlist.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class PlaylistGenerateResultPage extends HookConsumerWidget { @@ -28,7 +28,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final router = GoRouter.of(context); final scaffoldMessenger = ScaffoldMessenger.of(context); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final generatedPlaylist = ref.watch(generatePlaylistProvider(state)); @@ -81,9 +81,12 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { ? null : () async { await playlistNotifier.load( - generatedPlaylist.asData!.value.where( - (e) => selectedTracks.value.contains(e.id!), - ), + generatedPlaylist.asData!.value + .where( + (e) => selectedTracks.value + .contains(e.id!), + ) + .toList(), autoPlay: true, ); }, diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index c484046b..18ce6e28 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -18,7 +18,7 @@ import 'package:spotube/hooks/utils/use_palette_color.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/provider/spotify/spotify.dart'; @@ -30,7 +30,7 @@ class LyricsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); String albumArt = useMemoized( () => (playlist.activeTrack?.album?.images).asUrlString( index: (playlist.activeTrack?.album?.images?.length ?? 1) - 1, @@ -62,7 +62,7 @@ class LyricsPage extends HookConsumerWidget { const Spacer(), Consumer( builder: (context, ref, child) { - final playback = ref.watch(proxyPlaylistProvider); + final playback = ref.watch(audioPlayerProvider); final lyric = ref.watch(syncedLyricsProvider(playback.activeTrack)); final providerName = lyric.asData?.value.provider; diff --git a/lib/pages/lyrics/mini_lyrics.dart b/lib/pages/lyrics/mini_lyrics.dart index f9659538..d9222059 100644 --- a/lib/pages/lyrics/mini_lyrics.dart +++ b/lib/pages/lyrics/mini_lyrics.dart @@ -15,7 +15,7 @@ import 'package:spotube/hooks/utils/use_force_update.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -31,7 +31,7 @@ class MiniLyricsPage extends HookConsumerWidget { final update = useForceUpdate(); final wasMaximized = useRef(false); - final playlistQueue = ref.watch(proxyPlaylistProvider); + final playlistQueue = ref.watch(audioPlayerProvider); final areaActive = useState(false); final hoverMode = useState(true); @@ -230,14 +230,13 @@ class MiniLyricsPage extends HookConsumerWidget { builder: (context) { return Consumer(builder: (context, ref, _) { final playlist = - ref.watch(proxyPlaylistProvider); + ref.watch(audioPlayerProvider); - return PlayerQueue - .fromProxyPlaylistNotifier( + return PlayerQueue.fromAudioPlayerNotifier( floating: true, playlist: playlist, notifier: ref - .read(proxyPlaylistProvider.notifier), + .read(audioPlayerProvider.notifier), ); }); }, diff --git a/lib/pages/lyrics/plain_lyrics.dart b/lib/pages/lyrics/plain_lyrics.dart index 5340e8fd..7c571d5f 100644 --- a/lib/pages/lyrics/plain_lyrics.dart +++ b/lib/pages/lyrics/plain_lyrics.dart @@ -11,7 +11,7 @@ import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class PlainLyrics extends HookConsumerWidget { @@ -27,7 +27,7 @@ class PlainLyrics extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final lyricsQuery = ref.watch(syncedLyricsProvider(playlist.activeTrack)); final mediaQuery = MediaQuery.of(context); final textTheme = Theme.of(context).textTheme; diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart index 8a2dd356..3294bab5 100644 --- a/lib/pages/lyrics/synced_lyrics.dart +++ b/lib/pages/lyrics/synced_lyrics.dart @@ -12,7 +12,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; import 'package:spotube/modules/lyrics/use_synced_lyrics.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -32,7 +32,7 @@ class SyncedLyrics extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final mediaQuery = MediaQuery.of(context); final controller = useAutoScrollController(); @@ -54,7 +54,7 @@ class SyncedLyrics extends HookConsumerWidget { final textTheme = Theme.of(context).textTheme; ref.listen( - proxyPlaylistProvider.select((s) => s.activeTrack), + audioPlayerProvider.select((s) => s.activeTrack), (previous, next) { controller.scrollToIndex(0); ref.read(syncedLyricsDelayProvider.notifier).state = 0; diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 93a84f0a..322a8731 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -16,7 +16,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/configurators/use_endless_playback.dart'; import 'package:spotube/pages/home/home.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/routes/connect.dart'; import 'package:spotube/services/connectivity_adapter.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; @@ -201,11 +201,11 @@ class RootApp extends HookConsumerWidget { ), child: Consumer( builder: (context, ref, _) { - final playlist = ref.watch(proxyPlaylistProvider); + final playlist = ref.watch(audioPlayerProvider); final playlistNotifier = - ref.read(proxyPlaylistProvider.notifier); + ref.read(audioPlayerProvider.notifier); - return PlayerQueue.fromProxyPlaylistNotifier( + return PlayerQueue.fromAudioPlayerNotifier( floating: true, playlist: playlist, notifier: playlistNotifier, diff --git a/lib/pages/search/sections/tracks.dart b/lib/pages/search/sections/tracks.dart index 1bde2872..6ec8f685 100644 --- a/lib/pages/search/sections/tracks.dart +++ b/lib/pages/search/sections/tracks.dart @@ -8,7 +8,7 @@ import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/connect.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class SearchTracksSection extends HookConsumerWidget { @@ -24,8 +24,8 @@ class SearchTracksSection extends HookConsumerWidget { ref.watch(searchProvider(SearchType.track).notifier); final tracks = searchTrack.asData?.value.items.cast() ?? []; - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); - final playlist = ref.watch(proxyPlaylistProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); final theme = Theme.of(context); return Column( diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 1e9b2067..dc4defc8 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -14,7 +14,7 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -34,8 +34,8 @@ class TrackPage extends HookConsumerWidget { final ThemeData(:textTheme, :colorScheme) = Theme.of(context); final mediaQuery = MediaQuery.of(context); - final playlist = ref.watch(proxyPlaylistProvider); - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final isActive = playlist.activeTrack?.id == trackId; diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 747c78e6..258e15d8 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -1,13 +1,22 @@ +import 'dart:math'; + import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotify/spotify.dart' hide Playlist; +import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/audio_player/state.dart'; +import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/discord_provider.dart'; +import 'package:spotube/provider/server/sourced_track.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class AudioPlayerNotifier extends Notifier { + BlackListNotifier get _blacklist => ref.read(blacklistProvider.notifier); + Future _syncSavedState() async { final database = ref.read(databaseProvider); @@ -18,9 +27,9 @@ class AudioPlayerNotifier extends Notifier { await database.into(database.audioPlayerStateTable).insert( AudioPlayerStateTableCompanion.insert( playing: audioPlayer.isPlaying, - volume: audioPlayer.volume, loopMode: audioPlayer.loopMode, shuffled: audioPlayer.isShuffled, + collections: [], id: const Value(0), ), ); @@ -28,7 +37,6 @@ class AudioPlayerNotifier extends Notifier { playerState = await database.select(database.audioPlayerStateTable).getSingle(); } else { - await audioPlayer.setVolume(playerState.volume); await audioPlayer.setLoopMode(playerState.loopMode); await audioPlayer.setShuffle(playerState.shuffled); } @@ -130,15 +138,6 @@ class AudioPlayerNotifier extends Notifier { ), ); }), - audioPlayer.volumeStream.listen((volume) async { - state = state.copyWith(volume: volume); - - await _updatePlayerState( - AudioPlayerStateTableCompanion( - volume: Value(volume), - ), - ); - }), audioPlayer.loopModeStream.listen((loopMode) async { state = state.copyWith(loopMode: loopMode); @@ -177,49 +176,141 @@ class AudioPlayerNotifier extends Notifier { playing: audioPlayer.isPlaying, playlist: audioPlayer.playlist, shuffled: audioPlayer.isShuffled, - volume: audioPlayer.volume, + collections: [], ); } + // Collection related methods + Future addCollections(List collectionIds) async { + state = state.copyWith(collections: [ + ...state.collections, + ...collectionIds, + ]); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + collections: Value(state.collections), + ), + ); + } + + Future addCollection(String collectionId) async { + await addCollections([collectionId]); + } + + Future removeCollections(List collectionIds) async { + state = state.copyWith( + collections: state.collections + .where((element) => !collectionIds.contains(element)) + .toList(), + ); + + await _updatePlayerState( + AudioPlayerStateTableCompanion( + collections: Value(state.collections), + ), + ); + } + + Future removeCollection(String collectionId) async { + await removeCollections([collectionId]); + } + // Tracks related methods + Future addTracksAtFirst(Iterable tracks) async { + if (state.tracks.length == 1) { + return addTracks(tracks); + } + + tracks = _blacklist.filter(tracks).toList() as List; + + for (int i = 0; i < tracks.length; i++) { + final track = tracks.elementAt(i); + + await audioPlayer.addTrackAt( + SpotubeMedia(track), + max(state.playlist.index, 0) + i + 1, + ); + } + } + Future addTrack(Track track) async { + if (_blacklist.contains(track)) return; await audioPlayer.addTrack(SpotubeMedia(track)); } Future addTracks(Iterable tracks) async { + tracks = _blacklist.filter(tracks).toList() as List; for (final track in tracks) { - await addTrack(track); + await audioPlayer.addTrack(SpotubeMedia(track)); } } - Future removeTrack(Track track) async { - final index = state.tracks.indexWhere((element) => element == track); + Future removeTrack(String trackId) async { + final index = state.tracks.indexWhere((element) => element.id == trackId); if (index == -1) return; await audioPlayer.removeTrack(index); } - Future removeTracks(Iterable tracks) async { - for (final track in tracks) { - await removeTrack(track); + Future removeTracks(Iterable trackIds) async { + for (final trackId in trackIds) { + await removeTrack(trackId); } } Future load( - List track, { - required int initialIndex, + List tracks, { + int initialIndex = 0, bool autoPlay = false, }) async { + tracks = _blacklist.filter(tracks).toList() as List; + + // Giving the initial track a boost so MediaKit won't skip + // because of timeout + final intendedActiveTrack = tracks.elementAt(initialIndex); + if (intendedActiveTrack is! LocalTrack) { + await ref.read(sourcedTrackProvider(intendedActiveTrack).future); + } + await audioPlayer.openPlaylist( - track.map((t) => SpotubeMedia(t)).toList(), + tracks.asMediaList(), initialIndex: initialIndex, autoPlay: autoPlay, ); } + + Future jumpToTrack(Track track) async { + final index = + state.tracks.toList().indexWhere((element) => element.id == track.id); + if (index == -1) return; + await audioPlayer.jumpTo(index); + } + + Future moveTrack(int oldIndex, int newIndex) async { + if (oldIndex == newIndex || + newIndex < 0 || + oldIndex < 0 || + newIndex > state.tracks.length - 1 || + oldIndex > state.tracks.length - 1) return; + + await audioPlayer.moveTrack(oldIndex, newIndex); + } + + bool isFetching() { + if (state.activeTrack == null) return false; + return ref.read(sourcedTrackProvider(state.activeTrack!)).isLoading; + } + + Future stop() async { + await audioPlayer.stop(); + ref.read(discordProvider.notifier).clear(); + } } -final audioPlayerProvider = NotifierProvider( +final audioPlayerProvider = + NotifierProvider( () => AudioPlayerNotifier(), -); \ No newline at end of file +); diff --git a/lib/provider/proxy_playlist/player_listeners.dart b/lib/provider/audio_player/audio_player_streams.dart similarity index 51% rename from lib/provider/proxy_playlist/player_listeners.dart rename to lib/provider/audio_player/audio_player_streams.dart index 2c1423a5..d5473dd5 100644 --- a/lib/provider/proxy_playlist/player_listeners.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -1,19 +1,53 @@ -// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member - import 'dart:async'; -import 'package:spotube/services/logger/logger.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/local_track.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/state.dart'; +import 'package:spotube/provider/discord_provider.dart'; +import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/palette_provider.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; -import 'package:spotube/provider/proxy_playlist/skip_segments.dart'; +import 'package:spotube/provider/skip_segments/skip_segments.dart'; +import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/server/sourced_track.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/audio_services/audio_services.dart'; +import 'package:spotube/services/logger/logger.dart'; + +class AudioPlayerStreamListeners { + final Ref ref; + late final AudioServices notificationService; + AudioPlayerStreamListeners(this.ref) { + AudioServices.create(ref, ref.read(audioPlayerProvider.notifier)).then( + (value) => notificationService = value, + ); + + final subscriptions = [ + subscribeToPlaylist(), + subscribeToSkipSponsor(), + subscribeToScrobbleChanged(), + subscribeToPosition(), + subscribeToPlayerError(), + ]; + + ref.onDispose(() { + for (final subscription in subscriptions) { + subscription.cancel(); + } + }); + } + + ScrobblerNotifier get scrobbler => ref.read(scrobblerProvider.notifier); + UserPreferences get preferences => ref.read(userPreferencesProvider); + Discord get discord => ref.read(discordProvider); + AudioPlayerState get audioPlayerState => ref.read(audioPlayerProvider); + PlaybackHistoryNotifier get history => + ref.read(playbackHistoryProvider.notifier); -extension ProxyPlaylistListeners on ProxyPlaylistNotifier { Future updatePalette() async { final palette = ref.read(paletteProvider); if (!preferences.albumColorSync) { @@ -21,11 +55,12 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier { return; } return Future.microtask(() async { - if (playlist.activeTrack == null) return; + final activeTrack = ref.read(audioPlayerProvider).activeTrack; + if (activeTrack == null) return; final palette = await PaletteGenerator.fromImageProvider( UniversalImage.imageProvider( - (playlist.activeTrack?.album?.images).asUrlString( + (activeTrack.album?.images).asUrlString( placeholder: ImagePlaceholder.albumArt, ), height: 50, @@ -38,15 +73,8 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier { StreamSubscription subscribeToPlaylist() { return audioPlayer.playlistStream.listen((mpvPlaylist) { - state = playlist.copyWith( - tracks: mpvPlaylist.medias - .map((media) => SpotubeMedia.fromMedia(media).track) - .toSet(), - active: mpvPlaylist.index, - ); - - notificationService.addTrack(playlist.activeTrack!); - discord.updatePresence(playlist.activeTrack!); + notificationService.addTrack(audioPlayerState.activeTrack!); + discord.updatePresence(audioPlayerState.activeTrack!); updatePalette(); }); } @@ -72,18 +100,18 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier { String? lastScrobbled; return audioPlayer.positionStream.listen((position) { try { - final uid = playlist.activeTrack is LocalTrack - ? (playlist.activeTrack as LocalTrack).path - : playlist.activeTrack?.id; + final uid = audioPlayerState.activeTrack is LocalTrack + ? (audioPlayerState.activeTrack as LocalTrack).path + : audioPlayerState.activeTrack?.id; - if (playlist.activeTrack == null || + if (audioPlayerState.activeTrack == null || lastScrobbled == uid || position.inSeconds < 30) { return; } - scrobbler.scrobble(playlist.activeTrack!); - history.addTrack(playlist.activeTrack!); + scrobbler.scrobble(audioPlayerState.activeTrack!); + history.addTrack(audioPlayerState.activeTrack!); lastScrobbled = uid; } catch (e, stack) { AppLogger.reportError(e, stack); @@ -95,9 +123,13 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier { String lastTrack = ""; // used to prevent multiple calls to the same track return audioPlayer.positionStream.listen((event) async { if (event < const Duration(seconds: 3) || - playlist.active == null || - playlist.active == playlist.tracks.length - 1) return; - final nextTrack = playlist.tracks.elementAt(playlist.active! + 1); + audioPlayerState.playlist.index == -1 || + audioPlayerState.playlist.index == + audioPlayerState.tracks.length - 1) { + return; + } + final nextTrack = audioPlayerState.tracks + .elementAt(audioPlayerState.playlist.index + 1); if (lastTrack == nextTrack.id || nextTrack is LocalTrack) return; @@ -113,3 +145,6 @@ extension ProxyPlaylistListeners on ProxyPlaylistNotifier { return audioPlayer.errorStream.listen((event) {}); } } + +final audioPlayerStreamListenersProvider = + Provider(AudioPlayerStreamListeners.new); diff --git a/lib/provider/audio_player/state.dart b/lib/provider/audio_player/state.dart index 3c874011..685ce112 100644 --- a/lib/provider/audio_player/state.dart +++ b/lib/provider/audio_player/state.dart @@ -4,39 +4,97 @@ import 'package:spotube/services/audio_player/audio_player.dart'; class AudioPlayerState { final bool playing; - final double volume; final PlaylistMode loopMode; final bool shuffled; final Playlist playlist; final List tracks; + final List collections; AudioPlayerState({ required this.playing, - required this.volume, required this.loopMode, required this.shuffled, required this.playlist, + required this.collections, List? tracks, }) : tracks = tracks ?? playlist.medias .map((media) => SpotubeMedia.fromMedia(media).track) .toList(); + factory AudioPlayerState.fromJson(Map json) { + return AudioPlayerState( + playing: json['playing'], + loopMode: PlaylistMode.values.firstWhere( + (e) => e.name == json['loopMode'], + orElse: () => audioPlayer.loopMode, + ), + shuffled: json['shuffled'], + playlist: Playlist( + json['playlist']['medias'] + .map((media) => Media( + media['uri'], + extras: media['extras'], + httpHeaders: media['httpHeaders'], + )) + .toList(), + index: json['playlist']['index'], + ), + collections: List.from(json['collections']), + ); + } + + Map toJson() { + return { + 'playing': playing, + 'loopMode': loopMode.name, + 'shuffled': shuffled, + 'playlist': { + 'medias': playlist.medias + .map((media) => { + 'uri': media.uri, + 'extras': media.extras, + 'httpHeaders': media.httpHeaders, + }) + .toList(), + 'index': playlist.index, + }, + 'collections': collections, + }; + } + AudioPlayerState copyWith({ bool? playing, - double? volume, PlaylistMode? loopMode, bool? shuffled, Playlist? playlist, + List? collections, }) { return AudioPlayerState( playing: playing ?? this.playing, - volume: volume ?? this.volume, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, playlist: playlist ?? this.playlist, + collections: collections ?? this.collections, tracks: playlist == null ? tracks : null, ); } + + Track? get activeTrack { + if (playlist.index == -1) return null; + return tracks.elementAtOrNull(playlist.index); + } + + bool containsTrack(Track track) { + return tracks.any((t) => t.id == track.id); + } + + bool containsTracks(List tracks) { + return tracks.every(containsTrack); + } + + bool containsCollection(String collectionId) { + return collections.contains(collectionId); + } } diff --git a/lib/provider/connect/connect.dart b/lib/provider/connect/connect.dart index c6014445..28eb131b 100644 --- a/lib/provider/connect/connect.dart +++ b/lib/provider/connect/connect.dart @@ -1,13 +1,14 @@ import 'dart:convert'; import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotube/provider/audio_player/state.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/connect/clients.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; @@ -31,8 +32,14 @@ final loopModeProvider = StateProvider( (ref) => PlaylistMode.none, ); -final queueProvider = StateProvider( - (ref) => ProxyPlaylist({}), +final queueProvider = StateProvider( + (ref) => AudioPlayerState( + playing: audioPlayer.isPlaying, + loopMode: audioPlayer.loopMode, + shuffled: audioPlayer.isShuffled, + playlist: audioPlayer.playlist, + collections: [], + ), ); final volumeProvider = StateProvider( diff --git a/lib/provider/discord_provider.dart b/lib/provider/discord_provider.dart index f90db54a..29c53762 100644 --- a/lib/provider/discord_provider.dart +++ b/lib/provider/discord_provider.dart @@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/extensions/artist_simple.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/platform.dart'; @@ -55,7 +55,7 @@ final discordProvider = ChangeNotifierProvider( (ref) { final isEnabled = ref.watch(userPreferencesProvider.select((s) => s.discordPresence)); - final playback = ref.read(proxyPlaylistProvider); + final playback = ref.read(audioPlayerProvider); final discord = Discord(isEnabled); if (playback.activeTrack != null) { diff --git a/lib/provider/proxy_playlist/proxy_playlist.dart b/lib/provider/proxy_playlist/proxy_playlist.dart deleted file mode 100644 index 9f371b7a..00000000 --- a/lib/provider/proxy_playlist/proxy_playlist.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/models/local_track.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; - -class ProxyPlaylist { - final Set tracks; - final Set collections; - final int? active; - - ProxyPlaylist(this.tracks, [this.active, this.collections = const {}]); - - factory ProxyPlaylist.fromJson( - Map json, - ) { - return ProxyPlaylist( - List.castFrom>( - json['tracks'] ?? >[], - ).map((t) => _makeAppropriateTrack(t)).toSet(), - json['active'] as int?, - json['collections'] == null - ? {} - : (json['collections'] as List).toSet().cast(), - ); - } - - factory ProxyPlaylist.fromJsonRaw(Map json) => ProxyPlaylist( - json['tracks'] == null - ? {} - : (json['tracks'] as List).map((t) => Track.fromJson(t)).toSet(), - json['active'] as int?, - json['collections'] == null - ? {} - : (json['collections'] as List).toSet().cast(), - ); - - Track? get activeTrack => - active == null || active == -1 ? null : tracks.elementAtOrNull(active!); - - bool get isFetching => activeTrack == null && tracks.isNotEmpty; - - bool containsCollection(String collection) { - return collections.contains(collection); - } - - bool containsTrack(TrackSimple track) { - return tracks.firstWhereOrNull((element) { - if (element is LocalTrack && track is LocalTrack) { - return element.path == track.path; - } - - return element.id == track.id; - }) != - null; - } - - bool containsTracks(Iterable tracks) { - if (tracks.isEmpty) return false; - return tracks.every(containsTrack); - } - - static Track _makeAppropriateTrack(Map track) { - if (track.containsKey("path")) { - return LocalTrack.fromJson(track); - } else { - return Track.fromJson(track); - } - } - - /// To make sure proper instance method is used for JSON serialization - /// Otherwise default super.toJson() is used - static Map _makeAppropriateTrackJson(Track track) { - return switch (track) { - // ignore: unnecessary_cast - LocalTrack() => (track as LocalTrack).toJson(), - // ignore: unnecessary_cast - SourcedTrack() => (track as SourcedTrack).toJson(), - _ => track.toJson(), - }; - } - - Map toJson() { - return { - 'tracks': tracks.map(_makeAppropriateTrackJson).toList(), - 'active': active, - 'collections': collections.toList(), - }; - } - - ProxyPlaylist copyWith({ - Set? tracks, - int? active, - Set? collections, - }) { - return ProxyPlaylist( - tracks ?? this.tracks, - active ?? this.active, - collections ?? this.collections, - ); - } -} diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart deleted file mode 100644 index 067d8d44..00000000 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ /dev/null @@ -1,215 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/track.dart'; -import 'package:spotube/models/local_track.dart'; - -import 'package:spotube/provider/blacklist_provider.dart'; -import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/palette_provider.dart'; -import 'package:spotube/provider/proxy_playlist/player_listeners.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/provider/scrobbler/scrobbler.dart'; -import 'package:spotube/provider/server/sourced_track.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; - -import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_services/audio_services.dart'; -import 'package:spotube/provider/discord_provider.dart'; - -import 'package:spotube/utils/persisted_state_notifier.dart'; - -class ProxyPlaylistNotifier extends PersistedStateNotifier { - final Ref ref; - late final AudioServices notificationService; - - ScrobblerNotifier get scrobbler => ref.read(scrobblerProvider.notifier); - UserPreferences get preferences => ref.read(userPreferencesProvider); - ProxyPlaylist get playlist => state; - BlackListNotifier get blacklist => ref.read(blacklistProvider.notifier); - Discord get discord => ref.read(discordProvider); - PlaybackHistoryNotifier get history => - ref.read(playbackHistoryProvider.notifier); - - List _subscriptions = []; - - ProxyPlaylistNotifier(this.ref) : super(ProxyPlaylist({}), "playlist") { - AudioServices.create(ref, this).then( - (value) => notificationService = value, - ); - - _subscriptions = [ - // These are subscription methods from player_listeners.dart - subscribeToPlaylist(), - subscribeToSkipSponsor(), - subscribeToPosition(), - subscribeToScrobbleChanged(), - ]; - } - // Basic methods for adding or removing tracks to playlist - - Future addTrack(Track track) async { - if (blacklist.contains(track)) return; - await audioPlayer.addTrack(SpotubeMedia(track)); - } - - Future addTracks(Iterable tracks) async { - tracks = blacklist.filter(tracks).toList() as List; - for (final track in tracks) { - await audioPlayer.addTrack(SpotubeMedia(track)); - } - } - - void addCollection(String collectionId) { - state = state.copyWith(collections: { - ...state.collections, - collectionId, - }); - } - - void removeCollection(String collectionId) { - state = state.copyWith(collections: { - ...state.collections..remove(collectionId), - }); - } - - Future removeTrack(String trackId) async { - final trackIndex = - state.tracks.toList().indexWhere((element) => element.id == trackId); - if (trackIndex == -1) return; - await audioPlayer.removeTrack(trackIndex); - } - - Future removeTracks(Iterable tracksIds) async { - final tracks = state.tracks.map((t) => t.id!).toList(); - - for (final track in tracks) { - final index = tracks.indexOf(track); - if (index == -1) continue; - await audioPlayer.removeTrack(index); - } - } - - Future load( - Iterable tracks, { - int initialIndex = 0, - bool autoPlay = false, - }) async { - tracks = blacklist.filter(tracks).toList() as List; - - state = state.copyWith(collections: {}); - - // Giving the initial track a boost so MediaKit won't skip - // because of timeout - final intendedActiveTrack = tracks.elementAt(initialIndex); - if (intendedActiveTrack is! LocalTrack) { - await ref.read(sourcedTrackProvider(intendedActiveTrack).future); - } - - await audioPlayer.openPlaylist( - tracks.asMediaList(), - initialIndex: initialIndex, - autoPlay: autoPlay, - ); - } - - Future jumpTo(int index) async { - await audioPlayer.jumpTo(index); - } - - Future jumpToTrack(Track track) async { - final index = - state.tracks.toList().indexWhere((element) => element.id == track.id); - if (index == -1) return; - await jumpTo(index); - } - - Future moveTrack(int oldIndex, int newIndex) async { - if (oldIndex == newIndex || - newIndex < 0 || - oldIndex < 0 || - newIndex > state.tracks.length - 1 || - oldIndex > state.tracks.length - 1) return; - - await audioPlayer.moveTrack(oldIndex, newIndex); - } - - Future addTracksAtFirst(Iterable tracks) async { - if (state.tracks.length == 1) { - return addTracks(tracks); - } - - tracks = blacklist.filter(tracks).toList() as List; - - for (int i = 0; i < tracks.length; i++) { - final track = tracks.elementAt(i); - - await audioPlayer.addTrackAt( - SpotubeMedia(track), - (state.active ?? 0) + i + 1, - ); - } - } - - Future next() async { - await audioPlayer.skipToNext(); - } - - Future previous() async { - await audioPlayer.skipToPrevious(); - } - - Future stop() async { - state = ProxyPlaylist({}); - await audioPlayer.stop(); - discord.clear(); - } - - @override - set state(state) { - super.state = state; - if (state.tracks.isEmpty && ref.read(paletteProvider) != null) { - ref.read(paletteProvider.notifier).state = null; - } else { - updatePalette(); - } - } - - @override - onInit() async { - if (state.tracks.isEmpty) return null; - final oldCollections = state.collections; - await load( - state.tracks, - initialIndex: max(state.active ?? 0, 0), - autoPlay: false, - ); - state = state.copyWith(collections: oldCollections); - } - - @override - FutureOr fromJson(Map json) { - return ProxyPlaylist.fromJson(json); - } - - @override - Map toJson() { - final json = state.toJson(); - return json; - } - - @override - void dispose() { - for (final subscription in _subscriptions) { - subscription.cancel(); - } - super.dispose(); - } -} - -final proxyPlaylistProvider = - StateNotifierProvider( - (ref) => ProxyPlaylistNotifier(ref), -); diff --git a/lib/provider/server/active_sourced_track.dart b/lib/provider/server/active_sourced_track.dart index 410b788c..685896ec 100644 --- a/lib/provider/server/active_sourced_track.dart +++ b/lib/provider/server/active_sourced_track.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; @@ -28,7 +28,7 @@ class ActiveSourcedTrackNotifier extends Notifier { state = newTrack; await audioPlayer.pause(); - final playbackNotifier = ref.read(proxyPlaylistProvider.notifier); + final playbackNotifier = ref.read(audioPlayerProvider.notifier); final oldActiveIndex = audioPlayer.currentIndex; await playbackNotifier.addTracksAtFirst([newTrack]); diff --git a/lib/provider/server/routes/connect.dart b/lib/provider/server/routes/connect.dart index eee3365e..a2fa70b8 100644 --- a/lib/provider/server/routes/connect.dart +++ b/lib/provider/server/routes/connect.dart @@ -9,7 +9,7 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/provider/history/history.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -38,8 +38,8 @@ class ServerConnectRoutes { }); } - ProxyPlaylistNotifier get playbackNotifier => - ref.read(proxyPlaylistProvider.notifier); + AudioPlayerNotifier get audioPlayerNotifier => + ref.read(audioPlayerProvider.notifier); PlaybackHistoryNotifier get historyNotifier => ref.read(playbackHistoryProvider.notifier); Stream get connectClientStream => @@ -57,7 +57,7 @@ class ServerConnectRoutes { _connectClientStreamController.add(origin); ref.listen( - proxyPlaylistProvider, + audioPlayerProvider, (previous, next) { channel.sink.addEvent(WebSocketQueueEvent(next)); }, @@ -67,10 +67,10 @@ class ServerConnectRoutes { // because audioPlayer events doesn't fireImmediately channel.sink.addEvent(WebSocketPlayingEvent(audioPlayer.isPlaying)); channel.sink.addEvent( - WebSocketPositionEvent(await audioPlayer.position ?? Duration.zero), + WebSocketPositionEvent(audioPlayer.position), ); channel.sink.addEvent( - WebSocketDurationEvent(await audioPlayer.duration ?? Duration.zero), + WebSocketDurationEvent(audioPlayer.duration), ); channel.sink.addEvent(WebSocketShuffleEvent(audioPlayer.isShuffled)); channel.sink.addEvent(WebSocketLoopEvent(audioPlayer.loopMode)); @@ -116,14 +116,14 @@ class ServerConnectRoutes { ); event.onLoad((event) async { - await playbackNotifier.load( + await audioPlayerNotifier.load( event.data.tracks, autoPlay: true, initialIndex: event.data.initialIndex ?? 0, ); if (event.data.collectionId == null) return; - playbackNotifier.addCollection(event.data.collectionId!); + audioPlayerNotifier.addCollection(event.data.collectionId!); if (event.data.collection is AlbumSimple) { historyNotifier .addAlbums([event.data.collection as AlbumSimple]); @@ -146,15 +146,15 @@ class ServerConnectRoutes { }); event.onNext((event) async { - await playbackNotifier.next(); + await audioPlayer.skipToNext(); }); event.onPrevious((event) async { - await playbackNotifier.previous(); + await audioPlayer.skipToPrevious(); }); event.onJump((event) async { - await playbackNotifier.jumpTo(event.data); + await audioPlayer.jumpTo(event.data); }); event.onSeek((event) async { @@ -170,15 +170,15 @@ class ServerConnectRoutes { }); event.onAddTrack((event) async { - await playbackNotifier.addTrack(event.data); + await audioPlayerNotifier.addTrack(event.data); }); event.onRemoveTrack((event) async { - await playbackNotifier.removeTrack(event.data); + await audioPlayerNotifier.removeTrack(event.data); }); event.onReorder((event) async { - await playbackNotifier.moveTrack( + await audioPlayerNotifier.moveTrack( event.data.oldIndex, event.data.newIndex, ); diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index 679f58b1..f29aecf4 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -2,8 +2,8 @@ import 'package:dio/dio.dart' hide Response; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shelf/shelf.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/server/active_sourced_track.dart'; import 'package:spotube/provider/server/sourced_track.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -12,7 +12,7 @@ import 'package:spotube/services/logger/logger.dart'; class ServerPlaybackRoutes { final Ref ref; UserPreferences get userPreferences => ref.read(userPreferencesProvider); - ProxyPlaylist get playlist => ref.read(proxyPlaylistProvider); + AudioPlayerState get playlist => ref.read(audioPlayerProvider); final Dio dio; ServerPlaybackRoutes(this.ref) : dio = Dio(); diff --git a/lib/provider/server/sourced_track.dart b/lib/provider/server/sourced_track.dart index 82c7ddcd..37c889b0 100644 --- a/lib/provider/server/sourced_track.dart +++ b/lib/provider/server/sourced_track.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/local_track.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; final sourcedTrackProvider = @@ -12,10 +12,9 @@ final sourcedTrackProvider = } ref.listen( - proxyPlaylistProvider, + audioPlayerProvider.select((value) => value.tracks), (old, next) { - if (next.tracks.isEmpty || - next.tracks.none((element) => element.id == track.id)) { + if (next.isEmpty || next.none((element) => element.id == track.id)) { ref.invalidateSelf(); } }, diff --git a/lib/provider/proxy_playlist/skip_segments.dart b/lib/provider/skip_segments/skip_segments.dart similarity index 100% rename from lib/provider/proxy_playlist/skip_segments.dart rename to lib/provider/skip_segments/skip_segments.dart diff --git a/lib/provider/tray_manager/tray_menu.dart b/lib/provider/tray_manager/tray_menu.dart index 35aca4f5..42a3f948 100644 --- a/lib/provider/tray_manager/tray_menu.dart +++ b/lib/provider/tray_manager/tray_menu.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:tray_manager/tray_manager.dart'; @@ -19,9 +19,9 @@ final audioPlayerPlaying = StreamProvider((ref) { }); final trayMenuProvider = Provider((ref) { - final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final isPlaybackPlaying = - ref.watch(proxyPlaylistProvider.select((s) => s.activeTrack != null)); + ref.watch(audioPlayerProvider.select((s) => s.activeTrack != null)); final isLoopOne = ref.watch(audioPlayerLoopMode).asData?.value == PlaylistMode.single; final isShuffled = ref.watch(audioPlayerShuffleMode).asData?.value ?? false; @@ -56,14 +56,14 @@ final trayMenuProvider = Provider((ref) { label: "Next", disabled: !isPlaybackPlaying, onClick: (menuItem) { - playlistNotifier.next(); + audioPlayer.skipToNext(); }, ), MenuItem( label: "Previous", disabled: !isPlaybackPlaying, onClick: (menuItem) { - playlistNotifier.previous(); + audioPlayer.skipToPrevious(); }, ), MenuItem.submenu( diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index a730c313..a421e7d0 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -6,10 +6,9 @@ import 'package:path_provider/path_provider.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; +import 'package:spotube/provider/audio_player/audio_player_streams.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/palette_provider.dart'; -import 'package:spotube/provider/proxy_playlist/player_listeners.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/utils/platform.dart'; @@ -115,7 +114,7 @@ class UserPreferencesNotifier extends Notifier { if (!sync) { ref.read(paletteProvider.notifier).state = null; } else { - ref.read(proxyPlaylistProvider.notifier).updatePalette(); + ref.read(audioPlayerStreamListenersProvider).updatePalette(); } } diff --git a/lib/services/audio_services/audio_services.dart b/lib/services/audio_services/audio_services.dart index f42d6c4b..63e43c4d 100644 --- a/lib/services/audio_services/audio_services.dart +++ b/lib/services/audio_services/audio_services.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/mobile_audio_service.dart'; import 'package:spotube/services/audio_services/windows_audio_service.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; @@ -17,7 +17,7 @@ class AudioServices { static Future create( Ref ref, - ProxyPlaylistNotifier playback, + AudioPlayerNotifier playback, ) async { final mobile = kIsMobile || kIsMacOS || kIsLinux ? await AudioService.init( diff --git a/lib/services/audio_services/mobile_audio_service.dart b/lib/services/audio_services/mobile_audio_service.dart index 3dbae18f..cdd16138 100644 --- a/lib/services/audio_services/mobile_audio_service.dart +++ b/lib/services/audio_services/mobile_audio_service.dart @@ -2,19 +2,19 @@ import 'dart:async'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:media_kit/media_kit.dart' hide Track; class MobileAudioService extends BaseAudioHandler { AudioSession? session; - final ProxyPlaylistNotifier playlistNotifier; + final AudioPlayerNotifier audioPlayerNotifier; // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member - ProxyPlaylist get playlist => playlistNotifier.state; + AudioPlayerState get playlist => audioPlayerNotifier.state; - MobileAudioService(this.playlistNotifier) { + MobileAudioService(this.audioPlayerNotifier) { AudioSession.instance.then((s) { session = s; session?.configure(const AudioSessionConfiguration.music()); @@ -102,24 +102,24 @@ class MobileAudioService extends BaseAudioHandler { @override Future stop() async { - await playlistNotifier.stop(); + await audioPlayerNotifier.stop(); } @override Future skipToNext() async { - await playlistNotifier.next(); + await audioPlayer.skipToNext(); await super.skipToNext(); } @override Future skipToPrevious() async { - await playlistNotifier.previous(); + await audioPlayer.skipToPrevious(); await super.skipToPrevious(); } @override Future onTaskRemoved() async { - await playlistNotifier.stop(); + await audioPlayerNotifier.stop(); return super.onTaskRemoved(); } @@ -146,7 +146,7 @@ class MobileAudioService extends BaseAudioHandler { PlaylistMode.single => AudioServiceRepeatMode.one, _ => AudioServiceRepeatMode.none, }, - processingState: playlist.isFetching == true + processingState: audioPlayer.isBuffering ? AudioProcessingState.loading : AudioProcessingState.ready, ); diff --git a/lib/services/audio_services/windows_audio_service.dart b/lib/services/audio_services/windows_audio_service.dart index a3ee31e1..0b3113fc 100644 --- a/lib/services/audio_services/windows_audio_service.dart +++ b/lib/services/audio_services/windows_audio_service.dart @@ -5,18 +5,18 @@ import 'package:smtc_windows/smtc_windows.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/playback_state.dart'; class WindowsAudioService { final SMTCWindows smtc; final Ref ref; - final ProxyPlaylistNotifier playlistNotifier; + final AudioPlayerNotifier audioPlayerNotifier; final subscriptions = []; - WindowsAudioService(this.ref, this.playlistNotifier) + WindowsAudioService(this.ref, this.audioPlayerNotifier) : smtc = SMTCWindows(enabled: false) { smtc.setPlaybackStatus(PlaybackStatus.Stopped); final buttonStream = smtc.buttonPressStream.listen((event) { @@ -28,13 +28,13 @@ class WindowsAudioService { audioPlayer.pause(); break; case PressedButton.next: - playlistNotifier.next(); + audioPlayer.skipToNext(); break; case PressedButton.previous: - playlistNotifier.previous(); + audioPlayer.skipToPrevious(); break; case PressedButton.stop: - playlistNotifier.stop(); + audioPlayerNotifier.stop(); break; default: break;