Update shuffling system

Finally collapse all usage of shuffle into setShuffling, instead of the mix of checks that used to be throughout PlaybackStateManager.
This commit is contained in:
OxygenCobalt 2021-01-18 16:53:10 -07:00
parent 44fa178c34
commit 627553344c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 88 additions and 66 deletions

View file

@ -101,7 +101,7 @@ class MosaicFetcher(private val context: Context) : Fetcher<List<Uri>> {
* Iterate through a list of [Closeable]s, running [use] on each. * Iterate through a list of [Closeable]s, running [use] on each.
* @param action What to do for each [Closeable] * @param action What to do for each [Closeable]
*/ */
private fun <T : Closeable, R> List<T>.useForEach(action: (T) -> R) { private fun <T : Closeable> List<T>.useForEach(action: (T) -> Unit) {
forEach { forEach {
it.use(action) it.use(action)
} }

View file

@ -38,9 +38,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.coil.getBitmap import org.oxycblt.auxio.coil.getBitmap
@ -484,7 +484,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
// Play/Pause if any of the keys are play/pause // Play/Pause if any of the keys are play/pause
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> { KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
true true
} }
@ -557,7 +557,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
player.volume = VOLUME_DUCK player.volume = VOLUME_DUCK
animateVolume(VOLUME_DUCK, VOLUME_FULL) animateVolume(VOLUME_DUCK, VOLUME_FULL)
} else if (pauseWasFromAudioFocus) { } else if (pauseWasFromAudioFocus) {
playbackManager.setPlayingStatus(true) playbackManager.setPlaying(true)
} }
pauseWasFromAudioFocus = false pauseWasFromAudioFocus = false
@ -567,7 +567,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
private fun onLoss() { private fun onLoss() {
if (settingsManager.doAudioFocus && playbackManager.isPlaying) { if (settingsManager.doAudioFocus && playbackManager.isPlaying) {
pauseWasFromAudioFocus = true pauseWasFromAudioFocus = true
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
} }
} }
@ -606,10 +606,10 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
NotificationUtils.ACTION_LOOP -> NotificationUtils.ACTION_LOOP ->
playbackManager.setLoopMode(playbackManager.loopMode.increment()) playbackManager.setLoopMode(playbackManager.loopMode.increment())
NotificationUtils.ACTION_SHUFFLE -> NotificationUtils.ACTION_SHUFFLE ->
playbackManager.setShuffleStatus(!playbackManager.isShuffling) playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev() NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev()
NotificationUtils.ACTION_PLAY_PAUSE -> { NotificationUtils.ACTION_PLAY_PAUSE -> {
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
} }
NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next() NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next()
NotificationUtils.ACTION_EXIT -> stop() NotificationUtils.ACTION_EXIT -> stop()
@ -643,7 +643,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
if (playbackManager.song != null && settingsManager.doPlugMgt) { if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device connected, resuming...") logD("Device connected, resuming...")
playbackManager.setPlayingStatus(true) playbackManager.setPlaying(true)
} }
} }
@ -654,7 +654,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
if (playbackManager.song != null && settingsManager.doPlugMgt) { if (playbackManager.song != null && settingsManager.doPlugMgt) {
logD("Device disconnected, pausing...") logD("Device disconnected, pausing...")
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
} }
} }
@ -662,7 +662,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
* Stop if the X button was clicked from the notification * Stop if the X button was clicked from the notification
*/ */
private fun stop() { private fun stop() {
playbackManager.setPlayingStatus(false) playbackManager.setPlaying(false)
stopForegroundAndNotification() stopForegroundAndNotification()
} }
} }

View file

@ -279,12 +279,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
fun invertPlayingStatus() { fun invertPlayingStatus() {
enableAnimation() enableAnimation()
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlaying(!playbackManager.isPlaying)
} }
/** Flip the shuffle status, e.g from on to off */ /** Flip the shuffle status, e.g from on to off. Will keep song by default. */
fun invertShuffleStatus() { fun invertShuffleStatus() {
playbackManager.setShuffleStatus(!playbackManager.isShuffling) playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
} }
/** Increment the loop status, e.g from off to loop once */ /** Increment the loop status, e.g from off to loop once */

View file

@ -153,8 +153,6 @@ class PlaybackStateManager private constructor() {
fun playSong(song: Song, mode: PlaybackMode) { fun playSong(song: Song, mode: PlaybackMode) {
logD("Updating song to ${song.name} and mode to $mode") logD("Updating song to ${song.name} and mode to $mode")
val shouldShuffle = settingsManager.keepShuffle && mIsShuffling
when (mode) { when (mode) {
PlaybackMode.ALL_SONGS -> { PlaybackMode.ALL_SONGS -> {
mParent = null mParent = null
@ -190,7 +188,9 @@ class PlaybackStateManager private constructor() {
resetLoopMode() resetLoopMode()
updatePlayback(song) updatePlayback(song)
setShuffleStatus(shouldShuffle)
// Depending on the configuration, keep the shuffle mode on.
setShuffling(settingsManager.keepShuffle && mIsShuffling, keepSong = true)
mIndex = mQueue.indexOf(song) mIndex = mQueue.indexOf(song)
} }
@ -212,19 +212,18 @@ class PlaybackStateManager private constructor() {
mParent = baseModel mParent = baseModel
mIndex = 0 mIndex = 0
mIsShuffling = shuffled
when (baseModel) { when (baseModel) {
is Album -> { is Album -> {
mQueue = orderSongsInAlbum(baseModel) mQueue = baseModel.songs.toMutableList()
mMode = PlaybackMode.IN_ALBUM mMode = PlaybackMode.IN_ALBUM
} }
is Artist -> { is Artist -> {
mQueue = orderSongsInArtist(baseModel) mQueue = baseModel.songs.toMutableList()
mMode = PlaybackMode.IN_ARTIST mMode = PlaybackMode.IN_ARTIST
} }
is Genre -> { is Genre -> {
mQueue = orderSongsInGenre(baseModel) mQueue = baseModel.songs.toMutableList()
mMode = PlaybackMode.IN_GENRE mMode = PlaybackMode.IN_GENRE
} }
@ -233,14 +232,8 @@ class PlaybackStateManager private constructor() {
} }
resetLoopMode() resetLoopMode()
setShuffling(shuffled, keepSong = false)
updatePlayback(mQueue[0]) updatePlayback(mQueue[0])
if (mIsShuffling) {
genShuffle(false)
} else {
resetShuffle()
}
} }
/** /**
@ -254,7 +247,7 @@ class PlaybackStateManager private constructor() {
mPosition = 0 mPosition = 0
if (!mIsPlaying) { if (!mIsPlaying) {
setPlayingStatus(true) setPlaying(true)
} }
} }
@ -352,7 +345,7 @@ class PlaybackStateManager private constructor() {
mSong = mQueue[0] mSong = mQueue[0]
mPosition = 0 mPosition = 0
setPlayingStatus(false) setPlaying(false)
mIsInUserQueue = false mIsInUserQueue = false
} }
@ -503,25 +496,37 @@ class PlaybackStateManager private constructor() {
* Shuffle all songs. * Shuffle all songs.
*/ */
fun shuffleAll() { fun shuffleAll() {
mIsShuffling = true
mMode = PlaybackMode.ALL_SONGS mMode = PlaybackMode.ALL_SONGS
mIndex = 0
mQueue = musicStore.songs.toMutableList() mQueue = musicStore.songs.toMutableList()
genShuffle(false) setShuffling(true, keepSong = false)
updatePlayback(mQueue[0]) updatePlayback(mQueue[0])
} }
/**
* Set the shuffle status. Updates the queue accordingly
* @param value Whether the queue should be shuffled or not.
* @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled
*/
fun setShuffling(value: Boolean, keepSong: Boolean) {
mIsShuffling = value
if (mIsShuffling) {
genShuffle(keepSong, mIsInUserQueue)
} else {
resetShuffle(keepSong, mIsInUserQueue)
}
}
/** /**
* Generate a new shuffled queue. * Generate a new shuffled queue.
* @param keepSong Whether to keep the currently playing song or to dispose of it * @param keepSong Whether the current song should be kept as the queue is shuffled
* @param useLastSong (Optional, defaults to false) Whether to use the last song in the queue instead of the current one * @param useLastSong Whether to use the last song in the queue instead of the current one
* @return A new shuffled queue * @return A new shuffled queue
*/ */
private fun genShuffle( private fun genShuffle(
keepSong: Boolean, keepSong: Boolean,
useLastSong: Boolean = false useLastSong: Boolean
) { ) {
val lastSong = if (useLastSong) mQueue[0] else mSong val lastSong = if (useLastSong) mQueue[0] else mSong
@ -543,9 +548,13 @@ class PlaybackStateManager private constructor() {
/** /**
* Reset the queue to its normal, ordered state. * Reset the queue to its normal, ordered state.
* @param useLastSong (Optional, defaults to false) Whether to use the previous song for the index calculations. * @param keepSong Whether the current song should be kept as the queue is unshuffled
* @param useLastSong Whether to use the previous song for the index calculations.
*/ */
private fun resetShuffle(useLastSong: Boolean = false) { private fun resetShuffle(
keepSong: Boolean,
useLastSong: Boolean
) {
val lastSong = if (useLastSong) mQueue[mIndex] else mSong val lastSong = if (useLastSong) mQueue[mIndex] else mSong
mQueue = when (mMode) { mQueue = when (mMode) {
@ -555,7 +564,9 @@ class PlaybackStateManager private constructor() {
PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList()
} }
if (keepSong) {
mIndex = mQueue.indexOf(lastSong) mIndex = mQueue.indexOf(lastSong)
}
forceQueueUpdate() forceQueueUpdate()
} }
@ -566,7 +577,7 @@ class PlaybackStateManager private constructor() {
* Set the current playing status * Set the current playing status
* @param value Whether the playback should be playing or paused. * @param value Whether the playback should be playing or paused.
*/ */
fun setPlayingStatus(value: Boolean) { fun setPlaying(value: Boolean) {
if (mIsPlaying != value) { if (mIsPlaying != value) {
if (value) { if (value) {
mHasPlayed = true mHasPlayed = true
@ -577,25 +588,11 @@ class PlaybackStateManager private constructor() {
} }
/** /**
* Set the shuffle status. Updates the queue accordingly * Rewind to the beginning of a song.
* @param value Whether the queue should be shuffled or not.
*/ */
fun setShuffleStatus(value: Boolean) {
mIsShuffling = value
if (mIsShuffling) {
genShuffle(
keepSong = true,
useLastSong = mIsInUserQueue
)
} else {
resetShuffle(mIsInUserQueue)
}
}
fun rewind() { fun rewind() {
seekTo(0) seekTo(0)
setPlayingStatus(true) setPlaying(true)
} }
/** /**
@ -606,13 +603,6 @@ class PlaybackStateManager private constructor() {
mLoopMode = mode mLoopMode = mode
} }
/**
* Reset the has played status as if this instance is fresh.
*/
fun resetHasPlayedStatus() {
mHasPlayed = false
}
/** /**
* Reset the current [LoopMode], if needed. * Reset the current [LoopMode], if needed.
* Use this instead of duplicating the code manually. * Use this instead of duplicating the code manually.
@ -624,6 +614,13 @@ class PlaybackStateManager private constructor() {
} }
} }
/**
* Reset the has played status as if this instance is fresh.
*/
fun resetHasPlayedStatus() {
mHasPlayed = false
}
// --- PERSISTENCE FUNCTIONS --- // --- PERSISTENCE FUNCTIONS ---
/** /**

View file

@ -101,8 +101,24 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
compareBy(String.CASE_INSENSITIVE_ORDER) { it.name } compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }
) )
NUMERIC_UP -> songs.sortedWith(compareBy { it.album.year }) NUMERIC_UP -> {
NUMERIC_DOWN -> songs.sortedWith(compareByDescending { it.album.year }) val list = mutableListOf<Song>()
songs.groupBy { it.album }.toSortedMap(compareBy { it.year }).values.forEach {
list.addAll(it.sortedWith(compareBy { it.track }))
}
list
}
NUMERIC_DOWN -> {
val list = mutableListOf<Song>()
songs.groupBy { it.album }.toSortedMap(compareByDescending { it.year }).values.forEach {
list.addAll(it.sortedWith(compareBy { it.track }))
}
list
}
else -> songs else -> songs
} }

View file

@ -40,7 +40,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/background" android:background="@color/background"
android:elevation="@dimen/elevation_normal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -236,5 +236,15 @@
<item name="android:layout_marginEnd">@dimen/margin_large</item> <item name="android:layout_marginEnd">@dimen/margin_large</item>
<item name="android:scaleType">fitCenter</item> <item name="android:scaleType">fitCenter</item>
<item name="android:padding">@dimen/padding_play_pause</item> <item name="android:padding">@dimen/padding_play_pause</item>
</style>
<style name="Widget.Design.BottomNavigationView" parent="">
<item name="elevation">@dimen/design_bottom_navigation_elevation</item>
<item name="enforceTextAppearance">false</item>
<item name="enforceMaterialTheme">false</item>
<item name="itemBackground">?attr/selectableItemBackgroundBorderless</item>
<item name="itemHorizontalTranslationEnabled">true</item>
<item name="itemIconSize">@dimen/design_bottom_navigation_icon_size</item>
<item name="labelVisibilityMode">auto</item>
</style> </style>
</resources> </resources>