mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: replace all instances of proxy playlist
This commit is contained in:
parent
f79fedefd4
commit
a83dd64476
@ -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<SeekIntent> {
|
||||
@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<SeekIntent> {
|
||||
);
|
||||
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,
|
||||
|
@ -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),
|
||||
|
@ -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<Widget>? 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,
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -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();
|
||||
|
@ -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';
|
||||
|
@ -325,12 +325,12 @@ class WebSocketErrorEvent extends WebSocketEvent<String> {
|
||||
WebSocketErrorEvent(String data) : super(WsEvent.error, data);
|
||||
}
|
||||
|
||||
class WebSocketQueueEvent extends WebSocketEvent<ProxyPlaylist> {
|
||||
WebSocketQueueEvent(ProxyPlaylist data) : super(WsEvent.queue, data);
|
||||
class WebSocketQueueEvent extends WebSocketEvent<AudioPlayerState> {
|
||||
WebSocketQueueEvent(AudioPlayerState data) : super(WsEvent.queue, data);
|
||||
|
||||
factory WebSocketQueueEvent.fromJson(Map<String, dynamic> json) =>
|
||||
WebSocketQueueEvent(
|
||||
ProxyPlaylist.fromJsonRaw(json),
|
||||
AudioPlayerState.fromJson(json),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<double> volume = GeneratedColumn<double>(
|
||||
'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<List<String>, String>
|
||||
collections = GeneratedColumn<String>('collections', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<List<String>>(
|
||||
$AudioPlayerStateTableTable.$convertercollections);
|
||||
@override
|
||||
List<GeneratedColumn> 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<PlaylistMode, String, String> $converterloopMode =
|
||||
const EnumNameConverter<PlaylistMode>(PlaylistMode.values);
|
||||
static TypeConverter<List<String>, String> $convertercollections =
|
||||
const StringListConverter();
|
||||
}
|
||||
|
||||
class AudioPlayerStateTableData extends DataClass
|
||||
implements Insertable<AudioPlayerStateTableData> {
|
||||
final int id;
|
||||
final bool playing;
|
||||
final double volume;
|
||||
final PlaylistMode loopMode;
|
||||
final bool shuffled;
|
||||
final List<String> 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<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<int>(id);
|
||||
map['playing'] = Variable<bool>(playing);
|
||||
map['volume'] = Variable<double>(volume);
|
||||
{
|
||||
map['loop_mode'] = Variable<String>(
|
||||
$AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode));
|
||||
}
|
||||
map['shuffled'] = Variable<bool>(shuffled);
|
||||
{
|
||||
map['collections'] = Variable<String>(
|
||||
$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<int>(json['id']),
|
||||
playing: serializer.fromJson<bool>(json['playing']),
|
||||
volume: serializer.fromJson<double>(json['volume']),
|
||||
loopMode: $AudioPlayerStateTableTable.$converterloopMode
|
||||
.fromJson(serializer.fromJson<String>(json['loopMode'])),
|
||||
shuffled: serializer.fromJson<bool>(json['shuffled']),
|
||||
collections: serializer.fromJson<List<String>>(json['collections']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
@ -2745,103 +2749,103 @@ class AudioPlayerStateTableData extends DataClass
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<int>(id),
|
||||
'playing': serializer.toJson<bool>(playing),
|
||||
'volume': serializer.toJson<double>(volume),
|
||||
'loopMode': serializer.toJson<String>(
|
||||
$AudioPlayerStateTableTable.$converterloopMode.toJson(loopMode)),
|
||||
'shuffled': serializer.toJson<bool>(shuffled),
|
||||
'collections': serializer.toJson<List<String>>(collections),
|
||||
};
|
||||
}
|
||||
|
||||
AudioPlayerStateTableData copyWith(
|
||||
{int? id,
|
||||
bool? playing,
|
||||
double? volume,
|
||||
PlaylistMode? loopMode,
|
||||
bool? shuffled}) =>
|
||||
bool? shuffled,
|
||||
List<String>? 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<AudioPlayerStateTableData> {
|
||||
final Value<int> id;
|
||||
final Value<bool> playing;
|
||||
final Value<double> volume;
|
||||
final Value<PlaylistMode> loopMode;
|
||||
final Value<bool> shuffled;
|
||||
final Value<List<String>> 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<String> collections,
|
||||
}) : playing = Value(playing),
|
||||
volume = Value(volume),
|
||||
loopMode = Value(loopMode),
|
||||
shuffled = Value(shuffled);
|
||||
shuffled = Value(shuffled),
|
||||
collections = Value(collections);
|
||||
static Insertable<AudioPlayerStateTableData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<bool>? playing,
|
||||
Expression<double>? volume,
|
||||
Expression<String>? loopMode,
|
||||
Expression<bool>? shuffled,
|
||||
Expression<String>? 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<int>? id,
|
||||
Value<bool>? playing,
|
||||
Value<double>? volume,
|
||||
Value<PlaylistMode>? loopMode,
|
||||
Value<bool>? shuffled}) {
|
||||
Value<bool>? shuffled,
|
||||
Value<List<String>>? 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<bool>(playing.value);
|
||||
}
|
||||
if (volume.present) {
|
||||
map['volume'] = Variable<double>(volume.value);
|
||||
}
|
||||
if (loopMode.present) {
|
||||
map['loop_mode'] = Variable<String>(
|
||||
$AudioPlayerStateTableTable.$converterloopMode.toSql(loopMode.value));
|
||||
@ -2864,6 +2865,11 @@ class AudioPlayerStateTableCompanion
|
||||
if (shuffled.present) {
|
||||
map['shuffled'] = Variable<bool>(shuffled.value);
|
||||
}
|
||||
if (collections.present) {
|
||||
map['collections'] = Variable<String>($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<int> id,
|
||||
required bool playing,
|
||||
required double volume,
|
||||
required PlaylistMode loopMode,
|
||||
required bool shuffled,
|
||||
required List<String> collections,
|
||||
});
|
||||
typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder
|
||||
= AudioPlayerStateTableCompanion Function({
|
||||
Value<int> id,
|
||||
Value<bool> playing,
|
||||
Value<double> volume,
|
||||
Value<PlaylistMode> loopMode,
|
||||
Value<bool> shuffled,
|
||||
Value<List<String>> collections,
|
||||
});
|
||||
|
||||
class $$AudioPlayerStateTableTableTableManager extends RootTableManager<
|
||||
@ -4627,30 +4633,30 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager<
|
||||
getUpdateCompanionBuilder: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
Value<bool> playing = const Value.absent(),
|
||||
Value<double> volume = const Value.absent(),
|
||||
Value<PlaylistMode> loopMode = const Value.absent(),
|
||||
Value<bool> shuffled = const Value.absent(),
|
||||
Value<List<String>> collections = const Value.absent(),
|
||||
}) =>
|
||||
AudioPlayerStateTableCompanion(
|
||||
id: id,
|
||||
playing: playing,
|
||||
volume: volume,
|
||||
loopMode: loopMode,
|
||||
shuffled: shuffled,
|
||||
collections: collections,
|
||||
),
|
||||
getInsertCompanionBuilder: ({
|
||||
Value<int> id = const Value.absent(),
|
||||
required bool playing,
|
||||
required double volume,
|
||||
required PlaylistMode loopMode,
|
||||
required bool shuffled,
|
||||
required List<String> 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<double> get volume => $state.composableBuilder(
|
||||
column: $state.table.volume,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnWithTypeConverterFilters<PlaylistMode, PlaylistMode, String>
|
||||
get loopMode => $state.composableBuilder(
|
||||
column: $state.table.loopMode,
|
||||
@ -4698,6 +4699,13 @@ class $$AudioPlayerStateTableTableFilterComposer
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnFilters(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnWithTypeConverterFilters<List<String>, List<String>, 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<double> get volume => $state.composableBuilder(
|
||||
column: $state.table.volume,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
|
||||
ColumnOrderings<String> 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<String> get collections => $state.composableBuilder(
|
||||
column: $state.table.collections,
|
||||
builder: (column, joinBuilders) =>
|
||||
ColumnOrderings(column, joinBuilders: joinBuilders));
|
||||
}
|
||||
|
||||
typedef $$PlaylistTableTableInsertCompanionBuilder = PlaylistTableCompanion
|
||||
|
@ -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<PlaylistMode>()();
|
||||
BoolColumn get shuffled => boolean()();
|
||||
TextColumn get collections => text().map(const StringListConverter())();
|
||||
}
|
||||
|
||||
class PlaylistTable extends Table {
|
||||
|
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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)),
|
||||
|
@ -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<PlayPauseIntent>(
|
||||
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<PlaylistMode>(
|
||||
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);
|
||||
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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<void> Function(Track track) onJump;
|
||||
final Future<void> 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;
|
||||
|
@ -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: [
|
||||
|
@ -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,
|
||||
]
|
||||
: <SourceInfo>[],
|
||||
[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);
|
||||
|
@ -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: () {
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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<LocalTrack> 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>(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() ?? []);
|
||||
|
@ -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,
|
||||
);
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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<bool>(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),
|
||||
);
|
||||
});
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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<Track>() ?? [];
|
||||
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(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<AudioPlayerState> {
|
||||
BlackListNotifier get _blacklist => ref.read(blacklistProvider.notifier);
|
||||
|
||||
Future<void> _syncSavedState() async {
|
||||
final database = ref.read(databaseProvider);
|
||||
|
||||
@ -18,9 +27,9 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
await database.into(database.audioPlayerStateTable).insert(
|
||||
AudioPlayerStateTableCompanion.insert(
|
||||
playing: audioPlayer.isPlaying,
|
||||
volume: audioPlayer.volume,
|
||||
loopMode: audioPlayer.loopMode,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
collections: <String>[],
|
||||
id: const Value(0),
|
||||
),
|
||||
);
|
||||
@ -28,7 +37,6 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
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<AudioPlayerState> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
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<AudioPlayerState> {
|
||||
playing: audioPlayer.isPlaying,
|
||||
playlist: audioPlayer.playlist,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
volume: audioPlayer.volume,
|
||||
collections: [],
|
||||
);
|
||||
}
|
||||
|
||||
// Collection related methods
|
||||
Future<void> addCollections(List<String> collectionIds) async {
|
||||
state = state.copyWith(collections: [
|
||||
...state.collections,
|
||||
...collectionIds,
|
||||
]);
|
||||
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
collections: Value(state.collections),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> addCollection(String collectionId) async {
|
||||
await addCollections([collectionId]);
|
||||
}
|
||||
|
||||
Future<void> removeCollections(List<String> collectionIds) async {
|
||||
state = state.copyWith(
|
||||
collections: state.collections
|
||||
.where((element) => !collectionIds.contains(element))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
await _updatePlayerState(
|
||||
AudioPlayerStateTableCompanion(
|
||||
collections: Value(state.collections),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeCollection(String collectionId) async {
|
||||
await removeCollections([collectionId]);
|
||||
}
|
||||
|
||||
// Tracks related methods
|
||||
|
||||
Future<void> addTracksAtFirst(Iterable<Track> tracks) async {
|
||||
if (state.tracks.length == 1) {
|
||||
return addTracks(tracks);
|
||||
}
|
||||
|
||||
tracks = _blacklist.filter(tracks).toList() as List<Track>;
|
||||
|
||||
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<void> addTrack(Track track) async {
|
||||
if (_blacklist.contains(track)) return;
|
||||
await audioPlayer.addTrack(SpotubeMedia(track));
|
||||
}
|
||||
|
||||
Future<void> addTracks(Iterable<Track> tracks) async {
|
||||
tracks = _blacklist.filter(tracks).toList() as List<Track>;
|
||||
for (final track in tracks) {
|
||||
await addTrack(track);
|
||||
await audioPlayer.addTrack(SpotubeMedia(track));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> removeTrack(Track track) async {
|
||||
final index = state.tracks.indexWhere((element) => element == track);
|
||||
Future<void> removeTrack(String trackId) async {
|
||||
final index = state.tracks.indexWhere((element) => element.id == trackId);
|
||||
|
||||
if (index == -1) return;
|
||||
|
||||
await audioPlayer.removeTrack(index);
|
||||
}
|
||||
|
||||
Future<void> removeTracks(Iterable<Track> tracks) async {
|
||||
for (final track in tracks) {
|
||||
await removeTrack(track);
|
||||
Future<void> removeTracks(Iterable<String> trackIds) async {
|
||||
for (final trackId in trackIds) {
|
||||
await removeTrack(trackId);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> load(
|
||||
List<Track> track, {
|
||||
required int initialIndex,
|
||||
List<Track> tracks, {
|
||||
int initialIndex = 0,
|
||||
bool autoPlay = false,
|
||||
}) async {
|
||||
tracks = _blacklist.filter(tracks).toList() as List<Track>;
|
||||
|
||||
// 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<void> jumpToTrack(Track track) async {
|
||||
final index =
|
||||
state.tracks.toList().indexWhere((element) => element.id == track.id);
|
||||
if (index == -1) return;
|
||||
await audioPlayer.jumpTo(index);
|
||||
}
|
||||
|
||||
Future<void> moveTrack(int oldIndex, int newIndex) async {
|
||||
if (oldIndex == newIndex ||
|
||||
newIndex < 0 ||
|
||||
oldIndex < 0 ||
|
||||
newIndex > state.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<void> stop() async {
|
||||
await audioPlayer.stop();
|
||||
ref.read(discordProvider.notifier).clear();
|
||||
}
|
||||
}
|
||||
|
||||
final audioPlayerProvider = NotifierProvider<AudioPlayerNotifier, AudioPlayerState>(
|
||||
final audioPlayerProvider =
|
||||
NotifierProvider<AudioPlayerNotifier, AudioPlayerState>(
|
||||
() => AudioPlayerNotifier(),
|
||||
);
|
@ -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<void> 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>(AudioPlayerStreamListeners.new);
|
@ -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<Track> tracks;
|
||||
final List<String> collections;
|
||||
|
||||
AudioPlayerState({
|
||||
required this.playing,
|
||||
required this.volume,
|
||||
required this.loopMode,
|
||||
required this.shuffled,
|
||||
required this.playlist,
|
||||
required this.collections,
|
||||
List<Track>? tracks,
|
||||
}) : tracks = tracks ??
|
||||
playlist.medias
|
||||
.map((media) => SpotubeMedia.fromMedia(media).track)
|
||||
.toList();
|
||||
|
||||
factory AudioPlayerState.fromJson(Map<String, dynamic> 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<String>.from(json['collections']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> 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<String>? 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<Track> tracks) {
|
||||
return tracks.every(containsTrack);
|
||||
}
|
||||
|
||||
bool containsCollection(String collectionId) {
|
||||
return collections.contains(collectionId);
|
||||
}
|
||||
}
|
||||
|
@ -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<PlaylistMode>(
|
||||
(ref) => PlaylistMode.none,
|
||||
);
|
||||
|
||||
final queueProvider = StateProvider<ProxyPlaylist>(
|
||||
(ref) => ProxyPlaylist({}),
|
||||
final queueProvider = StateProvider<AudioPlayerState>(
|
||||
(ref) => AudioPlayerState(
|
||||
playing: audioPlayer.isPlaying,
|
||||
loopMode: audioPlayer.loopMode,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
playlist: audioPlayer.playlist,
|
||||
collections: [],
|
||||
),
|
||||
);
|
||||
|
||||
final volumeProvider = StateProvider<double>(
|
||||
|
@ -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) {
|
||||
|
@ -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<Track> tracks;
|
||||
final Set<String> collections;
|
||||
final int? active;
|
||||
|
||||
ProxyPlaylist(this.tracks, [this.active, this.collections = const {}]);
|
||||
|
||||
factory ProxyPlaylist.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
) {
|
||||
return ProxyPlaylist(
|
||||
List.castFrom<dynamic, Map<String, dynamic>>(
|
||||
json['tracks'] ?? <Map<String, dynamic>>[],
|
||||
).map((t) => _makeAppropriateTrack(t)).toSet(),
|
||||
json['active'] as int?,
|
||||
json['collections'] == null
|
||||
? {}
|
||||
: (json['collections'] as List).toSet().cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
factory ProxyPlaylist.fromJsonRaw(Map<String, dynamic> json) => ProxyPlaylist(
|
||||
json['tracks'] == null
|
||||
? <Track>{}
|
||||
: (json['tracks'] as List).map((t) => Track.fromJson(t)).toSet(),
|
||||
json['active'] as int?,
|
||||
json['collections'] == null
|
||||
? {}
|
||||
: (json['collections'] as List).toSet().cast<String>(),
|
||||
);
|
||||
|
||||
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<TrackSimple> tracks) {
|
||||
if (tracks.isEmpty) return false;
|
||||
return tracks.every(containsTrack);
|
||||
}
|
||||
|
||||
static Track _makeAppropriateTrack(Map<String, dynamic> 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<String, dynamic> _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<String, dynamic> toJson() {
|
||||
return {
|
||||
'tracks': tracks.map(_makeAppropriateTrackJson).toList(),
|
||||
'active': active,
|
||||
'collections': collections.toList(),
|
||||
};
|
||||
}
|
||||
|
||||
ProxyPlaylist copyWith({
|
||||
Set<Track>? tracks,
|
||||
int? active,
|
||||
Set<String>? collections,
|
||||
}) {
|
||||
return ProxyPlaylist(
|
||||
tracks ?? this.tracks,
|
||||
active ?? this.active,
|
||||
collections ?? this.collections,
|
||||
);
|
||||
}
|
||||
}
|
@ -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<ProxyPlaylist> {
|
||||
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<StreamSubscription> _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<void> addTrack(Track track) async {
|
||||
if (blacklist.contains(track)) return;
|
||||
await audioPlayer.addTrack(SpotubeMedia(track));
|
||||
}
|
||||
|
||||
Future<void> addTracks(Iterable<Track> tracks) async {
|
||||
tracks = blacklist.filter(tracks).toList() as List<Track>;
|
||||
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<void> removeTrack(String trackId) async {
|
||||
final trackIndex =
|
||||
state.tracks.toList().indexWhere((element) => element.id == trackId);
|
||||
if (trackIndex == -1) return;
|
||||
await audioPlayer.removeTrack(trackIndex);
|
||||
}
|
||||
|
||||
Future<void> removeTracks(Iterable<String> 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<void> load(
|
||||
Iterable<Track> tracks, {
|
||||
int initialIndex = 0,
|
||||
bool autoPlay = false,
|
||||
}) async {
|
||||
tracks = blacklist.filter(tracks).toList() as List<Track>;
|
||||
|
||||
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<void> jumpTo(int index) async {
|
||||
await audioPlayer.jumpTo(index);
|
||||
}
|
||||
|
||||
Future<void> jumpToTrack(Track track) async {
|
||||
final index =
|
||||
state.tracks.toList().indexWhere((element) => element.id == track.id);
|
||||
if (index == -1) return;
|
||||
await jumpTo(index);
|
||||
}
|
||||
|
||||
Future<void> 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<void> addTracksAtFirst(Iterable<Track> tracks) async {
|
||||
if (state.tracks.length == 1) {
|
||||
return addTracks(tracks);
|
||||
}
|
||||
|
||||
tracks = blacklist.filter(tracks).toList() as List<Track>;
|
||||
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
final track = tracks.elementAt(i);
|
||||
|
||||
await audioPlayer.addTrackAt(
|
||||
SpotubeMedia(track),
|
||||
(state.active ?? 0) + i + 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> next() async {
|
||||
await audioPlayer.skipToNext();
|
||||
}
|
||||
|
||||
Future<void> previous() async {
|
||||
await audioPlayer.skipToPrevious();
|
||||
}
|
||||
|
||||
Future<void> 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<ProxyPlaylist> fromJson(Map<String, dynamic> json) {
|
||||
return ProxyPlaylist.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = state.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final subscription in _subscriptions) {
|
||||
subscription.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
final proxyPlaylistProvider =
|
||||
StateNotifierProvider<ProxyPlaylistNotifier, ProxyPlaylist>(
|
||||
(ref) => ProxyPlaylistNotifier(ref),
|
||||
);
|
@ -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<SourcedTrack?> {
|
||||
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]);
|
||||
|
@ -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<String> 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,
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
@ -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<bool>((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(
|
||||
|
@ -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<PreferencesTableData> {
|
||||
if (!sync) {
|
||||
ref.read(paletteProvider.notifier).state = null;
|
||||
} else {
|
||||
ref.read(proxyPlaylistProvider.notifier).updatePalette();
|
||||
ref.read(audioPlayerStreamListenersProvider).updatePalette();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<AudioServices> create(
|
||||
Ref ref,
|
||||
ProxyPlaylistNotifier playback,
|
||||
AudioPlayerNotifier playback,
|
||||
) async {
|
||||
final mobile = kIsMobile || kIsMacOS || kIsLinux
|
||||
? await AudioService.init(
|
||||
|
@ -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<void> stop() async {
|
||||
await playlistNotifier.stop();
|
||||
await audioPlayerNotifier.stop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToNext() async {
|
||||
await playlistNotifier.next();
|
||||
await audioPlayer.skipToNext();
|
||||
await super.skipToNext();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> skipToPrevious() async {
|
||||
await playlistNotifier.previous();
|
||||
await audioPlayer.skipToPrevious();
|
||||
await super.skipToPrevious();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> 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,
|
||||
);
|
||||
|
@ -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 = <StreamSubscription>[];
|
||||
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user