From 118a2752cb898a11c3ee1a36baf211873f2bc82f Mon Sep 17 00:00:00 2001 From: jakobkukla Date: Thu, 17 Mar 2022 23:02:41 +0100 Subject: [PATCH 1/5] remove prepareNext() --- .../gramophone/service/MusicService.java | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java index 80bfbb92..2de39b23 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java +++ b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java @@ -104,7 +104,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final int TRACK_ENDED = 2; public static final int PLAY_SONG = 3; - public static final int PREPARE_NEXT = 4; public static final int SAVE_QUEUE = 0; public static final int LOAD_QUEUE = 9; @@ -146,8 +145,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onRepeatModeChanged() { notifyChange(REPEAT_MODE_CHANGED); - // FIXME This call will be removed in a subsequent PR - prepareNext(); } @Override @@ -168,7 +165,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP if (ready) { progressHandler.sendEmptyMessage(TRACK_STARTED); - prepareNext(); } else if (reason == PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM) { progressHandler.sendEmptyMessage(TRACK_ENDED); } @@ -183,7 +179,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP progressHandler.sendEmptyMessage(TRACK_CHANGED); } else if (reason == MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) { progressHandler.sendEmptyMessage(TRACK_CHANGED); - prepareNext(); } notifyChange(STATE_CHANGED); @@ -492,7 +487,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return playback != null && playback.isLoading(); } - public void playNextSong(boolean force) { playSongAt(queueManager.getNextPosition(force)); } @@ -510,18 +504,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP playback.setDataSource(queueManager.getCurrentSong()); } - private void prepareNext() { - playerHandler.removeMessages(PREPARE_NEXT); - playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); - } - - private synchronized void prepareNextImpl() { - if (queueManager.getCurrentSong() == null) return; - - queueManager.nextPosition = queueManager.getNextPosition(false); - playback.queueDataSource(queueManager.getSongAt(queueManager.nextPosition)); - } - public void initNotification() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !PreferenceUtil.getInstance(this).getClassicNotification()) { playingNotification = new PlayingNotificationNougat(); @@ -716,9 +698,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP // because playing queue size might have changed updateMediaSessionMetadata(); saveState(); - if (queueManager.getPlayingQueue().size() > 0) { - prepareNext(); - } else { + if (queueManager.getPlayingQueue().size() <= 0) { playback.pause(); playingNotification.stop(); } @@ -777,10 +757,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP if (service.queueManager.getRepeatMode() == QueueManager.REPEAT_MODE_NONE && service.queueManager.isLastTrack()) { service.pause(); service.seek(0); - } else { - service.queueManager.position = service.queueManager.nextPosition; - service.prepareNextImpl(); - service.notifyChange(QUEUE_CHANGED); } break; @@ -806,10 +782,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP case PLAY_SONG: service.openTrackAndPrepareNextAt(msg.arg1); break; - - case PREPARE_NEXT: - service.prepareNextImpl(); - break; } } } From bce832c753e9d2644c1b02850c519d2f07300ca2 Mon Sep 17 00:00:00 2001 From: jakobkukla Date: Fri, 18 Mar 2022 01:36:42 +0100 Subject: [PATCH 2/5] rework playback --- .../gramophone/helper/MusicPlayerRemote.java | 12 +- .../gramophone/service/MusicService.java | 109 +++--------- .../gramophone/service/QueueManager.java | 156 ++++++++++-------- .../service/playback/LocalPlayer.java | 142 +++++++++------- .../gramophone/service/playback/Playback.java | 13 +- .../gramophone/util/PreferenceUtil.java | 4 +- 6 files changed, 212 insertions(+), 224 deletions(-) diff --git a/app/src/main/java/com/dkanada/gramophone/helper/MusicPlayerRemote.java b/app/src/main/java/com/dkanada/gramophone/helper/MusicPlayerRemote.java index 61838fb4..f4d4d5f1 100644 --- a/app/src/main/java/com/dkanada/gramophone/helper/MusicPlayerRemote.java +++ b/app/src/main/java/com/dkanada/gramophone/helper/MusicPlayerRemote.java @@ -111,19 +111,19 @@ public class MusicPlayerRemote { public static void playNextSong() { if (musicService != null) { - musicService.playNextSong(true); + musicService.playNextSong(); } } public static void playPreviousSong() { if (musicService != null) { - musicService.playPreviousSong(true); + musicService.playPreviousSong(); } } public static void back() { if (musicService != null) { - musicService.back(true); + musicService.back(); } } @@ -143,10 +143,10 @@ public class MusicPlayerRemote { public static void openQueue(final List queue, final int startPosition, final boolean startPlaying) { if (!tryToHandleOpenPlayingQueue(queue, startPosition) && musicService != null) { - musicService.openQueue(queue, startPosition, startPlaying); - if (!PreferenceUtil.getInstance(musicService).getRememberShuffle()){ + if (!PreferenceUtil.getInstance(musicService).getRememberShuffle()) { setShuffleMode(QueueManager.SHUFFLE_MODE_NONE); } + musicService.openQueue(queue, startPosition, startPlaying); } } @@ -157,8 +157,8 @@ public class MusicPlayerRemote { } if (!tryToHandleOpenPlayingQueue(queue, startPosition) && musicService != null) { - openQueue(queue, startPosition, startPlaying); setShuffleMode(QueueManager.SHUFFLE_MODE_SHUFFLE); + openQueue(queue, startPosition, startPlaying); } } diff --git a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java index 2de39b23..4107fadd 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java +++ b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java @@ -103,8 +103,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public static final int TRACK_CHANGED = 1; public static final int TRACK_ENDED = 2; - public static final int PLAY_SONG = 3; - public static final int SAVE_QUEUE = 0; public static final int LOAD_QUEUE = 9; @@ -139,11 +137,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP public final QueueManager.QueueCallbacks queueCallbacks = new QueueManager.QueueCallbacks() { @Override public void onQueueChanged() { + playback.setQueue(queueManager.getPlayingQueue(), queueManager.getPosition(), queueManager.getRestoredProgress(), queueManager.isResetCurrentSong()); + saveState(); notifyChange(QUEUE_CHANGED); } @Override public void onRepeatModeChanged() { + playback.setRepeatMode(queueManager.getRepeatMode()); notifyChange(REPEAT_MODE_CHANGED); } @@ -175,8 +176,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP acquireWakeLock(30000); if (reason == MEDIA_ITEM_TRANSITION_REASON_AUTO) { - playerHandler.sendEmptyMessage(TRACK_CHANGED); progressHandler.sendEmptyMessage(TRACK_CHANGED); + queueManager.setNextPosition(); } else if (reason == MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) { progressHandler.sendEmptyMessage(TRACK_CHANGED); } @@ -285,12 +286,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onSkipToNext() { - playNextSong(true); + playNextSong(); } @Override public void onSkipToPrevious() { - back(true); + back(); } @Override @@ -352,10 +353,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } break; case ACTION_REWIND: - back(true); + back(); break; case ACTION_SKIP: - playNextSong(true); + playNextSong(); break; case ACTION_STOP: case ACTION_QUIT: @@ -417,14 +418,10 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP queueHandler.removeMessages(SAVE_QUEUE); queueHandler.sendEmptyMessage(SAVE_QUEUE); - PreferenceUtil.getInstance(this).setPosition(queueManager.position); PreferenceUtil.getInstance(this).setProgress(getSongProgressMillis()); } private void restoreState() { - queueManager.shuffleMode = PreferenceUtil.getInstance(this).getShuffle(); - queueManager.repeatMode = PreferenceUtil.getInstance(this).getRepeat(); - notifyChange(SHUFFLE_MODE_CHANGED); notifyChange(REPEAT_MODE_CHANGED); @@ -432,27 +429,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP queueHandler.sendEmptyMessage(LOAD_QUEUE); } - // FIXME This will be refactored and partly moved to QueueManager in a subsequent PR private synchronized void restoreQueuesAndPositionIfNecessary() { if (!queuesRestored && queueManager.getPlayingQueue().isEmpty()) { - List restoredQueue = App.getDatabase().queueSongDao().getQueue(0); - List restoredOriginalQueue = App.getDatabase().queueSongDao().getQueue(1); - - int restoredPosition = PreferenceUtil.getInstance(this).getPosition(); - int restoredProgress = PreferenceUtil.getInstance(this).getProgress(); - - if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { - queueManager.originalPlayingQueue = restoredOriginalQueue; - queueManager.playingQueue = restoredQueue; - - queueManager.position = restoredPosition; - openCurrent(); - - if (restoredProgress > 0) seek(restoredProgress); - - handleChangeInternal(META_CHANGED); - handleChangeInternal(QUEUE_CHANGED); - } + queueManager.restoreQueue(); } queuesRestored = true; @@ -487,21 +466,14 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP return playback != null && playback.isLoading(); } - public void playNextSong(boolean force) { - playSongAt(queueManager.getNextPosition(force)); + public void playPreviousSong() { + queueManager.setPreviousPosition(); + playback.previous(); } - private synchronized void openTrackAndPrepareNextAt(int position) { - queueManager.position = position; - - openCurrent(); - playback.start(); - } - - private synchronized void openCurrent() { - if (queueManager.getCurrentSong() == null) return; - - playback.setDataSource(queueManager.getCurrentSong()); + public void playNextSong() { + queueManager.setNextPosition(); + playback.next(); } public void initNotification() { @@ -543,7 +515,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, queueManager.position + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, queueManager.getPosition() + 1) .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); @@ -600,26 +572,24 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP // FIXME This will be refactored and partly moved to QueueManager in a subsequent PR public void openQueue(@Nullable final List playingQueue, final int startPosition, final boolean startPlaying) { if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) { - // it is important to copy the playing queue here first as we might add or remove songs later queueManager.originalPlayingQueue = new ArrayList<>(playingQueue); queueManager.playingQueue = new ArrayList<>(queueManager.originalPlayingQueue); int position = startPosition; - if (queueManager.shuffleMode == QueueManager.SHUFFLE_MODE_SHUFFLE) { + if (queueManager.getShuffleMode() == QueueManager.SHUFFLE_MODE_SHUFFLE) { ShuffleHelper.makeShuffleList(queueManager.playingQueue, startPosition); position = 0; } - playSongAt(position); - - notifyChange(QUEUE_CHANGED); + queueManager.setPlayingQueueAndPosition(queueManager.playingQueue, queueManager.originalPlayingQueue, startPosition); + if (startPlaying) play(); } } public void playSongAt(final int position) { - // handle this on the handlers thread to avoid blocking the ui thread - playerHandler.removeMessages(PLAY_SONG); - playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); + queueManager.setPosition(position); + playback.playSongAt(position); + play(); } public void pause() { @@ -628,25 +598,17 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } } - public synchronized void play() { + public void play() { if (!playback.isPlaying()) { - if (!playback.isReady()) { - playSongAt(queueManager.position); - } else { - playback.start(); - } + playback.start(); } } - public void playPreviousSong(boolean force) { - playSongAt(queueManager.getPreviousPosition(force)); - } - - public void back(boolean force) { + public void back() { if (getSongProgressMillis() > 5000) { seek(0); } else { - playPreviousSong(force); + playPreviousSong(); } } @@ -691,15 +653,13 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP updateNotification(); updateMediaSessionMetadata(); updateMediaSessionState(); - PreferenceUtil.getInstance(this).setPosition(queueManager.position); + PreferenceUtil.getInstance(this).setPosition(queueManager.getPosition()); PreferenceUtil.getInstance(this).setProgress(getSongProgressMillis()); break; case QUEUE_CHANGED: // because playing queue size might have changed updateMediaSessionMetadata(); - saveState(); if (queueManager.getPlayingQueue().size() <= 0) { - playback.pause(); playingNotification.stop(); } break; @@ -753,35 +713,20 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } switch (msg.what) { - case TRACK_CHANGED: - if (service.queueManager.getRepeatMode() == QueueManager.REPEAT_MODE_NONE && service.queueManager.isLastTrack()) { - service.pause(); - service.seek(0); - } - break; - case TRACK_ENDED: // FIXME This isn't used anywhere. This means releaseWakeLock() is never called // if there is a timer finished, don't continue if (service.pendingQuit || service.queueManager.getRepeatMode() == QueueManager.REPEAT_MODE_NONE && service.queueManager.isLastTrack()) { - service.notifyChange(STATE_CHANGED); - service.seek(0); if (service.pendingQuit) { service.pendingQuit = false; service.quit(); break; } - } else { - service.playNextSong(false); } service.releaseWakeLock(); break; - - case PLAY_SONG: - service.openTrackAndPrepareNextAt(msg.arg1); - break; } } } diff --git a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java index 46d6cd4a..4bd0bf60 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java +++ b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java @@ -3,7 +3,6 @@ package com.dkanada.gramophone.service; import android.content.Context; import com.dkanada.gramophone.App; -import com.dkanada.gramophone.helper.MusicPlayerRemote; import com.dkanada.gramophone.helper.ShuffleHelper; import com.dkanada.gramophone.model.Song; import com.dkanada.gramophone.util.PreferenceUtil; @@ -13,8 +12,8 @@ import java.util.List; public class QueueManager { public static final int REPEAT_MODE_NONE = 0; - public static final int REPEAT_MODE_ALL = 1; - public static final int REPEAT_MODE_THIS = 2; + public static final int REPEAT_MODE_THIS = 1; + public static final int REPEAT_MODE_ALL = 2; public static final int SHUFFLE_MODE_NONE = 0; public static final int SHUFFLE_MODE_SHUFFLE = 1; @@ -25,17 +24,26 @@ public class QueueManager { List playingQueue = new ArrayList<>(); List originalPlayingQueue = new ArrayList<>(); - int position = -1; - int nextPosition = -1; + private int position = 0; + private int restoredProgress = 0; + private boolean resetCurrentSong = true; - int shuffleMode; - int repeatMode; + private int shuffleMode; + private int repeatMode; public QueueManager(Context context, QueueCallbacks callbacks) { this.context = context; this.callbacks = callbacks; } + public void setPlayingQueueAndPosition(List queue, List originalPlayingQueue, int position) { + this.position = position; + this.playingQueue = new ArrayList<>(queue); + this.originalPlayingQueue = new ArrayList<>(originalPlayingQueue); + + callbacks.onQueueChanged(); + } + public int getPosition() { return position; } @@ -52,60 +60,32 @@ public class QueueManager { return null; } - public int getNextPosition(boolean force) { - int position = getPosition() + 1; - switch (getRepeatMode()) { - case REPEAT_MODE_ALL: - if (isLastTrack()) { - position = 0; - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (isLastTrack()) { - position = 0; - } - } else { - position -= 1; - } - break; - default: - case REPEAT_MODE_NONE: - if (isLastTrack()) { - position -= 1; - } - break; - } - - return position; + public void setPosition(int position) { + this.position = position; } - public int getPreviousPosition(boolean force) { - int newPosition = getPosition() - 1; - switch (repeatMode) { - case REPEAT_MODE_ALL: - if (newPosition < 0) { - newPosition = getPlayingQueue().size() - 1; - } - break; - case REPEAT_MODE_THIS: - if (force) { - if (newPosition < 0) { - newPosition = getPlayingQueue().size() - 1; - } - } else { - newPosition = getPosition(); - } - break; - default: + public void setNextPosition() { + switch (getRepeatMode()) { case REPEAT_MODE_NONE: - if (newPosition < 0) { - newPosition = 0; - } + case REPEAT_MODE_THIS: + position = Math.min(position + 1, playingQueue.size() - 1); + break; + case REPEAT_MODE_ALL: + position = (position + 1) % playingQueue.size(); break; } + } - return newPosition; + public void setPreviousPosition() { + switch (getRepeatMode()) { + case REPEAT_MODE_NONE: + case REPEAT_MODE_THIS: + position = Math.max(position - 1, 0); + break; + case REPEAT_MODE_ALL: + position = (position - 1 + playingQueue.size()) % playingQueue.size(); + break; + } } public boolean isLastTrack() { @@ -168,6 +148,7 @@ public class QueueManager { break; } + resetCurrentSong = false; callbacks.onShuffleModeChanged(); callbacks.onQueueChanged(); } @@ -175,24 +156,32 @@ public class QueueManager { public void addSong(int position, Song song) { playingQueue.add(position, song); originalPlayingQueue.add(position, song); + + resetCurrentSong = false; callbacks.onQueueChanged(); } public void addSong(Song song) { playingQueue.add(song); originalPlayingQueue.add(song); + + resetCurrentSong = false; callbacks.onQueueChanged(); } public void addSongs(int position, List songs) { playingQueue.addAll(position, songs); originalPlayingQueue.addAll(position, songs); + + resetCurrentSong = false; callbacks.onQueueChanged(); } public void addSongs(List songs) { playingQueue.addAll(songs); originalPlayingQueue.addAll(songs); + + resetCurrentSong = false; callbacks.onQueueChanged(); } @@ -204,26 +193,21 @@ public class QueueManager { originalPlayingQueue.remove(playingQueue.remove(position)); } - reposition(position); - callbacks.onQueueChanged(); - } - - // FIXME This will be refactored and removed in a subsequent PR - private void reposition(int deletedPosition) { int currentPosition = getPosition(); - if (deletedPosition < currentPosition) { - position = currentPosition - 1; - } else if (deletedPosition == currentPosition) { - if (playingQueue.size() > deletedPosition) { - MusicPlayerRemote.playSongAt(position); - } else { - MusicPlayerRemote.playSongAt(position - 1); - } + if (position != currentPosition) { + resetCurrentSong = false; } + + if (position < currentPosition) { + this.position = currentPosition - 1; + } + + callbacks.onQueueChanged(); } public void moveSong(int from, int to) { if (from == to) return; + final int currentPosition = getPosition(); Song songToMove = playingQueue.remove(from); playingQueue.add(to, songToMove); @@ -240,6 +224,7 @@ public class QueueManager { position = to; } + resetCurrentSong = false; callbacks.onQueueChanged(); } @@ -247,7 +232,7 @@ public class QueueManager { playingQueue.clear(); originalPlayingQueue.clear(); - position = -1; + position = 0; callbacks.onQueueChanged(); } @@ -274,7 +259,24 @@ public class QueueManager { } } + public void restoreQueue() { + position = PreferenceUtil.getInstance(context).getPosition(); + restoredProgress = PreferenceUtil.getInstance(context).getProgress(); + + playingQueue = new ArrayList<>(App.getDatabase().queueSongDao().getQueue(0)); + originalPlayingQueue = new ArrayList<>(App.getDatabase().queueSongDao().getQueue(1)); + + shuffleMode = PreferenceUtil.getInstance(context).getShuffle(); + repeatMode = PreferenceUtil.getInstance(context).getRepeat(); + + callbacks.onQueueChanged(); + callbacks.onRepeatModeChanged(); + callbacks.onShuffleModeChanged(); + } + public void saveQueue() { + PreferenceUtil.getInstance(context).setPosition(position); + // copy queues by value to avoid concurrent modification exceptions from database App.getDatabase().songDao().deleteSongs(); App.getDatabase().songDao().insertSongs(new ArrayList<>(playingQueue)); @@ -284,6 +286,20 @@ public class QueueManager { App.getDatabase().queueSongDao().setQueue(new ArrayList<>(originalPlayingQueue), 1); } + public int getRestoredProgress() { + int progress = restoredProgress; + restoredProgress = 0; + + return progress; + } + + public boolean isResetCurrentSong() { + boolean reset = resetCurrentSong; + resetCurrentSong = true; + + return reset; + } + interface QueueCallbacks { void onQueueChanged(); void onRepeatModeChanged(); diff --git a/app/src/main/java/com/dkanada/gramophone/service/playback/LocalPlayer.java b/app/src/main/java/com/dkanada/gramophone/service/playback/LocalPlayer.java index 57595bba..20b093f7 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/playback/LocalPlayer.java +++ b/app/src/main/java/com/dkanada/gramophone/service/playback/LocalPlayer.java @@ -30,6 +30,8 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.io.File; import java.util.List; import java.util.Locale; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; public class LocalPlayer implements Playback { @@ -41,6 +43,8 @@ public class LocalPlayer implements Playback { private PlaybackCallbacks callbacks; + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + @SuppressWarnings("FieldCanBeLocal") private final EventListener eventListener = new EventListener() { @Override @@ -64,14 +68,7 @@ public class LocalPlayer implements Playback { @Override public void onMediaItemTransition(MediaItem mediaItem, int reason) { Log.i(TAG, String.format("onMediaItemTransition: %s %d", mediaItem, reason)); - - if (exoPlayer.getMediaItemCount() > 1) { - exoPlayer.removeMediaItem(0); - } - - if (callbacks != null) { - callbacks.onTrackChanged(reason); - } + if (callbacks != null) callbacks.onTrackChanged(reason); } @Override @@ -115,66 +112,72 @@ public class LocalPlayer implements Playback { } @Override - public void setDataSource(Song song) { - MediaItem mediaItem = exoPlayer.getCurrentMediaItem(); + public void setQueue(List queue, int position, int progress, boolean resetCurrentSong) { + executorService.submit(() -> { + List mediaItems = createMediaItems(queue); - if (mediaItem != null && mediaItem.mediaId.equals(song.id)) { - return; - } + // TODO: Call this on main thread + if (resetCurrentSong) { + exoPlayer.setMediaItems(mediaItems, position, progress); + return; + } - exoPlayer.clearMediaItems(); - appendDataSource(song); - exoPlayer.seekTo(0, 0); + int currentPosition = exoPlayer.getCurrentWindowIndex(); + exoPlayer.removeMediaItems(0, currentPosition); + + if (exoPlayer.getMediaItemCount() > 1) { + exoPlayer.removeMediaItems(1, exoPlayer.getMediaItemCount()); + } + + if (position + 1 < mediaItems.size()) { + exoPlayer.addMediaItems(1, mediaItems.subList(position + 1, mediaItems.size())); + } + + exoPlayer.addMediaItems(0, mediaItems.subList(0, position)); + }); + } + + private List createMediaItems(List queue) { + return queue.stream().map(song -> { + File audio = new File(MusicUtil.getFileUri(song)); + Uri uri = Uri.fromFile(audio); + + if (!audio.exists()) { + uri = Uri.parse(MusicUtil.getTranscodeUri(song)); + } + + List containers = PreferenceUtil.getInstance(context).getDirectPlayCodecs().stream() + .map(codec -> codec.container.toLowerCase(Locale.ROOT)) + .collect(Collectors.toList()); + List codecs = PreferenceUtil.getInstance(context).getDirectPlayCodecs().stream() + .map(codec -> codec.codec.toLowerCase(Locale.ROOT)) + .collect(Collectors.toList()); + String maxBitrate = PreferenceUtil.getInstance(context).getMaximumBitrate(); + + MediaItem mediaItem; + + if (uri.toString().contains("file://") || (containers.contains(song.container.toLowerCase(Locale.ROOT)) && codecs.contains(song.codec.toLowerCase(Locale.ROOT)) && song.bitRate <= Integer.parseInt(maxBitrate))) { + mediaItem = new MediaItem.Builder() + .setUri(uri) + .setMediaId(song.id) + .build(); + } else { + mediaItem = new MediaItem.Builder() + .setUri(uri) + .setMediaId(song.id) + .setMimeType(MimeTypes.APPLICATION_M3U8) + .build(); + } + + return mediaItem; + }).collect(Collectors.toList()); } @Override - public void queueDataSource(Song song) { - while (exoPlayer.getMediaItemCount() > 1) { - exoPlayer.removeMediaItem(1); + public void playSongAt(int position) { + if (exoPlayer.getMediaItemCount() > 0) { + exoPlayer.seekTo(Math.max(0, position) % exoPlayer.getMediaItemCount(), 0); } - - appendDataSource(song); - } - - private void appendDataSource(Song song) { - File audio = new File(MusicUtil.getFileUri(song)); - Uri uri = Uri.fromFile(audio); - - if (!audio.exists()) { - uri = Uri.parse(MusicUtil.getTranscodeUri(song)); - } - - List containers = PreferenceUtil.getInstance(context).getDirectPlayCodecs().stream() - .map(codec -> codec.container.toLowerCase(Locale.ROOT)) - .collect(Collectors.toList()); - - List codecs = PreferenceUtil.getInstance(context).getDirectPlayCodecs().stream() - .map(codec -> codec.codec.toLowerCase(Locale.ROOT)) - .collect(Collectors.toList()); - - String maxBitrate = PreferenceUtil.getInstance(context).getMaximumBitrate(); - - MediaItem mediaItem; - - boolean shouldDirectPlay = - containers.contains(song.container.toLowerCase(Locale.ROOT)) && - codecs.contains(song.codec.toLowerCase(Locale.ROOT)) && - song.bitRate <= Integer.parseInt(maxBitrate); - - if (uri.toString().contains("file://") || shouldDirectPlay || !song.supportsTranscoding) { - mediaItem = new MediaItem.Builder() - .setUri(uri) - .setMediaId(song.id) - .build(); - } else { - mediaItem = new MediaItem.Builder() - .setUri(uri) - .setMediaId(song.id) - .setMimeType(MimeTypes.APPLICATION_M3U8) - .build(); - } - - exoPlayer.addMediaItem(mediaItem); } private DataSource.Factory buildDataSourceFactory() { @@ -230,6 +233,21 @@ public class LocalPlayer implements Playback { exoPlayer.release(); } + @Override + public void previous() { + exoPlayer.previous(); + } + + @Override + public void next() { + exoPlayer.next(); + } + + @Override + public void setRepeatMode(@Player.RepeatMode int repeatMode) { + exoPlayer.setRepeatMode(repeatMode); + } + @Override public int getProgress() { return (int) exoPlayer.getCurrentPosition(); diff --git a/app/src/main/java/com/dkanada/gramophone/service/playback/Playback.java b/app/src/main/java/com/dkanada/gramophone/service/playback/Playback.java index dddc1089..6570e6e3 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/playback/Playback.java +++ b/app/src/main/java/com/dkanada/gramophone/service/playback/Playback.java @@ -1,11 +1,14 @@ package com.dkanada.gramophone.service.playback; import com.dkanada.gramophone.model.Song; +import com.google.android.exoplayer2.Player; + +import java.util.List; public interface Playback { - void setDataSource(Song song); + void setQueue(List queue, int position, int progress, boolean resetCurrentSong); - void queueDataSource(Song song); + void playSongAt(int position); void setCallbacks(PlaybackCallbacks callbacks); @@ -21,6 +24,12 @@ public interface Playback { void stop(); + void previous(); + + void next(); + + void setRepeatMode(@Player.RepeatMode int repeatMode); + int getProgress(); int getDuration(); diff --git a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java index d8e03152..13c851dc 100644 --- a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java @@ -170,7 +170,7 @@ public final class PreferenceUtil { } public int getPosition() { - return mPreferences.getInt(POSITION, -1); + return mPreferences.getInt(POSITION, 0); } public void setPosition(int position) { @@ -178,7 +178,7 @@ public final class PreferenceUtil { } public int getProgress() { - return mPreferences.getInt(PROGRESS, -1); + return mPreferences.getInt(PROGRESS, 0); } public void setProgress(int progress) { From 7237003e33264defd817e3c3a8d6204ff210b67c Mon Sep 17 00:00:00 2001 From: jakobkukla Date: Fri, 18 Mar 2022 01:51:11 +0100 Subject: [PATCH 3/5] rework shuffle --- .../gramophone/helper/ShuffleHelper.java | 23 ----- .../gramophone/service/MusicService.java | 16 +--- .../gramophone/service/QueueManager.java | 89 +++++++++++-------- 3 files changed, 56 insertions(+), 72 deletions(-) delete mode 100644 app/src/main/java/com/dkanada/gramophone/helper/ShuffleHelper.java diff --git a/app/src/main/java/com/dkanada/gramophone/helper/ShuffleHelper.java b/app/src/main/java/com/dkanada/gramophone/helper/ShuffleHelper.java deleted file mode 100644 index 5dbc148f..00000000 --- a/app/src/main/java/com/dkanada/gramophone/helper/ShuffleHelper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.dkanada.gramophone.helper; - -import androidx.annotation.NonNull; - -import com.dkanada.gramophone.model.Song; - -import java.util.Collections; -import java.util.List; - -public class ShuffleHelper { - public static void makeShuffleList(@NonNull List listToShuffle, final int current) { - if (listToShuffle.isEmpty()) return; - - if (current >= 0) { - Song song = listToShuffle.remove(current); - - Collections.shuffle(listToShuffle); - listToShuffle.add(0, song); - } else { - Collections.shuffle(listToShuffle); - } - } -} diff --git a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java index 4107fadd..b7281701 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java +++ b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java @@ -39,7 +39,6 @@ import com.dkanada.gramophone.BuildConfig; import com.dkanada.gramophone.R; import com.dkanada.gramophone.glide.BlurTransformation; import com.dkanada.gramophone.glide.CustomGlideRequest; -import com.dkanada.gramophone.helper.ShuffleHelper; import com.dkanada.gramophone.model.Playlist; import com.dkanada.gramophone.model.Song; import com.dkanada.gramophone.service.notifications.PlayingNotification; @@ -569,19 +568,9 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP uiThreadHandler.post(runnable); } - // FIXME This will be refactored and partly moved to QueueManager in a subsequent PR public void openQueue(@Nullable final List playingQueue, final int startPosition, final boolean startPlaying) { if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) { - queueManager.originalPlayingQueue = new ArrayList<>(playingQueue); - queueManager.playingQueue = new ArrayList<>(queueManager.originalPlayingQueue); - - int position = startPosition; - if (queueManager.getShuffleMode() == QueueManager.SHUFFLE_MODE_SHUFFLE) { - ShuffleHelper.makeShuffleList(queueManager.playingQueue, startPosition); - position = 0; - } - - queueManager.setPlayingQueueAndPosition(queueManager.playingQueue, queueManager.originalPlayingQueue, startPosition); + queueManager.setPlayingQueueAndPosition(playingQueue, startPosition); if (startPlaying) play(); } } @@ -713,7 +702,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } switch (msg.what) { - case TRACK_ENDED: + /*case TRACK_ENDED: // FIXME This isn't used anywhere. This means releaseWakeLock() is never called // if there is a timer finished, don't continue @@ -727,6 +716,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP service.releaseWakeLock(); break; + */ } } } diff --git a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java index 4bd0bf60..0cfc9745 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java +++ b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java @@ -3,12 +3,13 @@ package com.dkanada.gramophone.service; import android.content.Context; import com.dkanada.gramophone.App; -import com.dkanada.gramophone.helper.ShuffleHelper; import com.dkanada.gramophone.model.Song; import com.dkanada.gramophone.util.PreferenceUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; public class QueueManager { public static final int REPEAT_MODE_NONE = 0; @@ -21,8 +22,8 @@ public class QueueManager { private final Context context; private final QueueCallbacks callbacks; - List playingQueue = new ArrayList<>(); - List originalPlayingQueue = new ArrayList<>(); + private List playingQueue = new ArrayList<>(); + private List shuffledQueue = new ArrayList<>(); private int position = 0; private int restoredProgress = 0; @@ -36,14 +37,19 @@ public class QueueManager { this.callbacks = callbacks; } - public void setPlayingQueueAndPosition(List queue, List originalPlayingQueue, int position) { + public void setPlayingQueueAndPosition(List queue, int position) { this.position = position; this.playingQueue = new ArrayList<>(queue); - this.originalPlayingQueue = new ArrayList<>(originalPlayingQueue); + this.shuffledQueue = new ArrayList<>(queue); + shuffleQueue(); callbacks.onQueueChanged(); } + public List getPlayingQueue() { + return shuffleMode == SHUFFLE_MODE_SHUFFLE ? shuffledQueue : playingQueue; + } + public int getPosition() { return position; } @@ -88,14 +94,6 @@ public class QueueManager { } } - public boolean isLastTrack() { - return getPosition() == getPlayingQueue().size() - 1; - } - - public List getPlayingQueue() { - return playingQueue; - } - public int getRepeatMode() { return repeatMode; } @@ -130,21 +128,25 @@ public class QueueManager { switch (shuffleMode) { case SHUFFLE_MODE_SHUFFLE: this.shuffleMode = shuffleMode; - ShuffleHelper.makeShuffleList(this.getPlayingQueue(), getPosition()); - position = 0; + shuffleQueue(); + break; case SHUFFLE_MODE_NONE: - this.shuffleMode = shuffleMode; String currentSongId = getCurrentSong().id; - playingQueue = new ArrayList<>(originalPlayingQueue); int newPosition = 0; - for (Song song : getPlayingQueue()) { - if (song.id == currentSongId) { - newPosition = getPlayingQueue().indexOf(song); - } + + Optional currentSong = playingQueue.stream() + .filter(song -> song.id.equals(currentSongId)) + .findFirst(); + + if (currentSong.isPresent()) { + newPosition = playingQueue.indexOf(currentSong.get()); } + shuffledQueue = new ArrayList<>(playingQueue); + position = newPosition; + this.shuffleMode = shuffleMode; break; } @@ -153,9 +155,28 @@ public class QueueManager { callbacks.onQueueChanged(); } + private void shuffleQueue() { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + this.shuffledQueue = new ArrayList<>(playingQueue); + + if (!shuffledQueue.isEmpty()) { + if (getPosition() >= 0) { + Song song = shuffledQueue.remove(getPosition()); + + Collections.shuffle(shuffledQueue); + shuffledQueue.add(0, song); + } else { + Collections.shuffle(shuffledQueue); + } + } + + position = 0; + } + } + public void addSong(int position, Song song) { playingQueue.add(position, song); - originalPlayingQueue.add(position, song); + shuffledQueue.add(position, song); resetCurrentSong = false; callbacks.onQueueChanged(); @@ -163,7 +184,7 @@ public class QueueManager { public void addSong(Song song) { playingQueue.add(song); - originalPlayingQueue.add(song); + shuffledQueue.add(song); resetCurrentSong = false; callbacks.onQueueChanged(); @@ -171,7 +192,7 @@ public class QueueManager { public void addSongs(int position, List songs) { playingQueue.addAll(position, songs); - originalPlayingQueue.addAll(position, songs); + shuffledQueue.addAll(position, songs); resetCurrentSong = false; callbacks.onQueueChanged(); @@ -179,7 +200,7 @@ public class QueueManager { public void addSongs(List songs) { playingQueue.addAll(songs); - originalPlayingQueue.addAll(songs); + shuffledQueue.addAll(songs); resetCurrentSong = false; callbacks.onQueueChanged(); @@ -188,9 +209,9 @@ public class QueueManager { public void removeSong(int position) { if (getShuffleMode() == SHUFFLE_MODE_NONE) { playingQueue.remove(position); - originalPlayingQueue.remove(position); + shuffledQueue.remove(position); } else { - originalPlayingQueue.remove(playingQueue.remove(position)); + playingQueue.remove(shuffledQueue.remove(position)); } int currentPosition = getPosition(); @@ -209,12 +230,8 @@ public class QueueManager { if (from == to) return; final int currentPosition = getPosition(); - Song songToMove = playingQueue.remove(from); - playingQueue.add(to, songToMove); - if (getShuffleMode() == SHUFFLE_MODE_NONE) { - Song tmpSong = originalPlayingQueue.remove(from); - originalPlayingQueue.add(to, tmpSong); - } + Song songToMove = getPlayingQueue().remove(from); + getPlayingQueue().add(to, songToMove); if (from > currentPosition && to <= currentPosition) { position = currentPosition + 1; @@ -230,7 +247,7 @@ public class QueueManager { public void clearQueue() { playingQueue.clear(); - originalPlayingQueue.clear(); + shuffledQueue.clear(); position = 0; callbacks.onQueueChanged(); @@ -264,7 +281,7 @@ public class QueueManager { restoredProgress = PreferenceUtil.getInstance(context).getProgress(); playingQueue = new ArrayList<>(App.getDatabase().queueSongDao().getQueue(0)); - originalPlayingQueue = new ArrayList<>(App.getDatabase().queueSongDao().getQueue(1)); + shuffledQueue = new ArrayList<>(App.getDatabase().queueSongDao().getQueue(1)); shuffleMode = PreferenceUtil.getInstance(context).getShuffle(); repeatMode = PreferenceUtil.getInstance(context).getRepeat(); @@ -283,7 +300,7 @@ public class QueueManager { App.getDatabase().queueSongDao().deleteQueueSongs(); App.getDatabase().queueSongDao().setQueue(new ArrayList<>(playingQueue), 0); - App.getDatabase().queueSongDao().setQueue(new ArrayList<>(originalPlayingQueue), 1); + App.getDatabase().queueSongDao().setQueue(new ArrayList<>(shuffledQueue), 1); } public int getRestoredProgress() { From f95e2e89b9900ef0b782395260d1a56efc8b174e Mon Sep 17 00:00:00 2001 From: jakobkukla Date: Fri, 18 Mar 2022 02:12:57 +0100 Subject: [PATCH 4/5] remove playerHandler and release wakelock properly --- .../gramophone/service/MusicService.java | 57 ++----------------- .../gramophone/service/QueueManager.java | 3 +- 2 files changed, 8 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java index b7281701..987db7f6 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java +++ b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java @@ -52,6 +52,7 @@ import com.dkanada.gramophone.util.Util; import com.dkanada.gramophone.views.widgets.AppWidgetAlbum; import com.dkanada.gramophone.views.widgets.AppWidgetCard; import com.dkanada.gramophone.views.widgets.AppWidgetClassic; +import com.google.android.exoplayer2.Player; import org.jellyfin.apiclient.interaction.EmptyResponse; import org.jellyfin.apiclient.interaction.Response; @@ -123,13 +124,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private MediaSessionCompat mediaSession; private PowerManager.WakeLock wakeLock; - private PlaybackHandler playerHandler; private Handler uiThreadHandler; private ThrottledSeekHandler throttledSeekHandler; private QueueHandler queueHandler; private ProgressHandler progressHandler; - private HandlerThread playerHandlerThread; private HandlerThread progressHandlerThread; private HandlerThread queueHandlerThread; @@ -157,6 +156,11 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP @Override public void onStateChanged(int state) { notifyChange(STATE_CHANGED); + + if (state == Player.STATE_ENDED) { + playingNotification.stop(); + releaseWakeLock(); + } } @Override @@ -236,10 +240,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP queueManager = new QueueManager(this, queueCallbacks); - playerHandlerThread = new HandlerThread(PlaybackHandler.class.getName()); - playerHandlerThread.start(); - playerHandler = new PlaybackHandler(this, playerHandlerThread.getLooper()); - progressHandlerThread = new HandlerThread(ProgressHandler.class.getName()); progressHandlerThread.start(); progressHandler = new ProgressHandler(this, progressHandlerThread.getLooper()); @@ -421,9 +421,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } private void restoreState() { - notifyChange(SHUFFLE_MODE_CHANGED); - notifyChange(REPEAT_MODE_CHANGED); - queueHandler.removeMessages(LOAD_QUEUE); queueHandler.sendEmptyMessage(LOAD_QUEUE); } @@ -444,9 +441,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } private void releaseResources() { - playerHandler.removeCallbacksAndMessages(null); - playerHandlerThread.quitSafely(); - progressHandler.removeCallbacksAndMessages(null); progressHandlerThread.quitSafely(); @@ -646,11 +640,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP PreferenceUtil.getInstance(this).setProgress(getSongProgressMillis()); break; case QUEUE_CHANGED: - // because playing queue size might have changed updateMediaSessionMetadata(); - if (queueManager.getPlayingQueue().size() <= 0) { - playingNotification.stop(); - } break; } } @@ -686,41 +676,6 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP } } - private static final class PlaybackHandler extends Handler { - private final WeakReference mService; - - public PlaybackHandler(final MusicService service, @NonNull final Looper looper) { - super(looper); - mService = new WeakReference<>(service); - } - - @Override - public void handleMessage(@NonNull final Message msg) { - final MusicService service = mService.get(); - if (service == null) { - return; - } - - switch (msg.what) { - /*case TRACK_ENDED: - // FIXME This isn't used anywhere. This means releaseWakeLock() is never called - - // if there is a timer finished, don't continue - if (service.pendingQuit || service.queueManager.getRepeatMode() == QueueManager.REPEAT_MODE_NONE && service.queueManager.isLastTrack()) { - if (service.pendingQuit) { - service.pendingQuit = false; - service.quit(); - break; - } - } - - service.releaseWakeLock(); - break; - */ - } - } - } - public class MusicBinder extends Binder { @NonNull public MusicService getService() { diff --git a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java index 0cfc9745..86604c8e 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java +++ b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java @@ -5,6 +5,7 @@ import android.content.Context; import com.dkanada.gramophone.App; import com.dkanada.gramophone.model.Song; import com.dkanada.gramophone.util.PreferenceUtil; +import com.google.android.exoplayer2.Player; import java.util.ArrayList; import java.util.Collections; @@ -30,7 +31,7 @@ public class QueueManager { private boolean resetCurrentSong = true; private int shuffleMode; - private int repeatMode; + private @Player.RepeatMode int repeatMode; public QueueManager(Context context, QueueCallbacks callbacks) { this.context = context; From bfd3818c0aace8bd3997ff684984f5a9d43ed8ca Mon Sep 17 00:00:00 2001 From: Jakob Kukla Date: Mon, 16 May 2022 23:36:48 +0200 Subject: [PATCH 5/5] fix database concurrency issue --- .../com/dkanada/gramophone/database/QueueSongDao.java | 11 +++++++++++ .../com/dkanada/gramophone/service/QueueManager.java | 9 +-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/dkanada/gramophone/database/QueueSongDao.java b/app/src/main/java/com/dkanada/gramophone/database/QueueSongDao.java index 69796902..9cf9ec9d 100644 --- a/app/src/main/java/com/dkanada/gramophone/database/QueueSongDao.java +++ b/app/src/main/java/com/dkanada/gramophone/database/QueueSongDao.java @@ -45,4 +45,15 @@ public abstract class QueueSongDao { insertQueueSongs(queueSongs); } + + @Transaction + public void updateQueues(List playingQueue, List shuffledQueue) { + // copy queues by value to avoid concurrent modification exceptions from database + App.getDatabase().songDao().deleteSongs(); + App.getDatabase().songDao().insertSongs(new ArrayList<>(playingQueue)); + + deleteQueueSongs(); + setQueue(new ArrayList<>(playingQueue), 0); + setQueue(new ArrayList<>(shuffledQueue), 1); + } } diff --git a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java index 86604c8e..ed783563 100644 --- a/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java +++ b/app/src/main/java/com/dkanada/gramophone/service/QueueManager.java @@ -294,14 +294,7 @@ public class QueueManager { public void saveQueue() { PreferenceUtil.getInstance(context).setPosition(position); - - // copy queues by value to avoid concurrent modification exceptions from database - App.getDatabase().songDao().deleteSongs(); - App.getDatabase().songDao().insertSongs(new ArrayList<>(playingQueue)); - - App.getDatabase().queueSongDao().deleteQueueSongs(); - App.getDatabase().queueSongDao().setQueue(new ArrayList<>(playingQueue), 0); - App.getDatabase().queueSongDao().setQueue(new ArrayList<>(shuffledQueue), 1); + App.getDatabase().queueSongDao().updateQueues(playingQueue, shuffledQueue); } public int getRestoredProgress() {