From d09ce20e02b55659de1eedd9c9b7fca08215f543 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 20 Nov 2020 20:56:43 -0700 Subject: [PATCH] Persist Queue Implement some very unoptimized queue persistence, Ill have to make it better in the future but for now it works. --- app/src/main/AndroidManifest.xml | 1 + ...ybackStateDatabase.kt => AuxioDatabase.kt} | 13 +- .../oxycblt/auxio/database/PlaybackState.kt | 9 - .../oxycblt/auxio/database/QueueConverter.kt | 30 -- .../org/oxycblt/auxio/database/QueueDAO.kt | 26 ++ .../org/oxycblt/auxio/database/QueueItem.kt | 17 + .../auxio/library/adapters/SearchAdapter.kt | 16 +- .../auxio/playback/CompactPlaybackFragment.kt | 5 + .../oxycblt/auxio/playback/PlaybackService.kt | 34 +- .../auxio/playback/PlaybackViewModel.kt | 6 + .../auxio/playback/queue/QueueAdapter.kt | 5 +- .../playback/state/PlaybackStateManager.kt | 339 +++++++++--------- 12 files changed, 270 insertions(+), 231 deletions(-) rename app/src/main/java/org/oxycblt/auxio/database/{PlaybackStateDatabase.kt => AuxioDatabase.kt} (65%) delete mode 100644 app/src/main/java/org/oxycblt/auxio/database/QueueConverter.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt create mode 100644 app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 67ef8206c..464f5dfbc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:theme="@style/Theme.Base"> diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt similarity index 65% rename from app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt rename to app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt index 219a253f1..bcdd4957f 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/AuxioDatabase.kt @@ -5,18 +5,19 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase -@Database(entities = [PlaybackState::class], version = 1, exportSchema = false) -abstract class PlaybackStateDatabase : RoomDatabase() { +@Database(entities = [PlaybackState::class, QueueItem::class], version = 1, exportSchema = false) +abstract class AuxioDatabase : RoomDatabase() { abstract val playbackStateDAO: PlaybackStateDAO + abstract val queueDAO: QueueDAO companion object { @Volatile - private var INSTANCE: PlaybackStateDatabase? = null + private var INSTANCE: AuxioDatabase? = null /** - * Get/Instantiate the single instance of [PlaybackStateDatabase]. + * Get/Instantiate the single instance of [AuxioDatabase]. */ - fun getInstance(context: Context): PlaybackStateDatabase { + fun getInstance(context: Context): AuxioDatabase { val currentInstance = INSTANCE if (currentInstance != null) { @@ -26,7 +27,7 @@ abstract class PlaybackStateDatabase : RoomDatabase() { synchronized(this) { val newInstance = Room.databaseBuilder( context.applicationContext, - PlaybackStateDatabase::class.java, + AuxioDatabase::class.java, "playback_state_database" ).fallbackToDestructiveMigration().build() INSTANCE = newInstance diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt index dbeea08f3..64f909284 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt @@ -18,21 +18,12 @@ data class PlaybackState( @ColumnInfo(name = "parent_id") val parentId: Long = -1L, - @ColumnInfo(name = "user_queue") - val userQueueIds: String, - - @ColumnInfo(name = "index") - val index: Int, - @ColumnInfo(name = "mode") val mode: Int, @ColumnInfo(name = "is_shuffling") val isShuffling: Boolean, - @ColumnInfo(name = "shuffle_seed") - val shuffleSeed: Long, - @ColumnInfo(name = "loop_mode") val loopMode: Int, diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueConverter.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueConverter.kt deleted file mode 100644 index c390be0d0..000000000 --- a/app/src/main/java/org/oxycblt/auxio/database/QueueConverter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.oxycblt.auxio.database - -import org.json.JSONArray -import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.music.Song - -object QueueConverter { - fun fromString(arrayString: String): MutableList { - val jsonArray = JSONArray(arrayString) - val queue = mutableListOf() - val musicStore = MusicStore.getInstance() - - for (i in 0 until jsonArray.length()) { - val id = jsonArray.getLong(i) - musicStore.songs.find { it.id == id }?.let { - queue.add(it) - } - } - - return queue - } - - fun fromQueue(queueIds: List): String { - val jsonArray = JSONArray() - queueIds.forEach { - jsonArray.put(it) - } - return jsonArray.toString(0) - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt new file mode 100644 index 000000000..f46c64caa --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/database/QueueDAO.kt @@ -0,0 +1,26 @@ +package org.oxycblt.auxio.database + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction + +@Dao +interface QueueDAO { + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insert(item: QueueItem) + + @Transaction + suspend fun insertAll(items: List) { + items.forEach { + insert(it) + } + } + + @Query("SELECT * FROM queue_table") + fun getAll(): List + + @Query("DELETE FROM queue_table") + fun clear() +} diff --git a/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt b/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt new file mode 100644 index 000000000..5833db57b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/database/QueueItem.kt @@ -0,0 +1,17 @@ +package org.oxycblt.auxio.database + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "queue_table") +data class QueueItem( + @PrimaryKey(autoGenerate = true) + var id: Long = 0L, + + @ColumnInfo(name = "song_id") + val songId: Long = Long.MIN_VALUE, + + @ColumnInfo(name = "is_user_queue") + val isUserQueue: Boolean = false +) diff --git a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt index 266aec854..7975cca74 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/adapters/SearchAdapter.kt @@ -44,19 +44,17 @@ class SearchAdapter( ) HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) - else -> HeaderViewHolder.from(parent.context) + else -> error("Someone messed with the ViewHolder item types.") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is GenreViewHolder -> holder.bind(getItem(position) as Genre) - is ArtistViewHolder -> holder.bind(getItem(position) as Artist) - is AlbumViewHolder -> holder.bind(getItem(position) as Album) - is SongViewHolder -> holder.bind(getItem(position) as Song) - is HeaderViewHolder -> holder.bind(getItem(position) as Header) - - else -> return + when (val item = getItem(position)) { + is Genre -> (holder as GenreViewHolder).bind(item) + is Artist -> (holder as ArtistViewHolder).bind(item) + is Album -> (holder as AlbumViewHolder).bind(item) + is Song -> (holder as SongViewHolder).bind(item) + is Header -> (holder as HeaderViewHolder).bind(item) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index 1ce42197a..63a067e70 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -55,6 +55,11 @@ class CompactPlaybackFragment : Fragment() { ) } + binding.root.setOnLongClickListener { + playbackModel.save(requireContext()) + true + } + // --- VIEWMODEL SETUP --- playbackModel.song.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 78884e995..a6d451f3b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -145,19 +145,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca if (playbackManager.song != null) { restorePlayer() - notification.updateLoop(this) - notification.updateMode(this) - notification.updatePlaying(this) - - playbackManager.song?.let { - notification.setMetadata(it, this) { - if (playbackManager.isPlaying) { - startForegroundOrNotify("Restore") - } else { - stopForegroundAndNotification() - } - } - } + restoreNotification() } } @@ -275,6 +263,8 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } } + // TODO: Do testing where service is destroyed after restore [Possible edge case] + override fun onLoopUpdate(mode: LoopMode) { changeIsFromAudioFocus = false @@ -308,6 +298,22 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } } + private fun restoreNotification() { + notification.updateLoop(this) + notification.updateMode(this) + notification.updatePlaying(this) + + playbackManager.song?.let { + notification.setMetadata(it, this) { + if (playbackManager.isPlaying) { + startForegroundOrNotify("Restore") + } else { + stopForegroundAndNotification() + } + } + } + } + override fun onRestoreFinish() { Log.d(this::class.simpleName, "Restore done") @@ -349,7 +355,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca private fun startForegroundOrNotify(reason: String) { // Start the service in the foreground if haven't already. if (playbackManager.isRestored) { - Log.d(this::class.simpleName, "Starting foreground because of $reason") + Log.d(this::class.simpleName, "Starting foreground/notifying because of $reason") if (!isForeground) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 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 b92afc9c5..1c6ec7c99 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -256,6 +256,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { } } + fun save(context: Context) { + viewModelScope.launch { + playbackManager.saveStateToDatabase(context) + } + } + // --- OVERRIDES --- override fun onCleared() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 8ce149eb0..b2f921636 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -47,7 +47,7 @@ class QueueAdapter( QUEUE_ITEM_TYPE -> ViewHolder( ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context)) ) - else -> error("Someone messed with the ViewHolder item types. Tell OxygenCobalt.") + else -> error("Someone messed with the ViewHolder item types.") } } @@ -57,7 +57,7 @@ class QueueAdapter( is Header -> (holder as HeaderViewHolder).bind(item) else -> { - Log.d(this::class.simpleName, "Bad data fed to QueueAdapter.") + Log.e(this::class.simpleName, "Bad data fed to QueueAdapter.") } } } @@ -88,6 +88,7 @@ class QueueAdapter( if (data[data.lastIndex] is Header) { val lastIndex = data.lastIndex + // TODO: Do notifyItemRangeRemoved instead of notifyItemRemoved data.removeAt(lastIndex) notifyItemRemoved(lastIndex) } else if (data.lastIndex >= 1 && data[0] is Header && data[1] is Header) { 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 7fb84ee36..dc0142c78 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 @@ -2,11 +2,12 @@ package org.oxycblt.auxio.playback.state import android.content.Context import android.util.Log +import kotlin.random.Random import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.oxycblt.auxio.database.AuxioDatabase import org.oxycblt.auxio.database.PlaybackState -import org.oxycblt.auxio.database.PlaybackStateDatabase -import org.oxycblt.auxio.database.QueueConverter +import org.oxycblt.auxio.database.QueueItem import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel @@ -14,7 +15,6 @@ import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song -import kotlin.random.Random /** * Master class for the playback state. This should ***not*** be used outside of the playback module. @@ -77,7 +77,6 @@ class PlaybackStateManager private constructor() { field = value callbacks.forEach { it.onShuffleUpdate(value) } } - private var mShuffleSeed = -1L private var mLoopMode = LoopMode.NONE set(value) { field = value @@ -85,6 +84,7 @@ class PlaybackStateManager private constructor() { } private var mIsInUserQueue = false private var mIsRestored = false + private var mShuffleSeed = -1L val song: Song? get() = mSong val parent: BaseModel? get() = mParent @@ -125,22 +125,27 @@ class PlaybackStateManager private constructor() { val musicStore = MusicStore.getInstance() - mParent = when (mode) { - PlaybackMode.ALL_SONGS -> null - PlaybackMode.IN_ARTIST -> song.album.artist - PlaybackMode.IN_ALBUM -> song.album - PlaybackMode.IN_GENRE -> song.album.artist.genres[0] + when (mode) { + PlaybackMode.ALL_SONGS -> { + mParent = null + mQueue = musicStore.songs.toMutableList() + } + + PlaybackMode.IN_ARTIST -> { + mParent = song.album.artist + mQueue = song.album.artist.songs + } + + PlaybackMode.IN_ALBUM -> { + mParent = song.album + mQueue = song.album.songs + } + + else -> {} } mMode = mode - mQueue = when (mode) { - PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() - PlaybackMode.IN_ARTIST -> song.album.artist.songs - PlaybackMode.IN_ALBUM -> song.album.songs - PlaybackMode.IN_GENRE -> song.album.artist.genres[0].songs - } - resetLoopMode() updatePlayback(song) @@ -351,15 +356,13 @@ class PlaybackStateManager private constructor() { // Generate a new shuffled queue. private fun genShuffle(keepSong: Boolean) { - // Take a random seed and then shuffle the current queue based off of that. - // This seed will be saved in a database, so that the shuffle mode - // can be restored when its started again. val newSeed = Random.Default.nextLong() + Log.d(this::class.simpleName, "Shuffling queue with seed $newSeed") + mShuffleSeed = newSeed - Log.d(this::class.simpleName, "Shuffling queue with a seed of $mShuffleSeed.") - mQueue.shuffle(Random(mShuffleSeed)) + mQueue.shuffle(Random(newSeed)) mIndex = 0 // If specified, make the current song the first member of the queue. @@ -377,14 +380,9 @@ class PlaybackStateManager private constructor() { // Stop the queue and attempt to restore to the previous state private fun resetShuffle() { - mShuffleSeed = -1 + mShuffleSeed = -1L - mQueue = when (mMode) { - PlaybackMode.IN_ARTIST -> orderSongsInArtist(mParent as Artist) - PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mParent as Album) - PlaybackMode.IN_GENRE -> orderSongsInGenre(mParent as Genre) - PlaybackMode.ALL_SONGS -> MusicStore.getInstance().songs.toMutableList() - } + setupOrderedQueue() mIndex = mQueue.indexOf(mSong) @@ -414,14 +412,165 @@ class PlaybackStateManager private constructor() { } private fun resetLoopMode() { - // Reset the loop mode froM ONCE if needed. + // Reset the loop mode from ONCE if needed. if (mLoopMode == LoopMode.ONCE) { mLoopMode = LoopMode.NONE } } + // --- PERSISTENCE FUNCTIONS --- + // TODO: Optimize queue persistence [Storing seed + edits instead of entire queue. + + suspend fun saveStateToDatabase(context: Context) { + Log.d(this::class.simpleName, "Saving state to DB.") + + val start = System.currentTimeMillis() + + Log.d(this::class.simpleName, packQueue().size.toString()) + + withContext(Dispatchers.IO) { + val playbackState = packToPlaybackState() + val queueItems = packQueue() + + val database = AuxioDatabase.getInstance(context) + database.playbackStateDAO.clear() + database.queueDAO.clear() + + database.playbackStateDAO.insert(playbackState) + database.queueDAO.insertAll(queueItems) + } + + val time = System.currentTimeMillis() - start + + Log.d(this::class.simpleName, "Save finished in ${time}ms") + } + + suspend fun getStateFromDatabase(context: Context) { + Log.d(this::class.simpleName, "Getting state from DB.") + + val start = System.currentTimeMillis() + + val states: List + val queueItems: List + + withContext(Dispatchers.IO) { + val database = AuxioDatabase.getInstance(context) + states = database.playbackStateDAO.getAll() + queueItems = database.queueDAO.getAll() + + database.playbackStateDAO.clear() + database.queueDAO.clear() + } + + if (states.isEmpty()) { + Log.d(this::class.simpleName, "Nothing here. Not restoring.") + + mIsRestored = true + + return + } + + Log.d(this::class.simpleName, "Old state found, ${states[0]}") + + unpackFromPlaybackState(states[0]) + + Log.d(this::class.simpleName, "Found queue of size ${queueItems.size}") + + unpackQueue(queueItems) + + mSong?.let { + mIndex = mQueue.indexOf(mSong) + } + + val time = System.currentTimeMillis() - start + + Log.d(this::class.simpleName, "Restore finished in ${time}ms") + + mIsRestored = true + } + + private fun packToPlaybackState(): PlaybackState { + val songId = mSong?.id ?: -1L + val parentId = mParent?.id ?: -1L + val intMode = mMode.toConstant() + val intLoopMode = mLoopMode.toConstant() + + return PlaybackState( + songId = songId, + position = mPosition, + parentId = parentId, + mode = intMode, + isShuffling = mIsShuffling, + loopMode = intLoopMode, + inUserQueue = mIsInUserQueue + ) + } + + private fun packQueue(): List { + val unified = mutableListOf() + + mUserQueue.forEach { + unified.add(QueueItem(songId = it.id, isUserQueue = true)) + } + + mQueue.forEach { + unified.add(QueueItem(songId = it.id, isUserQueue = false)) + } + + return unified + } + + private fun unpackFromPlaybackState(playbackState: PlaybackState) { + val musicStore = MusicStore.getInstance() + + // Turn the simplified information from PlaybackState into values that can be used + mSong = musicStore.songs.find { it.id == playbackState.songId } + mPosition = playbackState.position + mParent = musicStore.parents.find { it.id == playbackState.parentId } + mMode = PlaybackMode.fromConstant(playbackState.mode) ?: PlaybackMode.ALL_SONGS + mLoopMode = LoopMode.fromConstant(playbackState.loopMode) ?: LoopMode.NONE + mIsShuffling = playbackState.isShuffling + mIsInUserQueue = playbackState.inUserQueue + + callbacks.forEach { + it.onSeekConfirm(mPosition) + it.onModeUpdate(mMode) + it.onRestoreFinish() + } + } + + private fun unpackQueue(queueItems: List) { + val musicStore = MusicStore.getInstance() + + Log.d(this::class.simpleName, queueItems.size.toString()) + + for (item in queueItems) { + musicStore.songs.find { it.id == item.songId }?.let { + Log.d(this::class.simpleName, it.id.toString()) + + if (item.isUserQueue) { + mUserQueue.add(it) + } else { + mQueue.add(it) + } + } + } + + forceQueueUpdate() + forceUserQueueUpdate() + } + // --- ORDERING FUNCTIONS --- + private fun setupOrderedQueue() { + mQueue = when (mMode) { + PlaybackMode.IN_ARTIST -> orderSongsInArtist(mParent as Artist) + PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mParent as Album) + PlaybackMode.IN_GENRE -> orderSongsInGenre(mParent as Genre) + PlaybackMode.ALL_SONGS -> MusicStore.getInstance().songs.toMutableList() + } + } + private fun orderSongsInAlbum(album: Album): MutableList { return album.songs.sortedBy { it.track }.toMutableList() } @@ -450,138 +599,6 @@ class PlaybackStateManager private constructor() { return final } - // --- PERSISTENCE FUNCTIONS --- - // TODO: Persist queue edits? - // FIXME: Shuffling w/o knowing the original queue edit from keepSong will cause issues - - suspend fun saveStateToDatabase(context: Context) { - Log.d(this::class.simpleName, "Saving state to DB.") - - withContext(Dispatchers.IO) { - val playbackState = packToPlaybackState() - - val database = PlaybackStateDatabase.getInstance(context) - database.playbackStateDAO.clear() - database.playbackStateDAO.insert(playbackState) - } - } - - suspend fun getStateFromDatabase(context: Context) { - Log.d(this::class.simpleName, "Getting state from DB.") - - val states = withContext(Dispatchers.IO) { - val database = PlaybackStateDatabase.getInstance(context) - val states = database.playbackStateDAO.getAll() - - database.playbackStateDAO.clear() - - return@withContext states - } - - if (states.isEmpty()) { - Log.d(this::class.simpleName, "Nothing here. Not restoring.") - - mIsRestored = true - - return - } - - Log.d(this::class.simpleName, "Old state found, ${states[0]}") - - unpackFromPlaybackState(states[0]) - - mIsRestored = true - } - - private fun packToPlaybackState(): PlaybackState { - val songId = mSong?.id ?: -1L - val parentId = mParent?.id ?: -1L - val userQueueString = QueueConverter.fromQueue( - mutableListOf().apply { - mUserQueue.forEach { - this.add(it.id) - } - } - ) - val intMode = mMode.toConstant() - val intLoopMode = mLoopMode.toConstant() - - return PlaybackState( - songId = songId, - position = mPosition, - parentId = parentId, - userQueueIds = userQueueString, - index = mIndex, - mode = intMode, - isShuffling = mIsShuffling, - shuffleSeed = mShuffleSeed, - loopMode = intLoopMode, - inUserQueue = mIsInUserQueue - ) - } - - private fun unpackFromPlaybackState(playbackState: PlaybackState) { - val musicStore = MusicStore.getInstance() - - // Turn the simplified information from PlaybackState into values that can be used - mSong = musicStore.songs.find { it.id == playbackState.songId } - mPosition = playbackState.position - mParent = musicStore.parents.find { it.id == playbackState.parentId } - mUserQueue = QueueConverter.fromString(playbackState.userQueueIds) - mMode = PlaybackMode.fromConstant(playbackState.mode) ?: PlaybackMode.ALL_SONGS - mLoopMode = LoopMode.fromConstant(playbackState.loopMode) ?: LoopMode.NONE - mIsShuffling = playbackState.isShuffling - mShuffleSeed = playbackState.shuffleSeed - mIsInUserQueue = playbackState.inUserQueue - - // If the parent was somehow dropped during saving, attempt to restore it. - mSong?.let { - if (mParent == null && mMode != PlaybackMode.ALL_SONGS) { - Log.d( - this::class.simpleName, - "Parent was corrupted while in mode $mMode. Attempting to restore." - ) - mParent = when (mMode) { - PlaybackMode.IN_ARTIST -> it.album.artist - PlaybackMode.IN_ALBUM -> it.album - else -> { - // If that fails, then just put the mode into all songs. - mMode = PlaybackMode.ALL_SONGS - null - } - } - } - } - - if (mIsShuffling) { - mQueue = when (mMode) { - PlaybackMode.IN_ARTIST -> (mParent as Artist).songs - PlaybackMode.IN_ALBUM -> (mParent as Album).songs - PlaybackMode.IN_GENRE -> (mParent as Genre).songs - PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() - } - - mQueue.shuffle(Random(mShuffleSeed)) - - forceQueueUpdate() - } else { - mQueue = when (mMode) { - PlaybackMode.IN_ARTIST -> orderSongsInArtist(mParent as Artist) - PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mParent as Album) - PlaybackMode.IN_GENRE -> orderSongsInGenre(mParent as Genre) - PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() - } - } - - mIndex = playbackState.index - - callbacks.forEach { - it.onSeekConfirm(mPosition) - it.onModeUpdate(mMode) - it.onRestoreFinish() - } - } - /** * The interface for receiving updates from [PlaybackStateManager]. * Add the callback to [PlaybackStateManager] using [addCallback],