diff --git a/lib/components/Playlist/PlaylistView.dart b/lib/components/Playlist/PlaylistView.dart index 9db0c82e..247520f8 100644 --- a/lib/components/Playlist/PlaylistView.dart +++ b/lib/components/Playlist/PlaylistView.dart @@ -23,7 +23,7 @@ class PlaylistView extends HookConsumerWidget { playPlaylist(Playback playback, List tracks, {Track? currentTrack}) async { currentTrack ??= tracks.first; - var isPlaylistPlaying = playback.currentPlaylist?.id != null && + final isPlaylistPlaying = playback.currentPlaylist?.id != null && playback.currentPlaylist?.id == playlist.id; if (!isPlaylistPlaying) { playback.setCurrentPlaylist = CurrentPlaylist( diff --git a/lib/models/CurrentPlaylist.dart b/lib/models/CurrentPlaylist.dart index 11523220..75322941 100644 --- a/lib/models/CurrentPlaylist.dart +++ b/lib/models/CurrentPlaylist.dart @@ -1,5 +1,60 @@ import 'package:spotify/spotify.dart'; +extension AlbumJson on AlbumSimple { + Map toJson() { + return { + "albumType": albumType, + "id": id, + "name": name, + "images": images + ?.map((image) => { + "height": image.height, + "url": image.url, + "width": image.width, + }) + .toList(), + }; + } +} + +extension ArtistJson on ArtistSimple { + Map toJson() { + return { + "href": href, + "id": id, + "name": name, + "type": type, + "uri": uri, + }; + } +} + +extension TrackJson on Track { + Map toJson() { + return { + "album": album?.toJson(), + "artists": artists?.map((artist) => artist.toJson()).toList(), + "availableMarkets": availableMarkets, + "discNumber": discNumber, + "duration": duration.toString(), + "durationMs": durationMs, + "explicit": explicit, + // "externalIds": externalIds, + // "externalUrls": externalUrls, + "href": href, + "id": id, + "isPlayable": isPlayable, + // "linkedFrom": linkedFrom, + "name": name, + "popularity": popularity, + "previewUrl": previewUrl, + "trackNumber": trackNumber, + "type": type, + "uri": uri, + }; + } +} + class CurrentPlaylist { List? _tempTrack; List tracks; @@ -14,6 +69,16 @@ class CurrentPlaylist { required this.thumbnail, }); + static CurrentPlaylist fromJson(Map map) { + return CurrentPlaylist( + id: map["id"], + tracks: List.castFrom( + map["tracks"].map((track) => Track.fromJson(track)).toList()), + name: map["name"], + thumbnail: map["thumbnail"], + ); + } + List get trackIds => tracks.map((e) => e.id!).toList(); bool shuffle() { @@ -35,4 +100,13 @@ class CurrentPlaylist { } return false; } + + Map toJson() { + return { + "id": id, + "name": name, + "tracks": tracks.map((track) => track.toJson()).toList(), + "thumbnail": thumbnail, + }; + } } diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index ad41ae24..2abcf985 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.dart @@ -1,9 +1,10 @@ import 'dart:async'; +import 'dart:convert'; import 'package:audio_service/audio_service.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/image-to-url-string.dart'; @@ -13,9 +14,10 @@ import 'package:spotube/models/Logger.dart'; import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/utils/AudioPlayerHandler.dart'; +import 'package:spotube/utils/PersistedChangeNotifier.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; -class Playback extends ChangeNotifier { +class Playback extends PersistedChangeNotifier { AudioSource? _currentAudioSource; final _logger = getLogger(Playback); CurrentPlaylist? _currentPlaylist; @@ -38,13 +40,15 @@ class Playback extends ChangeNotifier { CurrentPlaylist? currentPlaylist, Track? currentTrack, }) : _currentPlaylist = currentPlaylist, - _currentTrack = currentTrack { + _currentTrack = currentTrack, + super() { player.onNextRequest = () { movePlaylistPositionBy(1); }; player.onPreviousRequest = () { movePlaylistPositionBy(-1); }; + _init(); } @@ -52,7 +56,7 @@ class Playback extends ChangeNotifier { StreamSubscription? _positionStream; StreamSubscription? _playingStream; - void _init() { + void _init() async { _playingStream = player.core.playingStream.listen( (playing) { _isPlaying = playing; @@ -119,12 +123,14 @@ class Playback extends ChangeNotifier { _logger.v("[Setting Current Track] ${track.name} - ${track.id}"); _currentTrack = track; notifyListeners(); + updatePersistence(); } set setCurrentPlaylist(CurrentPlaylist playlist) { _logger.v("[Current Playlist Changed] ${playlist.name} - ${playlist.id}"); _currentPlaylist = playlist; notifyListeners(); + updatePersistence(); } void reset() { @@ -135,6 +141,7 @@ class Playback extends ChangeNotifier { _currentPlaylist = null; _currentTrack = null; notifyListeners(); + updatePersistence(clearNullEntries: true); } /// sets the provided id matched track's uri\ @@ -147,6 +154,7 @@ class Playback extends ChangeNotifier { _currentPlaylist!.tracks.indexWhere((element) => element.id == id); if (index == -1) return false; _currentPlaylist!.tracks[index].uri = uri; + updatePersistence(); return _currentPlaylist!.tracks[index].uri == uri; } catch (e) { return false; @@ -170,6 +178,7 @@ class Playback extends ChangeNotifier { duration = null; _currentTrack = track; notifyListeners(); + updatePersistence(); // starts to play the newly entered next/prev track startPlaying(); } @@ -202,6 +211,7 @@ class Playback extends ChangeNotifier { .then((value) async { _currentTrack = track; notifyListeners(); + updatePersistence(); }); // await player.play(); return; @@ -215,6 +225,7 @@ class Playback extends ChangeNotifier { audioQuality: preferences.audioQuality, ); if (setTrackUriById(track.id!, spotubeTrack.ytUri)) { + logger.v("[Track Direct Source] - ${spotubeTrack.ytUri}"); _currentAudioSource = AudioSource.uri(Uri.parse(spotubeTrack.ytUri)); await player.core .setAudioSource( @@ -224,6 +235,7 @@ class Playback extends ChangeNotifier { .then((value) { _currentTrack = spotubeTrack; notifyListeners(); + updatePersistence(); }); // await player.play(); } @@ -246,6 +258,36 @@ class Playback extends ChangeNotifier { notifyListeners(); } } + + @override + FutureOr loadFromLocal(Map map) { + if (map["currentPlaylist"] != null) { + _currentPlaylist = + CurrentPlaylist.fromJson(jsonDecode(map["currentPlaylist"])); + } + if (map["currentTrack"] != null) { + _currentTrack = Track.fromJson(jsonDecode(map["currentTrack"])); + startPlaying().then((_) { + Timer.periodic(const Duration(milliseconds: 100), (timer) { + if (player.core.playing) { + player.pause(); + timer.cancel(); + } + }); + }); + } + } + + @override + FutureOr> toMap() { + return { + "currentPlaylist": currentPlaylist != null + ? jsonEncode(currentPlaylist?.toJson()) + : null, + "currentTrack": + currentTrack != null ? jsonEncode(currentTrack?.toJson()) : null, + }; + } } final playbackProvider = ChangeNotifierProvider((ref) { diff --git a/lib/utils/PersistedChangeNotifier.dart b/lib/utils/PersistedChangeNotifier.dart index 556984c7..d48cb67a 100644 --- a/lib/utils/PersistedChangeNotifier.dart +++ b/lib/utils/PersistedChangeNotifier.dart @@ -37,7 +37,7 @@ abstract class PersistedChangeNotifier extends ChangeNotifier { FutureOr> toMap(); - Future updatePersistence() async { + Future updatePersistence({bool clearNullEntries = false}) async { for (final entry in (await toMap()).entries) { if (entry.value is bool) { await _localStorage.setBool(entry.key, entry.value); @@ -47,6 +47,8 @@ abstract class PersistedChangeNotifier extends ChangeNotifier { await _localStorage.setDouble(entry.key, entry.value); } else if (entry.value is String) { await _localStorage.setString(entry.key, entry.value); + } else if (entry.value == null && clearNullEntries) { + _localStorage.remove(entry.key); } } }