mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
CurrentPlaylist cache & load from cache support
This commit is contained in:
parent
2b75076f82
commit
b4bb93c81e
@ -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(
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user