From e77f3bf9b3ee0926baf4104b6252d309f756efc3 Mon Sep 17 00:00:00 2001 From: eddyizm Date: Tue, 13 Jan 2026 20:00:46 -0800 Subject: [PATCH] fix: instant mix random songs (#354) * wip: updated instant mix request size * Address broken continuous play * wip: filling queue, getting dupes * fix: deduped the song track list --- .../tempo/repository/SongRepository.java | 102 +++++++++--------- .../tempo/service/MediaManager.java | 3 +- .../viewmodel/AlbumBottomSheetViewModel.java | 2 +- .../viewmodel/ArtistBottomSheetViewModel.java | 2 +- .../tempo/viewmodel/ArtistPageViewModel.java | 2 +- .../viewmodel/SongBottomSheetViewModel.java | 2 +- 6 files changed, 55 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java index 25990236..1e265cb6 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java +++ b/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java @@ -25,6 +25,7 @@ import retrofit2.Response; public class SongRepository { private static final String TAG = "SongRepository"; + public interface MediaCallbackInternal { void onSongsAvailable(List songs); } @@ -64,18 +65,25 @@ public class SongRepository { */ public MutableLiveData> getInstantMix(String id, SeedType type, int count) { MutableLiveData> instantMix = new MutableLiveData<>(new ArrayList<>()); + Set trackIds = new HashSet<>(); performSmartMix(id, type, count, songs -> { List current = instantMix.getValue(); if (current != null) { for (Child s : songs) { - if (!current.contains(s)) current.add(s); + if (!trackIds.contains(s.getId())) { + current.add(s); + trackIds.add(s.getId()); + } } - + if (current.size() < count / 2) { - fillWithRandom(count - current.size(), remainder -> { + fetchSimilarOnly(id, count, remainder -> { for (Child r : remainder) { - if (!current.contains(r)) current.add(r); + if (!trackIds.contains(r.getId())) { + current.add(r); + trackIds.add(r.getId()); + } } instantMix.postValue(current); }); @@ -130,6 +138,7 @@ public class SongRepository { isComplete = true; } } + } private void performSmartMix(final String id, final SeedType type, final int count, final MediaCallbackInternal callback) { @@ -138,7 +147,7 @@ public class SongRepository { fetchSimilarByArtist(id, count, callback); break; case ALBUM: - fetchAlbumSongsThenSimilar(id, count, callback); + fetchAlbumSongs(id, count, callback); break; case TRACK: fetchSingleTrackThenSimilar(id, count, callback); @@ -146,7 +155,7 @@ public class SongRepository { } } - private void fetchAlbumSongsThenSimilar(String albumId, int count, MediaCallbackInternal callback) { + private void fetchAlbumSongs(String albumId, int count, MediaCallbackInternal callback) { App.getSubsonicClientInstance(false).getBrowsingClient().getAlbum(albumId).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { @@ -157,25 +166,14 @@ public class SongRepository { int fromAlbum = Math.min(count, albumSongs.size()); List limitedAlbumSongs = albumSongs.subList(0, fromAlbum); callback.onSongsAvailable(new ArrayList<>(limitedAlbumSongs)); - - int remaining = count - fromAlbum; - if (remaining > 0 && albumSongs.get(0).getArtistId() != null) { - fetchSimilarByArtist(albumSongs.get(0).getArtistId(), remaining, callback); - } else if (remaining > 0) { - Log.d(TAG, "No artistId available, skipping similar artist fetch"); - } - return; + } } - - Log.d(TAG, "Album fetch failed or empty, calling fillWithRandom"); - fillWithRandom(count, callback); } - - @Override + + @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - Log.d(TAG, "Album fetch failed: " + t.getMessage()); - fillWithRandom(count, callback); + Log.e(TAG, "fetchAlbumSongsThenSimilar.onFailure()", t); } }); } @@ -188,17 +186,17 @@ public class SongRepository { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { List similar = extractSongs(response, "similarSongs2"); + Log.d(TAG, "fetchSimilarByArtist.onResponse() - similar songs: " + similar.size()); + if (!similar.isEmpty()) { List limitedSimilar = similar.subList(0, Math.min(count, similar.size())); callback.onSongsAvailable(limitedSimilar); - } else { - fillWithRandom(count, callback); } } - - @Override + + @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - fillWithRandom(count, callback); + Log.e(TAG, "fetchSimilarByArtist.onFailure()", t); } }); } @@ -211,17 +209,13 @@ public class SongRepository { Child song = response.body().getSubsonicResponse().getSong(); if (song != null) { callback.onSongsAvailable(Collections.singletonList(song)); - int remaining = count - 1; - if (remaining > 0) { - fetchSimilarOnly(trackId, remaining, callback); - } - return; } } - fillWithRandom(count, callback); } - @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - fillWithRandom(count, callback); + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "fetchSingleTrackThenSimilar.onFailure()", t); } }); } @@ -232,38 +226,40 @@ public class SongRepository { public void onResponse(@NonNull Call call, @NonNull Response response) { List songs = extractSongs(response, "similarSongs"); if (!songs.isEmpty()) { - List limitedSongs = songs.subList(0, Math.min(count, songs.size())); - callback.onSongsAvailable(limitedSongs); - } else { - fillWithRandom(count, callback); + int limit = Math.min(count, songs.size()); + callback.onSongsAvailable(songs.subList(0, limit)); } } - @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - fillWithRandom(count, callback); + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.e(TAG, "fetchSimilarOnly.onFailure()", t); } }); } - private void fillWithRandom(int target, final MediaCallbackInternal callback) { + public MutableLiveData> getContinuousMix(String id, int count) { + MutableLiveData> instantMix = new MutableLiveData<>(); + App.getSubsonicClientInstance(false) - .getAlbumSongListClient() - .getRandomSongs(target, null, null) + .getBrowsingClient() + .getSimilarSongs(id, count) .enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { - List random = extractSongs(response, "randomSongs"); - if (!random.isEmpty()) { - List limitedRandom = random.subList(0, Math.min(target, random.size())); - callback.onSongsAvailable(limitedRandom); - } else { - callback.onSongsAvailable(new ArrayList<>()); + if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs() != null) { + instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs().getSongs()); } } - @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - callback.onSongsAvailable(new ArrayList<>()); + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + instantMix.setValue(null); } }); + + return instantMix; } private List extractSongs(Response response, String type) { @@ -274,11 +270,10 @@ public class SongRepository { list = res.getSimilarSongs().getSongs(); } else if (type.equals("similarSongs2") && res.getSimilarSongs2() != null) { list = res.getSimilarSongs2().getSongs(); - } else if (type.equals("randomSongs") && res.getRandomSongs() != null) { - list = res.getRandomSongs().getSongs(); } return (list != null) ? list : new ArrayList<>(); } + return new ArrayList<>(); } @@ -299,6 +294,7 @@ public class SongRepository { public MutableLiveData> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) { MutableLiveData> randomSongsSample = new MutableLiveData<>(); + App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear, genre).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { List songs = new ArrayList<>(); diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java index ab591104..ba2cee60 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -448,7 +448,8 @@ public class MediaManager { if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) { Preferences.setLastInstantMix(); - LiveData> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, SeedType.TRACK, 10); + LiveData> instantMix = getSongRepository().getContinuousMix(mediaItem.mediaId,25); + instantMix.observeForever(new Observer>() { @Override public void onChanged(List media) { diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumBottomSheetViewModel.java index 3b7b0999..9ee6c007 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/AlbumBottomSheetViewModel.java @@ -138,7 +138,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel { public LiveData> getAlbumInstantMix(LifecycleOwner owner, AlbumID3 album) { instantMix.setValue(Collections.emptyList()); - albumRepository.getInstantMix(album, 20).observe(owner, instantMix::postValue); + albumRepository.getInstantMix(album, 30).observe(owner, instantMix::postValue); return instantMix; } diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistBottomSheetViewModel.java index 0df56246..1222a3b6 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistBottomSheetViewModel.java @@ -126,7 +126,7 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel { public LiveData> getArtistInstantMix(LifecycleOwner owner, ArtistID3 artist) { instantMix.setValue(Collections.emptyList()); - artistRepository.getInstantMix(artist, 20).observe(owner, instantMix::postValue); + artistRepository.getInstantMix(artist, 30).observe(owner, instantMix::postValue); return instantMix; } diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistPageViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistPageViewModel.java index ab6cc609..f2b7d9e6 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistPageViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/ArtistPageViewModel.java @@ -60,7 +60,7 @@ public class ArtistPageViewModel extends AndroidViewModel { } public LiveData> getArtistInstantMix() { - return artistRepository.getInstantMix(artist, 20); + return artistRepository.getInstantMix(artist, 30); } public ArtistID3 getArtist() { diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/SongBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/SongBottomSheetViewModel.java index 665756a7..9fca0c21 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/SongBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/SongBottomSheetViewModel.java @@ -130,7 +130,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel { public LiveData> getInstantMix(LifecycleOwner owner, Child media) { instantMix.setValue(Collections.emptyList()); - songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, instantMix::postValue); + songRepository.getInstantMix(media.getId(), SeedType.TRACK, 30).observe(owner, instantMix::postValue); return instantMix; }