CurrentPlaylist cache & load from cache support

This commit is contained in:
Kingkor Roy Tirtho 2022-06-13 17:33:00 +06:00
parent 2b75076f82
commit b4bb93c81e
4 changed files with 124 additions and 6 deletions

View File

@ -23,7 +23,7 @@ class PlaylistView extends HookConsumerWidget {
playPlaylist(Playback playback, List<Track> tracks, playPlaylist(Playback playback, List<Track> tracks,
{Track? currentTrack}) async { {Track? currentTrack}) async {
currentTrack ??= tracks.first; currentTrack ??= tracks.first;
var isPlaylistPlaying = playback.currentPlaylist?.id != null && final isPlaylistPlaying = playback.currentPlaylist?.id != null &&
playback.currentPlaylist?.id == playlist.id; playback.currentPlaylist?.id == playlist.id;
if (!isPlaylistPlaying) { if (!isPlaylistPlaying) {
playback.setCurrentPlaylist = CurrentPlaylist( playback.setCurrentPlaylist = CurrentPlaylist(

View File

@ -1,5 +1,60 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
extension AlbumJson on AlbumSimple {
Map<String, dynamic> 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<String, dynamic> toJson() {
return {
"href": href,
"id": id,
"name": name,
"type": type,
"uri": uri,
};
}
}
extension TrackJson on Track {
Map<String, dynamic> 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 { class CurrentPlaylist {
List<Track>? _tempTrack; List<Track>? _tempTrack;
List<Track> tracks; List<Track> tracks;
@ -14,6 +69,16 @@ class CurrentPlaylist {
required this.thumbnail, required this.thumbnail,
}); });
static CurrentPlaylist fromJson(Map<String, dynamic> map) {
return CurrentPlaylist(
id: map["id"],
tracks: List.castFrom<dynamic, Track>(
map["tracks"].map((track) => Track.fromJson(track)).toList()),
name: map["name"],
thumbnail: map["thumbnail"],
);
}
List<String> get trackIds => tracks.map((e) => e.id!).toList(); List<String> get trackIds => tracks.map((e) => e.id!).toList();
bool shuffle() { bool shuffle() {
@ -35,4 +100,13 @@ class CurrentPlaylist {
} }
return false; return false;
} }
Map<String, dynamic> toJson() {
return {
"id": id,
"name": name,
"tracks": tracks.map((track) => track.toJson()).toList(),
"thumbnail": thumbnail,
};
}
} }

View File

@ -1,9 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/artist-to-string.dart';
import 'package:spotube/helpers/image-to-url-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/UserPreferences.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
import 'package:spotube/utils/AudioPlayerHandler.dart'; import 'package:spotube/utils/AudioPlayerHandler.dart';
import 'package:spotube/utils/PersistedChangeNotifier.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart';
class Playback extends ChangeNotifier { class Playback extends PersistedChangeNotifier {
AudioSource? _currentAudioSource; AudioSource? _currentAudioSource;
final _logger = getLogger(Playback); final _logger = getLogger(Playback);
CurrentPlaylist? _currentPlaylist; CurrentPlaylist? _currentPlaylist;
@ -38,13 +40,15 @@ class Playback extends ChangeNotifier {
CurrentPlaylist? currentPlaylist, CurrentPlaylist? currentPlaylist,
Track? currentTrack, Track? currentTrack,
}) : _currentPlaylist = currentPlaylist, }) : _currentPlaylist = currentPlaylist,
_currentTrack = currentTrack { _currentTrack = currentTrack,
super() {
player.onNextRequest = () { player.onNextRequest = () {
movePlaylistPositionBy(1); movePlaylistPositionBy(1);
}; };
player.onPreviousRequest = () { player.onPreviousRequest = () {
movePlaylistPositionBy(-1); movePlaylistPositionBy(-1);
}; };
_init(); _init();
} }
@ -52,7 +56,7 @@ class Playback extends ChangeNotifier {
StreamSubscription<Duration>? _positionStream; StreamSubscription<Duration>? _positionStream;
StreamSubscription<bool>? _playingStream; StreamSubscription<bool>? _playingStream;
void _init() { void _init() async {
_playingStream = player.core.playingStream.listen( _playingStream = player.core.playingStream.listen(
(playing) { (playing) {
_isPlaying = playing; _isPlaying = playing;
@ -119,12 +123,14 @@ class Playback extends ChangeNotifier {
_logger.v("[Setting Current Track] ${track.name} - ${track.id}"); _logger.v("[Setting Current Track] ${track.name} - ${track.id}");
_currentTrack = track; _currentTrack = track;
notifyListeners(); notifyListeners();
updatePersistence();
} }
set setCurrentPlaylist(CurrentPlaylist playlist) { set setCurrentPlaylist(CurrentPlaylist playlist) {
_logger.v("[Current Playlist Changed] ${playlist.name} - ${playlist.id}"); _logger.v("[Current Playlist Changed] ${playlist.name} - ${playlist.id}");
_currentPlaylist = playlist; _currentPlaylist = playlist;
notifyListeners(); notifyListeners();
updatePersistence();
} }
void reset() { void reset() {
@ -135,6 +141,7 @@ class Playback extends ChangeNotifier {
_currentPlaylist = null; _currentPlaylist = null;
_currentTrack = null; _currentTrack = null;
notifyListeners(); notifyListeners();
updatePersistence(clearNullEntries: true);
} }
/// sets the provided id matched track's uri\ /// sets the provided id matched track's uri\
@ -147,6 +154,7 @@ class Playback extends ChangeNotifier {
_currentPlaylist!.tracks.indexWhere((element) => element.id == id); _currentPlaylist!.tracks.indexWhere((element) => element.id == id);
if (index == -1) return false; if (index == -1) return false;
_currentPlaylist!.tracks[index].uri = uri; _currentPlaylist!.tracks[index].uri = uri;
updatePersistence();
return _currentPlaylist!.tracks[index].uri == uri; return _currentPlaylist!.tracks[index].uri == uri;
} catch (e) { } catch (e) {
return false; return false;
@ -170,6 +178,7 @@ class Playback extends ChangeNotifier {
duration = null; duration = null;
_currentTrack = track; _currentTrack = track;
notifyListeners(); notifyListeners();
updatePersistence();
// starts to play the newly entered next/prev track // starts to play the newly entered next/prev track
startPlaying(); startPlaying();
} }
@ -202,6 +211,7 @@ class Playback extends ChangeNotifier {
.then((value) async { .then((value) async {
_currentTrack = track; _currentTrack = track;
notifyListeners(); notifyListeners();
updatePersistence();
}); });
// await player.play(); // await player.play();
return; return;
@ -215,6 +225,7 @@ class Playback extends ChangeNotifier {
audioQuality: preferences.audioQuality, audioQuality: preferences.audioQuality,
); );
if (setTrackUriById(track.id!, spotubeTrack.ytUri)) { if (setTrackUriById(track.id!, spotubeTrack.ytUri)) {
logger.v("[Track Direct Source] - ${spotubeTrack.ytUri}");
_currentAudioSource = AudioSource.uri(Uri.parse(spotubeTrack.ytUri)); _currentAudioSource = AudioSource.uri(Uri.parse(spotubeTrack.ytUri));
await player.core await player.core
.setAudioSource( .setAudioSource(
@ -224,6 +235,7 @@ class Playback extends ChangeNotifier {
.then((value) { .then((value) {
_currentTrack = spotubeTrack; _currentTrack = spotubeTrack;
notifyListeners(); notifyListeners();
updatePersistence();
}); });
// await player.play(); // await player.play();
} }
@ -246,6 +258,36 @@ class Playback extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
} }
@override
FutureOr<void> loadFromLocal(Map<String, dynamic> 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<Map<String, dynamic>> toMap() {
return {
"currentPlaylist": currentPlaylist != null
? jsonEncode(currentPlaylist?.toJson())
: null,
"currentTrack":
currentTrack != null ? jsonEncode(currentTrack?.toJson()) : null,
};
}
} }
final playbackProvider = ChangeNotifierProvider<Playback>((ref) { final playbackProvider = ChangeNotifierProvider<Playback>((ref) {

View File

@ -37,7 +37,7 @@ abstract class PersistedChangeNotifier extends ChangeNotifier {
FutureOr<Map<String, dynamic>> toMap(); FutureOr<Map<String, dynamic>> toMap();
Future<void> updatePersistence() async { Future<void> updatePersistence({bool clearNullEntries = false}) async {
for (final entry in (await toMap()).entries) { for (final entry in (await toMap()).entries) {
if (entry.value is bool) { if (entry.value is bool) {
await _localStorage.setBool(entry.key, entry.value); await _localStorage.setBool(entry.key, entry.value);
@ -47,6 +47,8 @@ abstract class PersistedChangeNotifier extends ChangeNotifier {
await _localStorage.setDouble(entry.key, entry.value); await _localStorage.setDouble(entry.key, entry.value);
} else if (entry.value is String) { } else if (entry.value is String) {
await _localStorage.setString(entry.key, entry.value); await _localStorage.setString(entry.key, entry.value);
} else if (entry.value == null && clearNullEntries) {
_localStorage.remove(entry.key);
} }
} }
} }