diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index cc0f9bf03..8d76972c5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -30,9 +30,9 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.oxycblt.auxio.R import org.oxycblt.auxio.list.Item -import org.oxycblt.auxio.music.storage.* import org.oxycblt.auxio.music.parsing.parseId3GenreNames import org.oxycblt.auxio.music.parsing.parseMultiValue +import org.oxycblt.auxio.music.storage.* import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.unlikelyToBeNull diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt index 88bce111f..1ac7b082a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt @@ -29,6 +29,7 @@ import androidx.core.database.getStringOrNull import java.io.File import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.parsing.parseId3v2Position import org.oxycblt.auxio.music.storage.Directory import org.oxycblt.auxio.music.storage.contentResolverSafe import org.oxycblt.auxio.music.storage.directoryCompat @@ -36,7 +37,6 @@ import org.oxycblt.auxio.music.storage.mediaStoreVolumeNameCompat import org.oxycblt.auxio.music.storage.safeQuery import org.oxycblt.auxio.music.storage.storageVolumesCompat import org.oxycblt.auxio.music.storage.useQuery -import org.oxycblt.auxio.music.parsing.parseId3v2Position import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt index 74efcb29a..d2047fabe 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt @@ -23,8 +23,8 @@ import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MetadataRetriever import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.storage.toAudioUri import org.oxycblt.auxio.music.parsing.parseId3v2Position +import org.oxycblt.auxio.music.storage.toAudioUri import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 2f5ea9f14..0ae5f2765 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -26,10 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.oxycblt.auxio.music.* -import org.oxycblt.auxio.playback.state.InternalPlayer -import org.oxycblt.auxio.playback.state.PlaybackStateDatabase -import org.oxycblt.auxio.playback.state.PlaybackStateManager -import org.oxycblt.auxio.playback.state.RepeatMode +import org.oxycblt.auxio.playback.state.* import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.context @@ -100,13 +97,18 @@ class PlaybackViewModel(application: Application) : playbackManager.removeListener(this) } - override fun onIndexMoved(index: Int) { - _song.value = playbackManager.song + override fun onIndexMoved(queue: Queue) { + _song.value = playbackManager.queue.currentSong } - override fun onNewPlayback(index: Int, queue: List, parent: MusicParent?) { - _song.value = playbackManager.song + override fun onQueueReworked(queue: Queue) { + _isShuffled.value = queue.isShuffled + } + + override fun onNewPlayback(queue: Queue, parent: MusicParent?) { + _song.value = playbackManager.queue.currentSong _parent.value = playbackManager.parent + _isShuffled.value = queue.isShuffled } override fun onStateChanged(state: InternalPlayer.State) { @@ -126,10 +128,6 @@ class PlaybackViewModel(application: Application) : } } - override fun onShuffledChanged(isShuffled: Boolean) { - _isShuffled.value = isShuffled - } - override fun onRepeatChanged(repeatMode: RepeatMode) { _repeatMode.value = repeatMode } @@ -141,21 +139,19 @@ class PlaybackViewModel(application: Application) : * @param song The [Song] to play. */ fun playFromAll(song: Song) { - playbackManager.play(song, null, settings) + playImpl(song, null) } /** Shuffle all songs in the music library. */ fun shuffleAll() { - playbackManager.play(null, null, settings, true) + playImpl(null, null, true) } /** * Play a [Song] from it's [Album]. * @param song The [Song] to play. */ - fun playFromAlbum(song: Song) { - playbackManager.play(song, song.album, settings) - } + fun playFromAlbum(song: Song) = playImpl(song, song.album) /** * Play a [Song] from one of it's [Artist]s. @@ -165,10 +161,9 @@ class PlaybackViewModel(application: Application) : */ fun playFromArtist(song: Song, artist: Artist? = null) { if (artist != null) { - check(artist in song.artists) { "Artist not in song artists" } - playbackManager.play(song, artist, settings) + playImpl(song, artist) } else if (song.artists.size == 1) { - playbackManager.play(song, song.artists[0], settings) + playImpl(song, song.artists[0]) } else { _artistPlaybackPickerSong.value = song } @@ -191,10 +186,9 @@ class PlaybackViewModel(application: Application) : */ fun playFromGenre(song: Song, genre: Genre? = null) { if (genre != null) { - check(genre.songs.contains(song)) { "Invalid input: Genre is not linked to song" } - playbackManager.play(song, genre, settings) + playImpl(song, genre) } else if (song.genres.size == 1) { - playbackManager.play(song, song.genres[0], settings) + playImpl(song, song.genres[0]) } else { _genrePlaybackPickerSong.value = song } @@ -204,49 +198,37 @@ class PlaybackViewModel(application: Application) : * Play an [Album]. * @param album The [Album] to play. */ - fun play(album: Album) { - playbackManager.play(null, album, settings, false) - } + fun play(album: Album) = playImpl(null, album, false) /** * Play an [Artist]. * @param artist The [Artist] to play. */ - fun play(artist: Artist) { - playbackManager.play(null, artist, settings, false) - } + fun play(artist: Artist) = playImpl(null, artist, false) /** * Play a [Genre]. * @param genre The [Genre] to play. */ - fun play(genre: Genre) { - playbackManager.play(null, genre, settings, false) - } + fun play(genre: Genre) = playImpl(null, genre, false) /** * Shuffle an [Album]. * @param album The [Album] to shuffle. */ - fun shuffle(album: Album) { - playbackManager.play(null, album, settings, true) - } + fun shuffle(album: Album) = playImpl(null, album, true) /** * Shuffle an [Artist]. * @param artist The [Artist] to shuffle. */ - fun shuffle(artist: Artist) { - playbackManager.play(null, artist, settings, true) - } + fun shuffle(artist: Artist) = playImpl(null, artist, true) /** * Shuffle an [Genre]. * @param genre The [Genre] to shuffle. */ - fun shuffle(genre: Genre) { - playbackManager.play(null, genre, settings, true) - } + fun shuffle(genre: Genre) = playImpl(null, genre, true) /** * Start the given [InternalPlayer.Action] to be completed eventually. This can be used to @@ -257,6 +239,24 @@ class PlaybackViewModel(application: Application) : playbackManager.startAction(action) } + private fun playImpl( + song: Song?, + parent: MusicParent?, + shuffled: Boolean = playbackManager.queue.isShuffled && settings.keepShuffle + ) { + check(song == null || parent == null || parent.songs.contains(song)) { + "Song to play not in parent" + } + val sort = + when (parent) { + is Genre -> settings.detailGenreSort + is Artist -> settings.detailArtistSort + is Album -> settings.detailAlbumSort + null -> settings.libSongSort + } + playbackManager.play(song, parent, sort, shuffled) + } + // --- PLAYER FUNCTIONS --- /** @@ -370,7 +370,7 @@ class PlaybackViewModel(application: Application) : /** Toggle [isShuffled] (ex. from on to off) */ fun invertShuffled() { - playbackManager.reshuffle(!playbackManager.isShuffled, settings) + playbackManager.reorder(!playbackManager.queue.isShuffled) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt index fb30d6c5c..44c227759 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.StateFlow import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.playback.state.Queue /** * A [ViewModel] that manages the current queue state and allows navigation through the queue. @@ -36,7 +37,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener { /** The current queue. */ val queue: StateFlow> = _queue - private val _index = MutableStateFlow(playbackManager.index) + private val _index = MutableStateFlow(playbackManager.queue.index) /** The index of the currently playing song in the queue. */ val index: StateFlow get() = _index @@ -56,10 +57,6 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener { * range. */ fun goto(adapterIndex: Int) { - if (adapterIndex !in playbackManager.queue.indices) { - // Invalid input. Nothing to do. - return - } playbackManager.goto(adapterIndex) } @@ -69,10 +66,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener { * range. */ fun removeQueueDataItem(adapterIndex: Int) { - if (adapterIndex <= playbackManager.index || - adapterIndex !in playbackManager.queue.indices) { - // Invalid input. Nothing to do. - // TODO: Allow editing played queue items. + if (adapterIndex !in queue.value.indices) { return } playbackManager.removeQueueItem(adapterIndex) @@ -85,7 +79,8 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener { * @return true if the items were moved, false otherwise. */ fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean { - if (adapterFrom <= playbackManager.index || adapterTo <= playbackManager.index) { + if (adapterFrom <= playbackManager.queue.index || + adapterTo <= playbackManager.queue.index) { // Invalid input. Nothing to do. return false } @@ -103,34 +98,34 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener { scrollTo = null } - override fun onIndexMoved(index: Int) { + override fun onIndexMoved(queue: Queue) { // Index moved -> Scroll to new index replaceQueue = null - scrollTo = index - _index.value = index + scrollTo = queue.index + _index.value = queue.index } - override fun onQueueChanged(queue: List) { + override fun onQueueChanged(queue: Queue) { // Queue changed trivially due to item move -> Diff queue, stay at current index. replaceQueue = false scrollTo = null - _queue.value = playbackManager.queue.toMutableList() + _queue.value = queue.resolve() } - override fun onQueueReworked(index: Int, queue: List) { + override fun onQueueReworked(queue: Queue) { // Queue changed completely -> Replace queue, update index replaceQueue = true - scrollTo = index - _queue.value = playbackManager.queue.toMutableList() - _index.value = index + scrollTo = queue.index + _queue.value = queue.resolve() + _index.value = queue.index } - override fun onNewPlayback(index: Int, queue: List, parent: MusicParent?) { + override fun onNewPlayback(queue: Queue, parent: MusicParent?) { // Entirely new queue -> Replace queue, update index replaceQueue = true - scrollTo = index - _queue.value = playbackManager.queue.toMutableList() - _index.value = index + scrollTo = queue.index + _queue.value = queue.resolve() + _index.value = queue.index } override fun onCleared() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt index 79d403233..77a633140 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/ReplayGainAudioProcessor.kt @@ -133,7 +133,7 @@ class ReplayGainAudioProcessor(private val context: Context) : // User wants album gain to be used when in an album, track gain otherwise. ReplayGainMode.DYNAMIC -> playbackManager.parent is Album && - playbackManager.song?.album == playbackManager.parent + playbackManager.queue.currentSong?.album == playbackManager.parent } val resolvedGain = diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index fb2995839..11f710456 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -17,18 +17,11 @@ package org.oxycblt.auxio.playback.state -import kotlin.math.max import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.oxycblt.auxio.BuildConfig -import org.oxycblt.auxio.music.Album -import org.oxycblt.auxio.music.Artist -import org.oxycblt.auxio.music.Genre -import org.oxycblt.auxio.music.MusicParent -import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.* import org.oxycblt.auxio.playback.state.PlaybackStateManager.Listener -import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logW @@ -59,22 +52,13 @@ class PlaybackStateManager private constructor() { @Volatile private var pendingAction: InternalPlayer.Action? = null @Volatile private var isInitialized = false - /** The currently playing [Song]. Null if nothing is playing. */ - val song - get() = queue.getOrNull(index) + /** The current [Queue]. */ + val queue = Queue() /** The [MusicParent] currently being played. Null if playback is occurring from all songs. */ @Volatile var parent: MusicParent? = null private set - @Volatile private var _queue = mutableListOf() - /** The current queue. */ - val queue - get() = _queue - /** The position of the currently playing item in the queue. */ - @Volatile - var index = -1 - private set /** The current [InternalPlayer] state. */ @Volatile var playerState = InternalPlayer.State.from(isPlaying = false, isAdvancing = false, 0) @@ -86,13 +70,8 @@ class PlaybackStateManager private constructor() { field = value notifyRepeatModeChanged() } - /** Whether the queue is shuffled. */ - @Volatile - var isShuffled = false - private set /** - * The current audio session ID of the internal player. Null if no [InternalPlayer] is - * available. + * The current audio session ID of the internal player. Null if [InternalPlayer] is unavailable. */ val currentAudioSessionId: Int? get() = internalPlayer?.audioSessionId @@ -106,9 +85,8 @@ class PlaybackStateManager private constructor() { @Synchronized fun addListener(listener: Listener) { if (isInitialized) { - listener.onNewPlayback(index, queue, parent) + listener.onNewPlayback(queue, parent) listener.onRepeatChanged(repeatMode) - listener.onShuffledChanged(isShuffled) listener.onStateChanged(playerState) } @@ -141,7 +119,7 @@ class PlaybackStateManager private constructor() { } if (isInitialized) { - internalPlayer.loadSong(song, playerState.isPlaying) + internalPlayer.loadSong(queue.currentSong, playerState.isPlaying) internalPlayer.seekTo(playerState.calculateElapsedPositionMs()) // See if there's any action that has been queued. requestAction(internalPlayer) @@ -175,27 +153,19 @@ class PlaybackStateManager private constructor() { * @param song A particular [Song] to play, or null to play the first [Song] in the new queue. * @param parent The [MusicParent] to play from, or null if to play from the entire * [MusicStore.Library]. - * @param settings [Settings] required to configure the queue. - * @param shuffled Whether to shuffle the queue. Defaults to the "Remember shuffle" - * configuration. + * @param sort [Sort] to initially sort an ordered queue with. + * @param shuffled Whether to shuffle or not. */ @Synchronized - fun play( - song: Song?, - parent: MusicParent?, - settings: Settings, - shuffled: Boolean = settings.keepShuffle && isShuffled - ) { + fun play(song: Song?, parent: MusicParent?, sort: Sort, shuffled: Boolean) { val internalPlayer = internalPlayer ?: return val library = musicStore.library ?: return - // Setup parent and queue + // Set up parent and queue this.parent = parent - _queue = (parent?.songs ?: library.songs).toMutableList() - orderQueue(settings, shuffled, song) + queue.start(song, sort.songs(parent?.songs ?: library.songs), shuffled) // Notify components of changes notifyNewPlayback() - notifyShuffledChanged() - internalPlayer.loadSong(this.song, true) + internalPlayer.loadSong(queue.currentSong, true) // Played something, so we are initialized now isInitialized = true } @@ -209,13 +179,13 @@ class PlaybackStateManager private constructor() { @Synchronized fun next() { val internalPlayer = internalPlayer ?: return - // Increment the index, if it cannot be incremented any further, then - // repeat and pause/resume playback depending on the setting - if (index < _queue.lastIndex) { - gotoImpl(internalPlayer, index + 1, true) - } else { - gotoImpl(internalPlayer, 0, repeatMode == RepeatMode.ALL) + var play = true + if (!queue.goto(queue.index + 1)) { + queue.goto(0) + play = false } + notifyIndexMoved() + internalPlayer.loadSong(queue.currentSong, play) } /** @@ -231,7 +201,11 @@ class PlaybackStateManager private constructor() { rewind() setPlaying(true) } else { - gotoImpl(internalPlayer, max(index - 1, 0), true) + if (!queue.goto(queue.index - 1)) { + queue.goto(0) + } + notifyIndexMoved() + internalPlayer.loadSong(queue.currentSong, true) } } @@ -242,13 +216,10 @@ class PlaybackStateManager private constructor() { @Synchronized fun goto(index: Int) { val internalPlayer = internalPlayer ?: return - gotoImpl(internalPlayer, index, true) - } - - private fun gotoImpl(internalPlayer: InternalPlayer, idx: Int, play: Boolean) { - index = idx - notifyIndexMoved() - internalPlayer.loadSong(song, play) + if (queue.goto(index)) { + notifyIndexMoved() + internalPlayer.loadSong(queue.currentSong, true) + } } /** @@ -257,7 +228,7 @@ class PlaybackStateManager private constructor() { */ @Synchronized fun playNext(song: Song) { - _queue.add(index + 1, song) + queue.playNext(listOf(song)) notifyQueueChanged() } @@ -267,7 +238,7 @@ class PlaybackStateManager private constructor() { */ @Synchronized fun playNext(songs: List) { - _queue.addAll(index + 1, songs) + queue.playNext(songs) notifyQueueChanged() } @@ -277,7 +248,7 @@ class PlaybackStateManager private constructor() { */ @Synchronized fun addToQueue(song: Song) { - _queue.add(song) + queue.addToQueue(listOf(song)) notifyQueueChanged() } @@ -287,82 +258,41 @@ class PlaybackStateManager private constructor() { */ @Synchronized fun addToQueue(songs: List) { - _queue.addAll(songs) + queue.addToQueue(songs) notifyQueueChanged() } /** * Move a [Song] in the queue. - * @param from The position of the [Song] to move in the queue. - * @param to The destination position in the queue. + * @param src The position of the [Song] to move in the queue. + * @param dst The destination position in the queue. */ @Synchronized - fun moveQueueItem(from: Int, to: Int) { - logD("Moving item $from to position $to") - _queue.add(to, _queue.removeAt(from)) + fun moveQueueItem(src: Int, dst: Int) { + logD("Moving item $src to position $dst") + queue.move(src, dst) notifyQueueChanged() } /** * Remove a [Song] from the queue. - * @param index The position of the [Song] to remove in the queue. + * @param at The position of the [Song] to remove in the queue. */ @Synchronized - fun removeQueueItem(index: Int) { - logD("Removing item ${_queue[index].rawName}") - _queue.removeAt(index) + fun removeQueueItem(at: Int) { + logD("Removing item at $at") + queue.remove(at) notifyQueueChanged() } /** * (Re)shuffle or (Re)order this instance. * @param shuffled Whether to shuffle the queue or not. - * @param settings [Settings] required to configure the queue. */ @Synchronized - fun reshuffle(shuffled: Boolean, settings: Settings) { - val song = song ?: return - orderQueue(settings, shuffled, song) + fun reorder(shuffled: Boolean) { + queue.reorder(shuffled) notifyQueueReworked() - notifyShuffledChanged() - } - - /** - * Re-configure the queue. - * @param settings [Settings] required to configure the queue. - * @param shuffled Whether to shuffle the queue or not. - * @param keep the [Song] to start at in the new queue, or null if not specified. - */ - private fun orderQueue(settings: Settings, shuffled: Boolean, keep: Song?) { - val newIndex: Int - if (shuffled) { - // Shuffling queue, randomize the current song list and move the Song to play - // to the start. - _queue.shuffle() - if (keep != null) { - _queue.add(0, _queue.removeAt(_queue.indexOf(keep))) - } - newIndex = 0 - } else { - // Ordering queue, re-sort it using the analogous parent sort configuration and - // then jump to the Song to play. - // TODO: Rework queue system to avoid having to do this - val sort = - parent.let { parent -> - when (parent) { - null -> settings.libSongSort - is Album -> settings.detailAlbumSort - is Artist -> settings.detailArtistSort - is Genre -> settings.detailGenreSort - } - } - sort.songsInPlace(_queue) - newIndex = keep?.let(_queue::indexOf) ?: 0 - } - - _queue = queue - index = newIndex - isShuffled = shuffled } // --- INTERNAL PLAYER FUNCTIONS --- @@ -379,7 +309,7 @@ class PlaybackStateManager private constructor() { return } - val newState = internalPlayer.getState(song?.durationMs ?: 0) + val newState = internalPlayer.getState(queue.currentSong?.durationMs ?: 0) if (newState != playerState) { playerState = newState notifyStateChanged() @@ -452,44 +382,49 @@ class PlaybackStateManager private constructor() { return false } - val library = musicStore.library ?: return false - val internalPlayer = internalPlayer ?: return false - val state = - try { - withContext(Dispatchers.IO) { database.read(library) } - } catch (e: Exception) { - logE("Unable to restore playback state.") - logE(e.stackTraceToString()) - return false - } + // TODO: Re-implement with new queue + return false - // Translate the state we have just read into a usable playback state for this - // instance. - return synchronized(this) { - // State could have changed while we were loading, so check if we were initialized - // now before applying the state. - if (state != null && (!isInitialized || force)) { - index = state.index - parent = state.parent - _queue = state.queue.toMutableList() - repeatMode = state.repeatMode - isShuffled = state.isShuffled - - notifyNewPlayback() - notifyRepeatModeChanged() - notifyShuffledChanged() - - // Continuing playback after drastic state updates is a bad idea, so pause. - internalPlayer.loadSong(song, false) - internalPlayer.seekTo(state.positionMs) - - isInitialized = true - - true - } else { - false - } - } + // val library = musicStore.library ?: return false + // val internalPlayer = internalPlayer ?: return false + // val state = + // try { + // withContext(Dispatchers.IO) { database.read(library) } + // } catch (e: Exception) { + // logE("Unable to restore playback state.") + // logE(e.stackTraceToString()) + // return false + // } + // + // // Translate the state we have just read into a usable playback state for this + // // instance. + // return synchronized(this) { + // // State could have changed while we were loading, so check if we were + // initialized + // // now before applying the state. + // if (state != null && (!isInitialized || force)) { + // index = state.index + // parent = state.parent + // _queue = state.queue.toMutableList() + // repeatMode = state.repeatMode + // isShuffled = state.isShuffled + // + // notifyNewPlayback() + // notifyRepeatModeChanged() + // notifyShuffledChanged() + // + // // Continuing playback after drastic state updates is a bad idea, so + // pause. + // internalPlayer.loadSong(song, false) + // internalPlayer.seekTo(state.positionMs) + // + // isInitialized = true + // + // true + // } else { + // false + // } + // } } /** @@ -499,26 +434,26 @@ class PlaybackStateManager private constructor() { */ suspend fun saveState(database: PlaybackStateDatabase): Boolean { logD("Saving state to DB") - - // Create the saved state from the current playback state. - val state = - synchronized(this) { - PlaybackStateDatabase.SavedState( - index = index, - parent = parent, - queue = _queue, - positionMs = playerState.calculateElapsedPositionMs(), - isShuffled = isShuffled, - repeatMode = repeatMode) - } - return try { - withContext(Dispatchers.IO) { database.write(state) } - true - } catch (e: Exception) { - logE("Unable to save playback state.") - logE(e.stackTraceToString()) - false - } + return false + // // Create the saved state from the current playback state. + // val state = + // synchronized(this) { + // PlaybackStateDatabase.SavedState( + // index = index, + // parent = parent, + // queue = _queue, + // positionMs = playerState.calculateElapsedPositionMs(), + // isShuffled = isShuffled, + // repeatMode = repeatMode) + // } + // return try { + // withContext(Dispatchers.IO) { database.write(state) } + // true + // } catch (e: Exception) { + // logE("Unable to save playback state.") + // logE(e.stackTraceToString()) + // false + // } } /** @@ -543,54 +478,57 @@ class PlaybackStateManager private constructor() { */ @Synchronized fun sanitize(newLibrary: MusicStore.Library) { - if (!isInitialized) { - // Nothing playing, nothing to do. - logD("Not initialized, no need to sanitize") - return - } - - val internalPlayer = internalPlayer ?: return - - logD("Sanitizing state") - - // While we could just save and reload the state, we instead sanitize the state - // at runtime for better performance (and to sidestep a co-routine on behalf of the caller). - - // Sanitize parent - parent = - parent?.let { - when (it) { - is Album -> newLibrary.sanitize(it) - is Artist -> newLibrary.sanitize(it) - is Genre -> newLibrary.sanitize(it) - } - } - - // Sanitize queue. Make sure we re-align the index to point to the previously playing - // Song in the queue queue. - val oldSongUid = song?.uid - _queue = _queue.mapNotNullTo(mutableListOf()) { newLibrary.sanitize(it) } - while (song?.uid != oldSongUid && index > -1) { - index-- - } - - notifyNewPlayback() - - val oldPosition = playerState.calculateElapsedPositionMs() - // Continuing playback while also possibly doing drastic state updates is - // a bad idea, so pause. - internalPlayer.loadSong(song, false) - if (index > -1) { - // Internal player may have reloaded the media item, re-seek to the previous position - seekTo(oldPosition) - } + // if (!isInitialized) { + // // Nothing playing, nothing to do. + // logD("Not initialized, no need to sanitize") + // return + // } + // + // val internalPlayer = internalPlayer ?: return + // + // logD("Sanitizing state") + // + // // While we could just save and reload the state, we instead sanitize the state + // // at runtime for better performance (and to sidestep a co-routine on behalf of + // the caller). + // + // // Sanitize parent + // parent = + // parent?.let { + // when (it) { + // is Album -> newLibrary.sanitize(it) + // is Artist -> newLibrary.sanitize(it) + // is Genre -> newLibrary.sanitize(it) + // } + // } + // + // // Sanitize queue. Make sure we re-align the index to point to the previously + // playing + // // Song in the queue queue. + // val oldSongUid = song?.uid + // _queue = _queue.mapNotNullTo(mutableListOf()) { newLibrary.sanitize(it) } + // while (song?.uid != oldSongUid && index > -1) { + // index-- + // } + // + // notifyNewPlayback() + // + // val oldPosition = playerState.calculateElapsedPositionMs() + // // Continuing playback while also possibly doing drastic state updates is + // // a bad idea, so pause. + // internalPlayer.loadSong(song, false) + // if (index > -1) { + // // Internal player may have reloaded the media item, re-seek to the previous + // position + // seekTo(oldPosition) + // } } // --- CALLBACKS --- private fun notifyIndexMoved() { for (callback in listeners) { - callback.onIndexMoved(index) + callback.onIndexMoved(queue) } } @@ -602,13 +540,13 @@ class PlaybackStateManager private constructor() { private fun notifyQueueReworked() { for (callback in listeners) { - callback.onQueueReworked(index, queue) + callback.onQueueReworked(queue) } } private fun notifyNewPlayback() { for (callback in listeners) { - callback.onNewPlayback(index, queue, parent) + callback.onNewPlayback(queue, parent) } } @@ -624,12 +562,6 @@ class PlaybackStateManager private constructor() { } } - private fun notifyShuffledChanged() { - for (callback in listeners) { - callback.onShuffledChanged(isShuffled) - } - } - /** * The interface for receiving updates from [PlaybackStateManager]. Add the listener to * [PlaybackStateManager] using [addListener], remove them on destruction with [removeListener]. @@ -638,30 +570,29 @@ class PlaybackStateManager private constructor() { /** * Called when the position of the currently playing item has changed, changing the current * [Song], but no other queue attribute has changed. - * @param index The new position in the queue. + * @param queue The new [Queue]. */ - fun onIndexMoved(index: Int) {} + fun onIndexMoved(queue: Queue) {} /** - * Called when the queue changed in a trivial manner, such as a move. - * @param queue The new queue. + * Called when the [Queue] changed in a trivial manner, such as a move. + * @param queue The new [Queue]. */ - fun onQueueChanged(queue: List) {} + fun onQueueChanged(queue: Queue) {} /** - * Called when the queue has changed in a non-trivial manner (such as re-shuffling), but the - * currently playing [Song] has not. - * @param index The new position in the queue. + * Called when the [Queue] has changed in a non-trivial manner (such as re-shuffling), but + * the currently playing [Song] has not. + * @param queue The new [Queue]. */ - fun onQueueReworked(index: Int, queue: List) {} + fun onQueueReworked(queue: Queue) {} /** * Called when a new playback configuration was created. - * @param index The new position in the queue. - * @param queue The new queue. + * @param queue The new [Queue]. * @param parent The new [MusicParent] being played from, or null if playing from all songs. */ - fun onNewPlayback(index: Int, queue: List, parent: MusicParent?) {} + fun onNewPlayback(queue: Queue, parent: MusicParent?) {} /** * Called when the state of the [InternalPlayer] changes. @@ -674,13 +605,6 @@ class PlaybackStateManager private constructor() { * @param repeatMode The new [RepeatMode]. */ fun onRepeatChanged(repeatMode: RepeatMode) {} - - /** - * Called when the queue's shuffle state changes. Handling the queue change itself should - * occur in [onQueueReworked], - * @param isShuffled Whether the queue is shuffled. - */ - fun onShuffledChanged(isShuffled: Boolean) {} } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt new file mode 100644 index 000000000..8cb285d69 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2023 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.playback.state + +import kotlin.random.Random +import kotlin.random.nextInt +import org.oxycblt.auxio.music.Song + +/** + * A heap-backed play queue. + * + * Whereas other queue implementations use a plain list, Auxio requires a more complicated data + * structure in order to implement features such as gapless playback in ExoPlayer. This queue + * implementation is instead based around an unorganized "heap" of [Song] instances, that are then + * interpreted into different queues depending on the current playback configuration. + * + * In general, the implementation details don't need ot be known for this data structure to be used. + * The functions exposed should be familiar for any typical play queue. + * + * @author OxygenCobalt + */ +class Queue { + @Volatile private var heap = mutableListOf() + @Volatile private var orderedMapping = mutableListOf() + @Volatile private var shuffledMapping = mutableListOf() + /** The index of the currently playing [Song] in the current mapping. */ + @Volatile + var index = 0 + private set + /** The currently playing [Song]. */ + val currentSong: Song? + get() = shuffledMapping.ifEmpty { orderedMapping.ifEmpty { null } }?.let { heap[it[index]] } + /** Whether this queue is shuffled. */ + val isShuffled: Boolean + get() = shuffledMapping.isNotEmpty() + + /** + * Resolve this queue into a more conventional list of [Song]s. + * @return A list of [Song] corresponding to the current queue mapping. + */ + fun resolve() = shuffledMapping.map { heap[it] }.ifEmpty { orderedMapping.map { heap[it] } } + + /** + * Go to a particular index in the queue. + * @param to The index of the [Song] to start playing, in the current queue mapping. + * @return true if the queue jumped to that position, false otherwise. + */ + fun goto(to: Int): Boolean { + if (to !in orderedMapping.indices) { + return false + } + index = to + return true + } + + /** + * Start a new queue configuration. + * @param song The [Song] to play, or null to start from a random position. + * @param queue The queue of [Song]s to play. Must contain [song]. This list will become the + * heap internally. + * @param shuffled Whether to shuffle the queue or not. This changes the interpretation of + * [queue]. + */ + fun start(song: Song?, queue: List, shuffled: Boolean) { + heap = queue.toMutableList() + orderedMapping = MutableList(queue.size) { it } + shuffledMapping = mutableListOf() + index = + song?.let(queue::indexOf) ?: if (shuffled) Random.Default.nextInt(queue.indices) else 0 + reorder(shuffled) + } + + /** + * Re-order the queue. + * @param shuffled Whether the queue should be shuffled or not. + */ + fun reorder(shuffled: Boolean) { + if (shuffled) { + val trueIndex = + if (shuffledMapping.isNotEmpty()) { + // Re-shuffling, song to preserve is in the shuffled mapping + shuffledMapping[index] + } else { + // First shuffle, song to preserve is in the ordered mapping + orderedMapping[index] + } + + // Since we are re-shuffling existing songs, we use the previous mapping size + // instead of the total queue size. + shuffledMapping = MutableList(orderedMapping.size) { it }.apply { shuffle() } + shuffledMapping.add(0, shuffledMapping.removeAt(shuffledMapping.indexOf(trueIndex))) + index = 0 + } else if (shuffledMapping.isNotEmpty()) { + // Un-shuffling, song to preserve is in the shuffled mapping. + index = orderedMapping.indexOf(shuffledMapping[index]) + shuffledMapping = mutableListOf() + } + } + + /** + * Add [Song]s to the top of the queue. Will start playback if nothing is playing. + * @param songs The [Song]s to add. + */ + fun playNext(songs: List) { + if (orderedMapping.isEmpty()) { + // No playback, start playing these songs. + start(songs[0], songs, false) + return + } + + val heapIndices = songs.map(::addSongToHeap) + if (shuffledMapping.isNotEmpty()) { + // Add the new songs in front of the current index in the shuffled mapping and in front + // of the analogous list song in the ordered mapping. + val orderedIndex = orderedMapping.indexOf(shuffledMapping[index]) + orderedMapping.addAll(orderedIndex, heapIndices) + shuffledMapping.addAll(index, heapIndices) + } else { + // Add the new song in front of the current index in the ordered mapping. + orderedMapping.addAll(index, heapIndices) + } + } + + /** + * Add [Song]s to the end of the queue. Will start playback if nothing is playing. + * @param songs The [Song]s to add. + */ + fun addToQueue(songs: List) { + if (orderedMapping.isEmpty()) { + // No playback, start playing these songs. + start(songs[0], songs, false) + return + } + + val heapIndices = songs.map(::addSongToHeap) + // Can simple append the new songs to the end of both mappings. + orderedMapping.addAll(heapIndices) + if (shuffledMapping.isNotEmpty()) { + shuffledMapping.addAll(heapIndices) + } + } + + /** + * Move a [Song] at the given position to a new position. + * @param src The position of the [Song] to move. + * @param dst The destination position of the [Song]. + */ + fun move(src: Int, dst: Int) { + if (shuffledMapping.isNotEmpty()) { + // Move songs only in the shuffled mapping. There is no sane analogous form of + // this for the ordered mapping. + shuffledMapping.add(dst, shuffledMapping.removeAt(src)) + } else { + // Move songs in the ordered mapping. + orderedMapping.add(dst, orderedMapping.removeAt(src)) + } + + if (index in (src + 1) until dst) { + // Index was ahead of moved song but not ahead of it's destination position. + // This makes it functionally a removal, so update the index to preserve consistency. + index -= 1 + } else if (index == src) { + // Moving the currently playing song. + index = dst + } + } + + /** + * Remove a [Song] at the given position. + * @param at The position of the [Song] to remove. + */ + fun remove(at: Int) { + if (shuffledMapping.isNotEmpty()) { + // Remove the specified index in the shuffled mapping and the analogous song in the + // ordered mapping. + orderedMapping.removeAt(orderedMapping.indexOf(shuffledMapping[at])) + shuffledMapping.removeAt(at) + } else { + // Remove the spe + orderedMapping.removeAt(at) + } + + // Note: We do not clear songs out from the heap, as that would require the backing data + // of the player to be completely invalidated. It's generally easier to not remove the + // song and retain player state consistency. + + if (index > at) { + // Index was ahead of removed song, shift back to preserve consistency. + index -= 1 + } + } + + private fun addSongToHeap(song: Song): Int { + // We want to first try to see if there are any "orphaned" songs in the queue + // that we can re-use. This way, we can reduce the memory used up by songs that + // were previously removed from the queue. + val currentMapping = orderedMapping + if (orderedMapping.isNotEmpty()) { + // While we could iterate through the queue and then check the mapping, it's + // faster if we first check the queue for all instances of this song, and then + // do a exclusion of this set of indices with the current mapping in order to + // obtain the orphaned songs. + val orphanCandidates = mutableSetOf() + for (entry in heap.withIndex()) { + if (entry.value == song) { + orphanCandidates.add(entry.index) + } + } + orphanCandidates.removeAll(currentMapping.toSet()) + if (orphanCandidates.isNotEmpty()) { + // There are orphaned songs, return the first one we find. + return orphanCandidates.first() + } + } + + // Nothing to re-use, add this song to the queue + heap.add(song) + return heap.lastIndex + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt index d1d28c3c3..090c81162 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -31,7 +31,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager class MediaButtonReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val playbackManager = PlaybackStateManager.getInstance() - if (playbackManager.song != null) { + if (playbackManager.queue.currentSong != null) { // We have a song, so we can assume that the service will start a foreground state. // At least, I hope. Again, *this is why we don't do this*. I cannot describe how // stupid this is with the state of foreground services on modern android. One diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index 863d71b6b..3833bc188 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -36,6 +36,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.ActionMode import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.playback.state.Queue import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD @@ -92,22 +93,29 @@ class MediaSessionComponent(private val context: Context, private val listener: // --- PLAYBACKSTATEMANAGER OVERRIDES --- - override fun onIndexMoved(index: Int) { - updateMediaMetadata(playbackManager.song, playbackManager.parent) + override fun onIndexMoved(queue: Queue) { + updateMediaMetadata(playbackManager.queue.currentSong, playbackManager.parent) invalidateSessionState() } - override fun onQueueChanged(queue: List) { + override fun onQueueChanged(queue: Queue) { updateQueue(queue) } - override fun onQueueReworked(index: Int, queue: List) { + override fun onQueueReworked(queue: Queue) { updateQueue(queue) invalidateSessionState() + mediaSession.setShuffleMode( + if (queue.isShuffled) { + PlaybackStateCompat.SHUFFLE_MODE_ALL + } else { + PlaybackStateCompat.SHUFFLE_MODE_NONE + }) + invalidateSecondaryAction() } - override fun onNewPlayback(index: Int, queue: List, parent: MusicParent?) { - updateMediaMetadata(playbackManager.song, parent) + override fun onNewPlayback(queue: Queue, parent: MusicParent?) { + updateMediaMetadata(playbackManager.queue.currentSong, parent) updateQueue(queue) invalidateSessionState() } @@ -131,23 +139,12 @@ class MediaSessionComponent(private val context: Context, private val listener: invalidateSecondaryAction() } - override fun onShuffledChanged(isShuffled: Boolean) { - mediaSession.setShuffleMode( - if (isShuffled) { - PlaybackStateCompat.SHUFFLE_MODE_ALL - } else { - PlaybackStateCompat.SHUFFLE_MODE_NONE - }) - - invalidateSecondaryAction() - } - // --- SETTINGS OVERRIDES --- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { when (key) { context.getString(R.string.set_key_cover_mode) -> - updateMediaMetadata(playbackManager.song, playbackManager.parent) + updateMediaMetadata(playbackManager.queue.currentSong, playbackManager.parent) context.getString(R.string.set_key_notif_action) -> invalidateSecondaryAction() } } @@ -219,16 +216,13 @@ class MediaSessionComponent(private val context: Context, private val listener: } override fun onSetShuffleMode(shuffleMode: Int) { - playbackManager.reshuffle( + playbackManager.reorder( shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL || - shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP, - settings) + shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP) } override fun onSkipToQueueItem(id: Long) { - if (id in playbackManager.queue.indices) { - playbackManager.goto(id.toInt()) - } + playbackManager.goto(id.toInt()) } override fun onCustomAction(action: String?, extras: Bundle?) { @@ -318,9 +312,9 @@ class MediaSessionComponent(private val context: Context, private val listener: * Upload a new queue to the [MediaSessionCompat]. * @param queue The current queue to upload. */ - private fun updateQueue(queue: List) { + private fun updateQueue(queue: Queue) { val queueItems = - queue.mapIndexed { i, song -> + queue.resolve().mapIndexed { i, song -> val description = MediaDescriptionCompat.Builder() // Media ID should not be the item index but rather the UID, @@ -350,7 +344,7 @@ class MediaSessionComponent(private val context: Context, private val listener: .intoPlaybackState(PlaybackStateCompat.Builder()) .setActions(ACTIONS) // Active queue ID corresponds to the indices we populated prior, use them here. - .setActiveQueueItemId(playbackManager.index.toLong()) + .setActiveQueueItemId(playbackManager.queue.index.toLong()) // Android 13+ relies on custom actions in the notification. @@ -361,7 +355,7 @@ class MediaSessionComponent(private val context: Context, private val listener: PlaybackStateCompat.CustomAction.Builder( PlaybackService.ACTION_INVERT_SHUFFLE, context.getString(R.string.desc_shuffle), - if (playbackManager.isShuffled) { + if (playbackManager.queue.isShuffled) { R.drawable.ic_shuffle_on_24 } else { R.drawable.ic_shuffle_off_24 @@ -391,7 +385,7 @@ class MediaSessionComponent(private val context: Context, private val listener: invalidateSessionState() when (settings.playbackNotificationAction) { - ActionMode.SHUFFLE -> notification.updateShuffled(playbackManager.isShuffled) + ActionMode.SHUFFLE -> notification.updateShuffled(playbackManager.queue.isShuffled) else -> notification.updateRepeatMode(playbackManager.repeatMode) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index c615a27ab..521bb5949 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -351,12 +351,16 @@ class PlaybackService : } // Shuffle all -> Start new playback from all songs is InternalPlayer.Action.ShuffleAll -> { - playbackManager.play(null, null, settings, true) + playbackManager.play(null, null, settings.libSongSort, true) } // Open -> Try to find the Song for the given file and then play it from all songs is InternalPlayer.Action.Open -> { library.findSongForUri(application, action.uri)?.let { song -> - playbackManager.play(song, null, settings) + playbackManager.play( + song, + null, + settings.libSongSort, + playbackManager.queue.isShuffled && settings.keepShuffle) } } } @@ -411,8 +415,7 @@ class PlaybackService : playbackManager.setPlaying(!playbackManager.playerState.isPlaying) ACTION_INC_REPEAT_MODE -> playbackManager.repeatMode = playbackManager.repeatMode.increment() - ACTION_INVERT_SHUFFLE -> - playbackManager.reshuffle(!playbackManager.isShuffled, settings) + ACTION_INVERT_SHUFFLE -> playbackManager.reorder(!playbackManager.queue.isShuffled) ACTION_SKIP_PREV -> playbackManager.prev() ACTION_SKIP_NEXT -> playbackManager.next() ACTION_EXIT -> { @@ -428,7 +431,7 @@ class PlaybackService : // which would result in unexpected playback. Work around it by dropping the first // call to this function, which should come from that Intent. if (settings.headsetAutoplay && - playbackManager.song != null && + playbackManager.queue.currentSong != null && initialHeadsetPlugEventHandled) { logD("Device connected, resuming") playbackManager.setPlaying(true) @@ -436,7 +439,7 @@ class PlaybackService : } private fun pauseFromHeadsetPlug() { - if (playbackManager.song != null) { + if (playbackManager.queue.currentSong != null) { logD("Device disconnected, pausing") playbackManager.setPlaying(false) } diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 638299bb9..42b99d989 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -30,6 +30,7 @@ import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.playback.state.Queue import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.getDimenPixels @@ -55,7 +56,7 @@ class WidgetComponent(private val context: Context) : /** Update [WidgetProvider] with the current playback state. */ fun update() { - val song = playbackManager.song + val song = playbackManager.queue.currentSong if (song == null) { logD("No song, resetting widget") widgetProvider.update(context, null) @@ -65,7 +66,7 @@ class WidgetComponent(private val context: Context) : // Note: Store these values here so they remain consistent once the bitmap is loaded. val isPlaying = playbackManager.playerState.isPlaying val repeatMode = playbackManager.repeatMode - val isShuffled = playbackManager.isShuffled + val isShuffled = playbackManager.queue.isShuffled provider.load( song, @@ -115,10 +116,10 @@ class WidgetComponent(private val context: Context) : // Hook all the major song-changing updates + the major player state updates // to updating the "Now Playing" widget. - override fun onIndexMoved(index: Int) = update() - override fun onNewPlayback(index: Int, queue: List, parent: MusicParent?) = update() + override fun onIndexMoved(queue: Queue) = update() + override fun onQueueReworked(queue: Queue) = update() + override fun onNewPlayback(queue: Queue, parent: MusicParent?) = update() override fun onStateChanged(state: InternalPlayer.State) = update() - override fun onShuffledChanged(isShuffled: Boolean) = update() override fun onRepeatChanged(repeatMode: RepeatMode) = update() override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {