From d04ed8d430a819feca4832e3e465ad41870de815 Mon Sep 17 00:00:00 2001 From: eddyizm Date: Thu, 1 Jan 2026 11:50:48 -0800 Subject: [PATCH] fix: address duplicate track bug, wrong order in queue, and updated album instant mix --- .../tempo/repository/SongRepository.java | 101 ++++++++++-------- .../tempo/service/MediaManager.java | 13 +-- .../ui/fragment/HomeTabMusicFragment.java | 23 ++-- .../AlbumBottomSheetDialog.java | 34 +++--- .../ArtistBottomSheetDialog.java | 2 +- .../SongBottomSheetDialog.java | 37 +++++-- 6 files changed, 120 insertions(+), 90 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 0c82e022..d457c44a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java +++ b/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java @@ -1,5 +1,7 @@ package com.cappielloantonio.tempo.repository; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.lifecycle.MutableLiveData; @@ -22,6 +24,7 @@ import retrofit2.Response; public class SongRepository { + private static final String TAG = "SongRepository"; public interface MediaCallbackInternal { void onSongsAvailable(List songs); } @@ -126,7 +129,6 @@ public class SongRepository { originalCallback.onSongsAvailable(new ArrayList<>(accumulatedSongs)); if (accumulatedSongs.size() >= targetCount) { isComplete = true; - android.util.Log.d("SongRepository", "Reached target of " + targetCount + " songs"); } } } @@ -150,28 +152,61 @@ public class SongRepository { App.getSubsonicClientInstance(false).getBrowsingClient().getAlbum(albumId).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) { + if (response.isSuccessful() && response.body() != null && + response.body().getSubsonicResponse().getAlbum() != null) { List albumSongs = response.body().getSubsonicResponse().getAlbum().getSongs(); if (albumSongs != null && !albumSongs.isEmpty()) { - List limitedAlbumSongs = albumSongs.subList(0, Math.min(count, albumSongs.size())); + int fromAlbum = Math.min(count, albumSongs.size()); + List limitedAlbumSongs = albumSongs.subList(0, fromAlbum); callback.onSongsAvailable(new ArrayList<>(limitedAlbumSongs)); - - // If we need more, get similar songs - int remaining = count - limitedAlbumSongs.size(); - if (remaining > 0) { + + 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 public void onFailure(@NonNull Call call, @NonNull Throwable t) { + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.d(TAG, "Album fetch failed: " + t.getMessage()); fillWithRandom(count, callback); } }); } + private void fetchSimilarByArtist(String artistId, final int count, final MediaCallbackInternal callback) { + App.getSubsonicClientInstance(false) + .getBrowsingClient() + .getSimilarSongs2(artistId, count) + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + List similar = extractSongs(response, "similarSongs2"); + if (!similar.isEmpty()) { + List limitedSimilar = similar.subList(0, Math.min(count, similar.size())); + callback.onSongsAvailable(limitedSimilar); + } else { + Log.d(TAG, "No similar songs, calling fillWithRandom"); + fillWithRandom(count, callback); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + Log.d(TAG, "getSimilarSongs2 failed: " + t.getMessage()); + fillWithRandom(count, callback); + } + }); + } + private void fetchSingleTrackThenSimilar(String trackId, int count, MediaCallbackInternal callback) { App.getSubsonicClientInstance(false).getBrowsingClient().getSong(trackId).enqueue(new Callback() { @Override @@ -195,45 +230,25 @@ public class SongRepository { }); } -private void fetchSimilarOnly(String id, int count, MediaCallbackInternal callback) { - App.getSubsonicClientInstance(false).getBrowsingClient().getSimilarSongs(id, count).enqueue(new Callback() { - @Override - 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 { + private void fetchSimilarOnly(String id, int count, MediaCallbackInternal callback) { + App.getSubsonicClientInstance(false).getBrowsingClient().getSimilarSongs(id, count).enqueue(new Callback() { + @Override + 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); + } + } + @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { fillWithRandom(count, callback); } - } - @Override public void onFailure(@NonNull Call call, @NonNull Throwable t) { - fillWithRandom(count, callback); - } - }); -} - private void fetchSimilarByArtist(String artistId, final int count, final MediaCallbackInternal callback) { - App.getSubsonicClientInstance(false) - .getBrowsingClient() - .getSimilarSongs2(artistId, count) - .enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - List similar = extractSongs(response, "similarSongs2"); - if (!similar.isEmpty()) { - List limitedSimilar = similar.subList(0, Math.min(count, similar.size())); - callback.onSongsAvailable(limitedSimilar); - } else { - fillWithRandom(count, callback); - } - } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - fillWithRandom(count, callback); - } - }); + }); } + private void fillWithRandom(int target, final MediaCallbackInternal callback) { App.getSubsonicClientInstance(false) .getAlbumSongListClient() 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 12ae3e85..ab591104 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -184,44 +184,33 @@ public class MediaManager { @OptIn(markerClass = UnstableApi.class) public static void startQueue(ListenableFuture mediaBrowserListenableFuture, List media, int startIndex) { if (mediaBrowserListenableFuture != null) { - Log.d(TAG, "startQueue called with " + (media != null ? media.size() : 0) + " songs"); - + mediaBrowserListenableFuture.addListener(() -> { try { if (mediaBrowserListenableFuture.isDone()) { - Log.d(TAG, "MediaBrowser future is done"); final MediaBrowser browser = mediaBrowserListenableFuture.get(); - Log.d(TAG, "Got MediaBrowser, connected: " + browser.isConnected()); - final List items = MappingUtil.mapMediaItems(media); - Log.d(TAG, "Mapped " + items.size() + " media items"); new Handler(Looper.getMainLooper()).post(() -> { - Log.d(TAG, "Setting " + items.size() + " media items at index " + startIndex); justStarted.set(true); browser.setMediaItems(items, startIndex, 0); browser.prepare(); - Log.d(TAG, "setMediaItems and prepare called"); Player.Listener timelineListener = new Player.Listener() { @Override public void onTimelineChanged(Timeline timeline, int reason) { - Log.d(TAG, "onTimelineChanged: itemCount=" + browser.getMediaItemCount() + ", reason=" + reason); int itemCount = browser.getMediaItemCount(); if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) { - Log.d(TAG, "Seeking to " + startIndex + " and playing"); browser.seekTo(startIndex, 0); browser.play(); browser.removeListener(this); - Log.d(TAG, "Playback started"); } else { Log.d(TAG, "Cannot start playback: itemCount=" + itemCount + ", startIndex=" + startIndex); } } }; - Log.d(TAG, "Adding timeline listener"); browser.addListener(timelineListener); }); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java index 191c520d..d7f739ae 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java @@ -5,6 +5,8 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -1253,20 +1255,25 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback { MediaBrowser.releaseFuture(mediaBrowserListenableFuture); } - @Override public void onMediaClick(Bundle bundle) { if (bundle.containsKey(Constants.MEDIA_MIX)) { - MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT)); + Child track = bundle.getParcelable(Constants.TRACK_OBJECT); activity.setBottomSheetInPeek(true); if (mediaBrowserListenableFuture != null) { - homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> { - MusicUtil.ratingFilter(songs); + final boolean[] playbackStarted = {false}; - if (songs != null && !songs.isEmpty()) { - MediaManager.enqueue(mediaBrowserListenableFuture, songs, true); - } - }); + homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), track) + .observe(getViewLifecycleOwner(), songs -> { + if (playbackStarted[0] || songs == null || songs.isEmpty()) return; + + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (playbackStarted[0]) return; + + MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); + playbackStarted[0] = true; + }, 300); + }); } } else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) { List media = bundle.getParcelableArrayList(Constants.TRACKS_OBJECT); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java index 072fa391..f1e4c89a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/AlbumBottomSheetDialog.java @@ -5,6 +5,8 @@ import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -61,6 +63,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements private List currentAlbumMediaItems = Collections.emptyList(); private ListenableFuture mediaBrowserListenableFuture; + private static final String TAG = "AlbumBottomSheetDialog"; @Nullable @Override @@ -116,12 +119,11 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements favoriteToggle.setOnClickListener(v -> albumBottomSheetViewModel.setFavorite(requireContext())); TextView playRadio = view.findViewById(R.id.play_radio_text_view); - playRadio.setOnClickListener(v -> { - AlbumRepository albumRepository = new AlbumRepository(); - albumRepository.getInstantMix(album, 20, new MediaCallback() { + playRadio.setOnClickListener(v -> { + new AlbumRepository().getInstantMix(album, 20, new MediaCallback() { @Override public void onError(Exception exception) { - exception.printStackTrace(); + Log.e(TAG, "Error: " + exception.getMessage()); } @Override @@ -140,19 +142,19 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements } view.postDelayed(() -> { - try { - if (mediaBrowserListenableFuture.isDone()) { - MediaBrowser browser = mediaBrowserListenableFuture.get(); - if (browser != null && browser.isPlaying()) { - dismissBottomSheet(); - return; - } - } - } catch (Exception e) { - // Ignore + try { + if (mediaBrowserListenableFuture.isDone()) { + MediaBrowser browser = mediaBrowserListenableFuture.get(); + if (browser != null && browser.isPlaying()) { + dismissBottomSheet(); + return; } - view.postDelayed(() -> dismissBottomSheet(), 200); - }, 300); + } + } catch (Exception e) { + Log.e(TAG, "Error checking playback: " + e.getMessage()); + } + view.postDelayed(() -> dismissBottomSheet(), 200); + }, 300); } }); }); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/ArtistBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/ArtistBottomSheetDialog.java index b6e88c1a..be69b76a 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/ArtistBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/ArtistBottomSheetDialog.java @@ -89,7 +89,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement TextView playRadio = view.findViewById(R.id.play_radio_text_view); playRadio.setOnClickListener(v -> { - Log.d(TAG, "Artist instant mix clicked"); + Log.d(TAG, "Artist instant mix clicked"); ArtistRepository artistRepository = new ArtistRepository(); artistRepository.getInstantMix(artist, 20) diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java index 39ba4394..271f00cc 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/bottomsheetdialog/SongBottomSheetDialog.java @@ -5,6 +5,8 @@ import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -143,21 +145,36 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements TextView playRadio = view.findViewById(R.id.play_radio_text_view); playRadio.setOnClickListener(v -> { - MediaManager.startQueue(mediaBrowserListenableFuture, song); ((MainActivity) requireActivity()).setBottomSheetInPeek(true); + final boolean[] playbackStarted = {false}; + songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> { - MusicUtil.ratingFilter(songs); + if (playbackStarted[0] || songs == null || songs.isEmpty()) return; - if (songs == null) { - dismissBottomSheet(); - return; - } + new Handler(Looper.getMainLooper()).postDelayed(() -> { + if (playbackStarted[0]) return; - if (!songs.isEmpty()) { - MediaManager.enqueue(mediaBrowserListenableFuture, songs, true); - dismissBottomSheet(); - } + MusicUtil.ratingFilter(songs); + + MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); + playbackStarted[0] = true; + + view.postDelayed(() -> { + try { + if (mediaBrowserListenableFuture.isDone()) { + MediaBrowser browser = mediaBrowserListenableFuture.get(); + if (browser != null && browser.isPlaying()) { + dismissBottomSheet(); + return; + } + } + } catch (Exception e) { + // Ignore + } + view.postDelayed(() -> dismissBottomSheet(), 200); + }, 300); + }, 300); }); });