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 fcb59d63a..1abab30e5 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java @@ -229,13 +229,17 @@ public void onTimelineChanged(Timeline timeline, int reason) { } public static void startQueue(ListenableFuture mediaBrowserListenableFuture, Child media) { + startQueue(mediaBrowserListenableFuture, media, 0, 0); + } + + public static void startQueue(ListenableFuture 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); diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java index 3529d601e..b6a83a451 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java @@ -52,6 +52,8 @@ public class PlayerBottomSheetFragment extends Fragment { private Handler progressBarHandler; private Runnable progressBarRunnable; + private Handler autoSaveHandler; + private Runnable autoSaveRunnable; @Nullable @Override @@ -79,6 +81,8 @@ public void onStart() { @Override public void onStop() { + runProgressBarHandler(false); + runAutoSaveHandler(false); releaseMediaBrowser(); super.onStop(); } @@ -127,6 +131,7 @@ private void bindMediaController() { private void setMediaControllerListener(MediaBrowser mediaBrowser) { defineProgressBarHandler(mediaBrowser); + defineAutoSaveHandler(mediaBrowser); setMediaControllerUI(mediaBrowser); setMetadata(mediaBrowser.getMediaMetadata()); setContentDuration(mediaBrowser.getContentDuration()); @@ -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() { @@ -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() { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java index a2def4503..b194d9389 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java @@ -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 -> { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java index f1c10371e..dfc7eafd0 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerQueueFragment.java @@ -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; @@ -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; @@ -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()); @@ -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(); @@ -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); @@ -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(); } @@ -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()); + } + private void handleLoadQueueClick() { Log.d(TAG, "Load Queue Clicked!"); if (!Preferences.isSyncronizationEnabled()) { @@ -469,7 +496,14 @@ public void onChanged(PlayQueue playQueue) { } } - MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), currentIndex); + long positionMs = playQueue.getPosition() != null ? playQueue.getPosition() : 0L; + List 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 { diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt index c3822e266..6787116b9 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt @@ -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 diff --git a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlayerBottomSheetViewModel.java b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlayerBottomSheetViewModel.java index 19ced9995..f32d8443c 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlayerBottomSheetViewModel.java +++ b/app/src/main/java/com/cappielloantonio/tempo/viewmodel/PlayerBottomSheetViewModel.java @@ -286,15 +286,14 @@ public LiveData getPlayQueue() { return queueRepository.getPlayQueue(); } - public boolean savePlayQueue() { + public boolean savePlayQueue(long position) { Child media = getLiveMedia().getValue(); List queue = queueRepository.getMedia(); List 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; } return false; diff --git a/app/src/main/res/layout/inner_fragment_player_queue.xml b/app/src/main/res/layout/inner_fragment_player_queue.xml index 3ddd112b2..ba6f7cdf0 100644 --- a/app/src/main/res/layout/inner_fragment_player_queue.xml +++ b/app/src/main/res/layout/inner_fragment_player_queue.xml @@ -56,6 +56,15 @@ android:text="@string/menu_download_all_button" app:icon="@android:drawable/stat_sys_download_done" /> + + + Thirty seconds + Twenty seconds Ten seconds - Five seconds - Two seconds + 30 + 20 10 - 5 - 2 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 570b64640..5572c571e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -264,6 +264,7 @@ Playback Speed Cancel Clean play queue + Save Queue Saved play queue Save Queue to Playlist Load Queue