diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index 9e8c191e..31b42fc0 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -1,4 +1,5 @@ import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -23,19 +24,55 @@ void useEndlessPlayback(WidgetRef ref) { if (index != playlist.tracks.length - 1) return; final track = playlist.tracks.last; + final plugin = await metadataPlugin; + if (plugin == null) return; - final tracks = await (await metadataPlugin)?.track.radio(track.id); + final tracks = []; + final existingIds = playlist.tracks.map((track) => track.id).toSet(); - if (tracks == null || tracks.isEmpty) return; + Future addFirstAvailable( + Future> Function() load, + ) async { + if (tracks.isNotEmpty) return; - await playback.addTracks( - tracks.toList() - ..removeWhere((e) { - final playlist = ref.read(audioPlayerProvider); - final isDuplicate = playlist.tracks.any((t) => t.id == e.id); - return e.id == track.id || isDuplicate; - }), + try { + final candidates = await load(); + for (final candidate in candidates) { + if (existingIds.add(candidate.id)) { + tracks.add(candidate); + } + } + } catch (error, stack) { + // A provider may not implement every discovery endpoint. Keep + // trying the progressively broader fallbacks below. + AppLogger.reportError(error, stack); + } + } + + // Prefer the provider's recommendations. Some providers currently + // return an empty list or throw here, leaving a searched song as a + // one-item queue even when Endless Playback is enabled. + await addFirstAvailable(() => plugin.track.radio(track.id)); + + // Keep playback moving with the rest of the album when radio is not + // available. Singles naturally fall through to the artist fallback. + await addFirstAvailable( + () async => + (await plugin.album.tracks(track.album.id, limit: 50)).items, ); + + if (track.artists.isNotEmpty) { + await addFirstAvailable( + () async => (await plugin.artist.topTracks( + track.artists.first.id, + limit: 50, + )) + .items, + ); + } + + if (tracks.isEmpty) return; + await playback.addTracks(tracks); } catch (e, stack) { AppLogger.reportError(e, stack); }