Persist Queue

Implement some very unoptimized queue persistence, Ill have to make it better in the future but for now it works.
This commit is contained in:
OxygenCobalt 2020-11-20 20:56:43 -07:00
parent da224ffda0
commit d09ce20e02
12 changed files with 270 additions and 231 deletions

View file

@ -14,6 +14,7 @@
android:theme="@style/Theme.Base"> android:theme="@style/Theme.Base">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleInstance"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">

View file

@ -5,18 +5,19 @@ import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
@Database(entities = [PlaybackState::class], version = 1, exportSchema = false) @Database(entities = [PlaybackState::class, QueueItem::class], version = 1, exportSchema = false)
abstract class PlaybackStateDatabase : RoomDatabase() { abstract class AuxioDatabase : RoomDatabase() {
abstract val playbackStateDAO: PlaybackStateDAO abstract val playbackStateDAO: PlaybackStateDAO
abstract val queueDAO: QueueDAO
companion object { companion object {
@Volatile @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 val currentInstance = INSTANCE
if (currentInstance != null) { if (currentInstance != null) {
@ -26,7 +27,7 @@ abstract class PlaybackStateDatabase : RoomDatabase() {
synchronized(this) { synchronized(this) {
val newInstance = Room.databaseBuilder( val newInstance = Room.databaseBuilder(
context.applicationContext, context.applicationContext,
PlaybackStateDatabase::class.java, AuxioDatabase::class.java,
"playback_state_database" "playback_state_database"
).fallbackToDestructiveMigration().build() ).fallbackToDestructiveMigration().build()
INSTANCE = newInstance INSTANCE = newInstance

View file

@ -18,21 +18,12 @@ data class PlaybackState(
@ColumnInfo(name = "parent_id") @ColumnInfo(name = "parent_id")
val parentId: Long = -1L, val parentId: Long = -1L,
@ColumnInfo(name = "user_queue")
val userQueueIds: String,
@ColumnInfo(name = "index")
val index: Int,
@ColumnInfo(name = "mode") @ColumnInfo(name = "mode")
val mode: Int, val mode: Int,
@ColumnInfo(name = "is_shuffling") @ColumnInfo(name = "is_shuffling")
val isShuffling: Boolean, val isShuffling: Boolean,
@ColumnInfo(name = "shuffle_seed")
val shuffleSeed: Long,
@ColumnInfo(name = "loop_mode") @ColumnInfo(name = "loop_mode")
val loopMode: Int, val loopMode: Int,

View file

@ -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<Song> {
val jsonArray = JSONArray(arrayString)
val queue = mutableListOf<Song>()
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<Long>): String {
val jsonArray = JSONArray()
queueIds.forEach {
jsonArray.put(it)
}
return jsonArray.toString(0)
}
}

View file

@ -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<QueueItem>) {
items.forEach {
insert(it)
}
}
@Query("SELECT * FROM queue_table")
fun getAll(): List<QueueItem>
@Query("DELETE FROM queue_table")
fun clear()
}

View file

@ -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
)

View file

@ -44,19 +44,17 @@ class SearchAdapter(
) )
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) 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) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (val item = getItem(position)) {
is GenreViewHolder -> holder.bind(getItem(position) as Genre) is Genre -> (holder as GenreViewHolder).bind(item)
is ArtistViewHolder -> holder.bind(getItem(position) as Artist) is Artist -> (holder as ArtistViewHolder).bind(item)
is AlbumViewHolder -> holder.bind(getItem(position) as Album) is Album -> (holder as AlbumViewHolder).bind(item)
is SongViewHolder -> holder.bind(getItem(position) as Song) is Song -> (holder as SongViewHolder).bind(item)
is HeaderViewHolder -> holder.bind(getItem(position) as Header) is Header -> (holder as HeaderViewHolder).bind(item)
else -> return
} }
} }
} }

View file

@ -55,6 +55,11 @@ class CompactPlaybackFragment : Fragment() {
) )
} }
binding.root.setOnLongClickListener {
playbackModel.save(requireContext())
true
}
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
playbackModel.song.observe(viewLifecycleOwner) { playbackModel.song.observe(viewLifecycleOwner) {

View file

@ -145,19 +145,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
if (playbackManager.song != null) { if (playbackManager.song != null) {
restorePlayer() restorePlayer()
notification.updateLoop(this) restoreNotification()
notification.updateMode(this)
notification.updatePlaying(this)
playbackManager.song?.let {
notification.setMetadata(it, this) {
if (playbackManager.isPlaying) {
startForegroundOrNotify("Restore")
} else {
stopForegroundAndNotification()
}
}
}
} }
} }
@ -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) { override fun onLoopUpdate(mode: LoopMode) {
changeIsFromAudioFocus = false 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() { override fun onRestoreFinish() {
Log.d(this::class.simpleName, "Restore done") Log.d(this::class.simpleName, "Restore done")
@ -349,7 +355,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
private fun startForegroundOrNotify(reason: String) { private fun startForegroundOrNotify(reason: String) {
// Start the service in the foreground if haven't already. // Start the service in the foreground if haven't already.
if (playbackManager.isRestored) { 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 (!isForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

View file

@ -256,6 +256,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
} }
fun save(context: Context) {
viewModelScope.launch {
playbackManager.saveStateToDatabase(context)
}
}
// --- OVERRIDES --- // --- OVERRIDES ---
override fun onCleared() { override fun onCleared() {

View file

@ -47,7 +47,7 @@ class QueueAdapter(
QUEUE_ITEM_TYPE -> ViewHolder( QUEUE_ITEM_TYPE -> ViewHolder(
ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context)) 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) is Header -> (holder as HeaderViewHolder).bind(item)
else -> { 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) { if (data[data.lastIndex] is Header) {
val lastIndex = data.lastIndex val lastIndex = data.lastIndex
// TODO: Do notifyItemRangeRemoved instead of notifyItemRemoved
data.removeAt(lastIndex) data.removeAt(lastIndex)
notifyItemRemoved(lastIndex) notifyItemRemoved(lastIndex)
} else if (data.lastIndex >= 1 && data[0] is Header && data[1] is Header) { } else if (data.lastIndex >= 1 && data[0] is Header && data[1] is Header) {

View file

@ -2,11 +2,12 @@ package org.oxycblt.auxio.playback.state
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import kotlin.random.Random
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.auxio.database.AuxioDatabase
import org.oxycblt.auxio.database.PlaybackState import org.oxycblt.auxio.database.PlaybackState
import org.oxycblt.auxio.database.PlaybackStateDatabase import org.oxycblt.auxio.database.QueueItem
import org.oxycblt.auxio.database.QueueConverter
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel 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.Header
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song 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. * 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 field = value
callbacks.forEach { it.onShuffleUpdate(value) } callbacks.forEach { it.onShuffleUpdate(value) }
} }
private var mShuffleSeed = -1L
private var mLoopMode = LoopMode.NONE private var mLoopMode = LoopMode.NONE
set(value) { set(value) {
field = value field = value
@ -85,6 +84,7 @@ class PlaybackStateManager private constructor() {
} }
private var mIsInUserQueue = false private var mIsInUserQueue = false
private var mIsRestored = false private var mIsRestored = false
private var mShuffleSeed = -1L
val song: Song? get() = mSong val song: Song? get() = mSong
val parent: BaseModel? get() = mParent val parent: BaseModel? get() = mParent
@ -125,22 +125,27 @@ class PlaybackStateManager private constructor() {
val musicStore = MusicStore.getInstance() val musicStore = MusicStore.getInstance()
mParent = when (mode) { when (mode) {
PlaybackMode.ALL_SONGS -> null PlaybackMode.ALL_SONGS -> {
PlaybackMode.IN_ARTIST -> song.album.artist mParent = null
PlaybackMode.IN_ALBUM -> song.album mQueue = musicStore.songs.toMutableList()
PlaybackMode.IN_GENRE -> song.album.artist.genres[0] }
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 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() resetLoopMode()
updatePlayback(song) updatePlayback(song)
@ -351,15 +356,13 @@ class PlaybackStateManager private constructor() {
// Generate a new shuffled queue. // Generate a new shuffled queue.
private fun genShuffle(keepSong: Boolean) { 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() val newSeed = Random.Default.nextLong()
Log.d(this::class.simpleName, "Shuffling queue with seed $newSeed")
mShuffleSeed = newSeed mShuffleSeed = newSeed
Log.d(this::class.simpleName, "Shuffling queue with a seed of $mShuffleSeed.") mQueue.shuffle(Random(newSeed))
mQueue.shuffle(Random(mShuffleSeed))
mIndex = 0 mIndex = 0
// If specified, make the current song the first member of the queue. // 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 // Stop the queue and attempt to restore to the previous state
private fun resetShuffle() { private fun resetShuffle() {
mShuffleSeed = -1 mShuffleSeed = -1L
mQueue = when (mMode) { setupOrderedQueue()
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()
}
mIndex = mQueue.indexOf(mSong) mIndex = mQueue.indexOf(mSong)
@ -414,14 +412,165 @@ class PlaybackStateManager private constructor() {
} }
private fun resetLoopMode() { private fun resetLoopMode() {
// Reset the loop mode froM ONCE if needed. // Reset the loop mode from ONCE if needed.
if (mLoopMode == LoopMode.ONCE) { if (mLoopMode == LoopMode.ONCE) {
mLoopMode = LoopMode.NONE 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<PlaybackState>
val queueItems: List<QueueItem>
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<QueueItem> {
val unified = mutableListOf<QueueItem>()
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<QueueItem>) {
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 --- // --- 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<Song> { private fun orderSongsInAlbum(album: Album): MutableList<Song> {
return album.songs.sortedBy { it.track }.toMutableList() return album.songs.sortedBy { it.track }.toMutableList()
} }
@ -450,138 +599,6 @@ class PlaybackStateManager private constructor() {
return final 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<Long>().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]. * The interface for receiving updates from [PlaybackStateManager].
* Add the callback to [PlaybackStateManager] using [addCallback], * Add the callback to [PlaybackStateManager] using [addCallback],