From 1d82bb098717c7321d3e338f071c7661987fc3be Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 3 Feb 2023 13:21:41 +0600 Subject: [PATCH] feat: bring pre download on desktop, disable pre download for long videos fix: audio service calling self ref of playlist queue provider --- .../player/sibling_tracks_sheet.dart | 12 ++-- lib/components/root/bottom_player.dart | 10 +--- lib/extensions/video.dart | 4 ++ lib/models/spotube_track.dart | 6 +- lib/pages/settings/settings.dart | 30 +++++----- lib/provider/playlist_queue_provider.dart | 60 ++++++++++++------- lib/provider/user_preferences_provider.dart | 14 +++-- lib/services/linux_audio_service.dart | 13 ++-- lib/services/mobile_audio_service.dart | 10 ++-- lib/utils/persisted_state_notifier.dart | 8 ++- 10 files changed, 95 insertions(+), 72 deletions(-) diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart index e00410116..683ec23a7 100644 --- a/lib/components/player/sibling_tracks_sheet.dart +++ b/lib/components/player/sibling_tracks_sheet.dart @@ -92,15 +92,19 @@ class SiblingTracksSheet extends HookConsumerWidget { subtitle: PlatformText(video.author), enabled: playlist?.isLoading != true, selected: playlist?.isLoading != true && - video.id == - (playlist?.activeTrack as SpotubeTrack).ytTrack.id, + video.id.value == + (playlist?.activeTrack as SpotubeTrack) + .ytTrack + .id + .value, selectedTileColor: Theme.of(context).popupMenuTheme.color, onTap: () async { if (playlist?.isLoading == false && - video.id != + video.id.value != (playlist?.activeTrack as SpotubeTrack) .ytTrack - .id) { + .id + .value) { await playlistNotifier.swapSibling(video); } }, diff --git a/lib/components/root/bottom_player.dart b/lib/components/root/bottom_player.dart index e021ac201..92f23ff3e 100644 --- a/lib/components/root/bottom_player.dart +++ b/lib/components/root/bottom_player.dart @@ -124,10 +124,11 @@ class BottomPlayer extends HookConsumerWidget { useEffect(() { if (volume.value != volumeState) { - volumeNotifier.setVolume(volume.value); + volume.value = volumeState; } return null; }, [volumeState]); + return Listener( onPointerSignal: (event) async { if (event is PointerScrollEvent) { @@ -147,12 +148,7 @@ class BottomPlayer extends HookConsumerWidget { onChanged: (v) { volume.value = v; }, - onChangeEnd: (value) async { - // You don't really need to know why but this - // way it works only - await volumeNotifier.setVolume(value); - await volumeNotifier.setVolume(value); - }, + onChangeEnd: volumeNotifier.setVolume, ), ); }), diff --git a/lib/extensions/video.dart b/lib/extensions/video.dart index ec9687fe0..36fee1b8c 100644 --- a/lib/extensions/video.dart +++ b/lib/extensions/video.dart @@ -136,6 +136,10 @@ extension GetSkipSegments on Video { }, )); + if (res.body == "Not Found") { + return List.castFrom>([]); + } + final data = jsonDecode(res.body); final segments = data.map((obj) { return Map.castFrom({ diff --git a/lib/models/spotube_track.dart b/lib/models/spotube_track.dart index 087a09290..c6ff1563e 100644 --- a/lib/models/spotube_track.dart +++ b/lib/models/spotube_track.dart @@ -136,7 +136,8 @@ class SpotubeTrack extends Track { ); } - if (preferences.androidBytesPlay) { + if (preferences.predownload && + ytVideo.duration! < const Duration(minutes: 15)) { await DefaultCacheManager().getFileFromCache(track.id!).then( (file) async { if (file != null) return file.file; @@ -232,7 +233,8 @@ class SpotubeTrack extends Track { ); } - if (preferences.androidBytesPlay) { + if (preferences.predownload && + video.duration! < const Duration(minutes: 15)) { await DefaultCacheManager().getFileFromCache(id!).then( (file) async { if (file != null) return file.file; diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index c2fa7c02a..52c80b6b2 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -15,7 +15,6 @@ import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/provider/auth_provider.dart'; import 'package:spotube/provider/user_preferences_provider.dart'; -import 'package:spotube/utils/platform.dart'; import 'package:url_launcher/url_launcher_string.dart'; class SettingsPage extends HookConsumerWidget { @@ -308,22 +307,21 @@ class SettingsPage extends HookConsumerWidget { }, ), ), - if (kIsMobile) - PlatformListTile( - leading: const Icon(SpotubeIcons.download), - title: const PlatformText( - "Pre download and play", - ), - subtitle: const PlatformText( - "Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)", - ), - trailing: PlatformSwitch( - value: preferences.androidBytesPlay, - onChanged: (state) { - preferences.setAndroidBytesPlay(state); - }, - ), + PlatformListTile( + leading: const Icon(SpotubeIcons.download), + title: const PlatformText( + "Pre download and play", ), + subtitle: const PlatformText( + "Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)", + ), + trailing: PlatformSwitch( + value: preferences.predownload, + onChanged: (state) { + preferences.setPredownload(state); + }, + ), + ), PlatformListTile( leading: const Icon(SpotubeIcons.fastForward), title: const PlatformText( diff --git a/lib/provider/playlist_queue_provider.dart b/lib/provider/playlist_queue_provider.dart index c359be4cc..78ad23f09 100644 --- a/lib/provider/playlist_queue_provider.dart +++ b/lib/provider/playlist_queue_provider.dart @@ -14,6 +14,7 @@ import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart' hide Playlist; +import 'package:collection/collection.dart'; final audioPlayer = AudioPlayer(); final youtube = YoutubeExplode(); @@ -24,20 +25,32 @@ class PlaylistQueue { Track get activeTrack => tracks.elementAt(active); - factory PlaylistQueue.fromJson(Map json) { + static Future fromJson( + Map json, UserPreferences preferences) async { + final List? tracks = json['tracks']; return PlaylistQueue( - Set.from(json['tracks'].map( - (e) { - final json = Map.castFrom(e); - if (e["ytTrack"] != null) { - return SpotubeTrack.fromJson(json); - } else if (e["path"] != null) { - return LocalTrack.fromJson(json); - } else { - return Track.fromJson(json); - } - }, - )), + Set.from( + await Future.wait( + tracks?.mapIndexed( + (i, e) async { + final jsonTrack = + Map.castFrom(e); + + if (e["path"] != null) { + return LocalTrack.fromJson(jsonTrack); + } else if (i == json["active"] && !json.containsKey("path")) { + return await SpotubeTrack.fromFetchTrack( + Track.fromJson(jsonTrack), + preferences, + ); + } else { + return Track.fromJson(jsonTrack); + } + }, + ) ?? + [], + ), + ), active: json['active'], ); } @@ -97,7 +110,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { void configure() async { if (kIsMobile || kIsMacOS) { mobileService = await AudioService.init( - builder: () => MobileAudioService(ref), + builder: () => MobileAudioService(this), config: const AudioServiceConfig( androidNotificationChannelId: 'com.krtirtho.Spotube', androidNotificationChannelName: 'Spotube', @@ -106,7 +119,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { ); } if (kIsLinux) { - linuxService = LinuxAudioService(ref); + linuxService = LinuxAudioService(ref, this); } addListener((state) { linuxService?.player.updateProperties(); @@ -160,10 +173,13 @@ class PlaylistQueueNotifier extends PersistedStateNotifier { static bool get isPaused => audioPlayer.state == PlayerState.paused; static bool get isStopped => audioPlayer.state == PlayerState.stopped; - static Stream get duration => audioPlayer.onDurationChanged; - static Stream get position => audioPlayer.onPositionChanged; + static Stream get duration => + audioPlayer.onDurationChanged.asBroadcastStream(); + static Stream get position => + audioPlayer.onPositionChanged.asBroadcastStream(); static Stream get playing => audioPlayer.onPlayerStateChanged - .map((event) => event == PlayerState.playing); + .map((event) => event == PlayerState.playing) + .asBroadcastStream(); List