Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,17 @@ public void onTimelineChanged(Timeline timeline, int reason) {
}

public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media) {
startQueue(mediaBrowserListenableFuture, media, 0, 0);
}

public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media, int startIndex, long positionMs) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
MediaBrowser browser = mediaBrowserListenableFuture.get();
justStarted.set(true);
browser.setMediaItem(MappingUtil.mapMediaItem(media));
browser.setMediaItem(MappingUtil.mapMediaItem(media), positionMs);
browser.prepare();
browser.play();
enqueueDatabase(media, true, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class PlayerBottomSheetFragment extends Fragment {

private Handler progressBarHandler;
private Runnable progressBarRunnable;
private Handler autoSaveHandler;
private Runnable autoSaveRunnable;

@Nullable
@Override
Expand Down Expand Up @@ -79,6 +81,8 @@ public void onStart() {

@Override
public void onStop() {
runProgressBarHandler(false);
runAutoSaveHandler(false);
releaseMediaBrowser();
super.onStop();
}
Expand Down Expand Up @@ -127,6 +131,7 @@ private void bindMediaController() {

private void setMediaControllerListener(MediaBrowser mediaBrowser) {
defineProgressBarHandler(mediaBrowser);
defineAutoSaveHandler(mediaBrowser);
setMediaControllerUI(mediaBrowser);
setMetadata(mediaBrowser.getMediaMetadata());
setContentDuration(mediaBrowser.getContentDuration());
Expand Down Expand Up @@ -267,6 +272,7 @@ private void setProgress(MediaBrowser mediaBrowser) {
private void setPlayingState(boolean isPlaying) {
bind.playerHeaderLayout.playerHeaderButton.setChecked(isPlaying);
runProgressBarHandler(isPlaying);
runAutoSaveHandler(isPlaying);
}

private void setHeaderMediaController() {
Expand Down Expand Up @@ -335,6 +341,24 @@ private void runProgressBarHandler(boolean isPlaying) {
}
}

private void defineAutoSaveHandler(MediaBrowser mediaBrowser) {
autoSaveHandler = new Handler();
autoSaveRunnable = () -> {
if (Preferences.isSyncronizationEnabled()) {
playerBottomSheetViewModel.savePlayQueue(mediaBrowser.getCurrentPosition());
}
autoSaveHandler.postDelayed(autoSaveRunnable, Preferences.getSyncCountdownTimer() * 1000L);
};
}

private void runAutoSaveHandler(boolean isPlaying) {
if (autoSaveHandler == null || autoSaveRunnable == null) return;
autoSaveHandler.removeCallbacks(autoSaveRunnable);
if (isPlaying && Preferences.isSyncronizationEnabled()) {
autoSaveHandler.postDelayed(autoSaveRunnable, Preferences.getSyncCountdownTimer() * 1000L);
}
}

private void setHeaderBookmarksButton() {
if (Preferences.isSyncronizationEnabled()) {
playerBottomSheetViewModel.getPlayQueue().observeForever(new Observer<PlayQueue>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,24 @@ private void initInnerButton() {
});

bind.innerButtonBottomRight.setOnClickListener(view -> {
if (playerBottomSheetViewModel.savePlayQueue()) {
Snackbar.make(requireView(), R.string.player_queue_save_queue_success, Snackbar.LENGTH_LONG).show();
if (mediaBrowserListenableFuture == null) {
return;
}
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
long position = mediaBrowser.getCurrentPosition();
if (getView() != null && getActivity() != null) {
getActivity().runOnUiThread(() -> {
if (playerBottomSheetViewModel.savePlayQueue(position)) {
Snackbar.make(requireView(), R.string.player_queue_save_queue_success, Snackbar.LENGTH_LONG).show();
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
});

bind.innerButtonBottomRightAlternative.setOnClickListener(view -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
import com.cappielloantonio.tempo.util.ExternalAudioWriter;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
import com.cappielloantonio.tempo.util.ExternalAudioWriter;
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
Expand All @@ -60,6 +60,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabSaveToPlaylist;
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabDownloadAll;
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabLoadQueue;
private com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton fabSaveQueue;

private boolean isMenuOpen = false;
private final int ANIMATION_DURATION = 250;
Expand All @@ -86,6 +87,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
fabSaveToPlaylist = bind.fabSaveToPlaylist;
fabDownloadAll = bind.fabDownloadAll;
fabLoadQueue = bind.fabLoadQueue;
fabSaveQueue = bind.fabSaveQueue;

fabMenuToggle.setOnClickListener(v -> toggleFabMenu());
fabClearQueue.setOnClickListener(v -> handleClearQueueClick());
Expand All @@ -94,10 +96,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
fabSaveToPlaylist.setOnClickListener(v -> handleSaveToPlaylistClick());
fabDownloadAll.setOnClickListener(v -> handleDownloadAllClick());
fabLoadQueue.setOnClickListener(v -> handleLoadQueueClick());
fabSaveQueue.setOnClickListener(v -> handleSaveQueueClick());

// Hide Load Queue FAB if sync is disabled
// Hide Load/Save Queue FAB if sync is disabled
if (!Preferences.isSyncronizationEnabled()) {
fabLoadQueue.setVisibility(View.GONE);
fabSaveQueue.setVisibility(View.GONE);
}

initQueueRecyclerView();
Expand Down Expand Up @@ -248,7 +252,8 @@ private void toggleFabMenu() {
if (isMenuOpen) {
// CLOSE MENU (Reverse order for visual effect)
if (Preferences.isSyncronizationEnabled()) {
closeFab(fabLoadQueue, 4);
closeFab(fabLoadQueue, 5);
closeFab(fabSaveQueue, 4);
}
closeFab(fabSaveToPlaylist, 3);
closeFab(fabClearQueue, 2);
Expand All @@ -263,7 +268,8 @@ private void toggleFabMenu() {
openFab(fabClearQueue, 2);
openFab(fabSaveToPlaylist, 3);
if (Preferences.isSyncronizationEnabled()) {
openFab(fabLoadQueue, 4);
openFab(fabSaveQueue, 4);
openFab(fabLoadQueue, 5);
}
fabMenuToggle.animate().rotation(45f).setDuration(ANIMATION_DURATION).start();
}
Expand Down Expand Up @@ -446,6 +452,27 @@ private void handleDownloadAllClick() {
toggleFabMenu();
}

private void handleSaveQueueClick() {
Log.d(TAG, "Save Queue Clicked!");
if (!Preferences.isSyncronizationEnabled()) {
toggleFabMenu();
return;
}

mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
long position = mediaBrowser.getCurrentPosition();
if (playerBottomSheetViewModel.savePlayQueue(position)) {
Toast.makeText(requireContext(), R.string.player_queue_save_queue_success, Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Log.e(TAG, "Error saving queue", e);
}
toggleFabMenu();
}, MoreExecutors.directExecutor());
Comment on lines +462 to +473

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate file and print relevant section with line numbers
FILE="app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java"
if [ ! -f "$FILE" ]; then
  echo "Missing $FILE" >&2
  exit 1
fi

nl -ba "$FILE" | sed -n '430,520p'

# Show toggleFabMenu implementation and any usage of it
rg -n "toggleFabMenu\\s*\\(" "$FILE"
# Print toggleFabMenu method body if found
if rg -q "void toggleFabMenu\\s*\\(" "$FILE"; then
  # use sed to extract around method
  START=$(rg -n "void toggleFabMenu\\s*\\(" "$FILE" | head -n1 | cut -d: -f1)
  END=$((START+80))
  nl -ba "$FILE" | sed -n "${START},${END}p"
fi

# Search for mediaBrowserListenableFuture creation / listener registration
rg -n "mediaBrowserListenableFuture" "$FILE"

Repository: eddyizm/tempus

Length of output: 102


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java"
nl -ba "$FILE" | sed -n '430,520p'

rg -n "toggleFabMenu\\s*\\(" "$FILE"
if rg -q "void toggleFabMenu\\s*\\(" "$FILE"; then
  START=$(rg -n "void toggleFabMenu\\s*\\(" "$FILE" | head -n1 | cut -d: -f1)
  END=$((START+90))
  nl -ba "$FILE" | sed -n "${START},${END}p"
fi

rg -n "mediaBrowserListenableFuture" "$FILE"

Repository: eddyizm/tempus

Length of output: 101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java"

echo "=== Context around lines 430-520 ==="
nl -ba "$FILE" | sed -n '430,520p'

echo "=== toggleFabMenu occurrences ==="
rg -n "toggleFabMenu\\s*\\(" "$FILE"

echo "=== toggleFabMenu implementation (if present) ==="
if rg -q "void toggleFabMenu\\s*\\(" "$FILE"; then
  START=$(rg -n "void toggleFabMenu\\s*\\(" "$FILE" | head -n1 | cut -d: -f1)
  END=$((START+120))
  nl -ba "$FILE" | sed -n "${START},${END}p"
fi

echo "=== mediaBrowserListenableFuture occurrences ==="
rg -n "mediaBrowserListenableFuture" "$FILE"

Repository: eddyizm/tempus

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java"
if [ ! -f "$FILE" ]; then
  echo "Missing $FILE" >&2
  exit 1
fi

awk '{
  printf "%6d\t%s\n", NR, $0
}' "$FILE" | sed -n '430,520p'

echo "=== toggleFabMenu occurrences ==="
rg -n "toggleFabMenu\\s*\\(" "$FILE" || true

echo "=== toggleFabMenu implementation (if present) ==="
if rg -q "void toggleFabMenu\\s*\\(" "$FILE"; then
  START=$(rg -n "void toggleFabMenu\\s*\\(" "$FILE" | head -n1 | cut -d: -f1)
  awk '{
    printf "%6d\t%s\n", NR, $0
  }' "$FILE" | sed -n "${START},$((START+140))p"
fi

echo "=== mediaBrowserListenableFuture occurrences ==="
rg -n "mediaBrowserListenableFuture" "$FILE" || true

Repository: eddyizm/tempus

Length of output: 13316


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java"

echo "=== Context around lines 1-220 ==="
awk '{printf "%6d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo "=== Context around lines 220-420 ==="
awk '{printf "%6d\t%s\n", NR, $0}' "$FILE" | sed -n '220,420p'

echo "=== Direct executor addListener occurrences (for UI work patterns) ==="
rg -n "addListener\\(\\(\\) -> \\{|addListener\\([^\\)]*directExecutor\\(" "$FILE" || true

Repository: eddyizm/tempus

Length of output: 20862


Move Save Queue listener UI work to the main thread

mediaBrowserListenableFuture.addListener(..., MoreExecutors.directExecutor()) runs on the future’s completion thread, so Toast.makeText(...) and toggleFabMenu() in handleSaveQueueClick() (lines 462-473) can execute off the main thread and cause UI crashes. Dispatch those UI calls to the main thread (the same pattern is also used in the shuffle/clear listeners).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java`
around lines 462 - 473, The listener passed to
mediaBrowserListenableFuture.addListener (in handleSaveQueueClick) performs UI
work (Toast.makeText and toggleFabMenu) on the future's completion thread; wrap
those UI calls in a main-thread dispatch instead of running them directly.
Modify the listener body so you still call mediaBrowserListenableFuture.get()
and playerBottomSheetViewModel.savePlayQueue(position) on the current thread,
but when showing the Toast and calling toggleFabMenu() post a Runnable to the
main thread (e.g., requireActivity().runOnUiThread(...) or new
Handler(Looper.getMainLooper()).post(...)) so the UI interactions occur on the
main thread and keep the existing try/catch around the non-UI operations.

}

private void handleLoadQueueClick() {
Log.d(TAG, "Load Queue Clicked!");
if (!Preferences.isSyncronizationEnabled()) {
Expand All @@ -469,7 +496,14 @@ public void onChanged(PlayQueue playQueue) {
}
}

MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), currentIndex);
long positionMs = playQueue.getPosition() != null ? playQueue.getPosition() : 0L;
List<Child> entries = playQueue.getEntries();
if (currentIndex >= 0 && currentIndex < entries.size()) {
MediaManager.startQueue(mediaBrowserListenableFuture, entries.get(currentIndex), currentIndex, positionMs);
} else {
Log.e("PlayerQueueFragment", "Invalid currentIndex: " + currentIndex + " for entries size: " + entries.size());
Toast.makeText(requireContext(), "Error loading queue: Invalid index", Toast.LENGTH_SHORT).show();
}

Toast.makeText(requireContext(), "Queue loaded", Toast.LENGTH_SHORT).show();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ object Preferences {

@JvmStatic
fun getSyncCountdownTimer(): Int {
return App.getInstance().preferences.getString(QUEUE_SYNCING_COUNTDOWN, "5")!!.toInt()
return App.getInstance().preferences.getString(QUEUE_SYNCING_COUNTDOWN, "20")!!.toInt()
}

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,14 @@ public LiveData<PlayQueue> getPlayQueue() {
return queueRepository.getPlayQueue();
}

public boolean savePlayQueue() {
public boolean savePlayQueue(long position) {
Child media = getLiveMedia().getValue();
List<Child> queue = queueRepository.getMedia();
List<String> ids = queue.stream().map(Child::getId).collect(Collectors.toList());

if (media != null) {
// TODO: We need to get the actual playback position here
Log.d(TAG, "Saving play queue - Current: " + media.getId() + ", Items: " + ids.size());
queueRepository.savePlayQueue(ids, media.getId(), 0); // Still hardcoded to 0 for now
Log.d(TAG, "Saving play queue - Current: " + media.getId() + ", Items: " + ids.size() + ", Position: " + position);
queueRepository.savePlayQueue(ids, media.getId(), position);
return true;
Comment thread
eddyizm marked this conversation as resolved.
}
return false;
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/layout/inner_fragment_player_queue.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@
android:text="@string/menu_download_all_button"
app:icon="@android:drawable/stat_sys_download_done" />

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_save_queue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone"
android:text="@string/player_queue_save_queue"
app:icon="@drawable/ic_bookmark" />

<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab_load_queue"
android:layout_width="wrap_content"
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,14 @@
</string-array>

<string-array name="queue_syncing_countdown_titles">
<item>Thirty seconds</item>
<item>Twenty seconds</item>
<item>Ten seconds</item>
<item>Five seconds</item>
<item>Two seconds</item>
</string-array>
<string-array name="queue_syncing_countdown_values">
<item>30</item>
<item>20</item>
<item>10</item>
<item>5</item>
<item>2</item>
</string-array>

<string-array name="rounded_corner_size_titles">
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
<string name="playback_speed_dialog_title">Playback Speed</string>
<string name="playback_speed_dialog_negative_button">Cancel</string>
<string name="player_queue_clean_all_button">Clean play queue</string>
<string name="player_queue_save_queue">Save Queue</string>
<string name="player_queue_save_queue_success">Saved play queue</string>
<string name="player_queue_save_to_playlist">Save Queue to Playlist</string>
<string name="player_queue_load_queue">Load Queue</string>
Expand Down