Implement persistence
Implement the ability for [most of] the playback state to persist even after the app's process has been killed.
This commit is contained in:
parent
ee95bc1a9e
commit
6d809f4303
14 changed files with 355 additions and 7 deletions
|
@ -72,6 +72,13 @@ dependencies {
|
||||||
// Media
|
// Media
|
||||||
implementation 'androidx.media:media:1.2.0'
|
implementation 'androidx.media:media:1.2.0'
|
||||||
|
|
||||||
|
// Database
|
||||||
|
def room_version = '2.2.5'
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
|
||||||
// --- THIRD PARTY ---
|
// --- THIRD PARTY ---
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.oxycblt.auxio.database
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "playback_state_table")
|
||||||
|
data class PlaybackState(
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Long = 0L,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "song_id")
|
||||||
|
val songId: Long = -1L,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "position")
|
||||||
|
val position: Long,
|
||||||
|
|
||||||
|
@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,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "in_user_queue")
|
||||||
|
val inUserQueue: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.oxycblt.auxio.database
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface PlaybackStateDAO {
|
||||||
|
@Insert
|
||||||
|
fun insert(playbackState: PlaybackState)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun update(playbackState: PlaybackState)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM playback_state_table")
|
||||||
|
fun getAll(): List<PlaybackState>
|
||||||
|
|
||||||
|
@Query("DELETE FROM playback_state_table")
|
||||||
|
fun clear()
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.oxycblt.auxio.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(entities = [PlaybackState::class], version = 1, exportSchema = false)
|
||||||
|
abstract class PlaybackStateDatabase : RoomDatabase() {
|
||||||
|
abstract val playbackStateDAO: PlaybackStateDAO
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: PlaybackStateDatabase? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get/Instantiate the single instance of [PlaybackStateDatabase].
|
||||||
|
*/
|
||||||
|
fun getInstance(context: Context): PlaybackStateDatabase {
|
||||||
|
val currentInstance = INSTANCE
|
||||||
|
|
||||||
|
if (currentInstance != null) {
|
||||||
|
return currentInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
val newInstance = Room.databaseBuilder(
|
||||||
|
context.applicationContext,
|
||||||
|
PlaybackStateDatabase::class.java,
|
||||||
|
"playback_state_database"
|
||||||
|
).fallbackToDestructiveMigration().build()
|
||||||
|
INSTANCE = newInstance
|
||||||
|
return newInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,14 @@ class MusicStore private constructor() {
|
||||||
private var mSongs = listOf<Song>()
|
private var mSongs = listOf<Song>()
|
||||||
val songs: List<Song> get() = mSongs
|
val songs: List<Song> get() = mSongs
|
||||||
|
|
||||||
|
val parents: MutableList<BaseModel> by lazy {
|
||||||
|
val parents = mutableListOf<BaseModel>()
|
||||||
|
parents.addAll(mGenres)
|
||||||
|
parents.addAll(mArtists)
|
||||||
|
parents.addAll(mAlbums)
|
||||||
|
parents
|
||||||
|
}
|
||||||
|
|
||||||
var loaded = false
|
var loaded = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import org.oxycblt.auxio.music.MusicStore
|
||||||
* A [Fragment] that displays the currently played song at a glance, with some basic controls.
|
* A [Fragment] that displays the currently played song at a glance, with some basic controls.
|
||||||
* Extends into [PlaybackFragment] when clicked on.
|
* Extends into [PlaybackFragment] when clicked on.
|
||||||
*
|
*
|
||||||
* Instantiation is done by the navigation component, **do not instantiate this fragment manually.**
|
* Instantiation is done by FragmentContainerView, **do not instantiate this fragment manually.**
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class CompactPlaybackFragment : Fragment() {
|
class CompactPlaybackFragment : Fragment() {
|
||||||
|
|
|
@ -32,6 +32,12 @@ object NotificationUtils {
|
||||||
const val ACTION_EXIT = "ACTION_AUXIO_EXIT"
|
const val ACTION_EXIT = "ACTION_AUXIO_EXIT"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the standard media notification used by Auxio.
|
||||||
|
* @param context [Context] required to create the notification
|
||||||
|
* @param mediaSession [MediaSessionCompat] required for the [MediaStyle] notification
|
||||||
|
* @author OxygenCobalt
|
||||||
|
*/
|
||||||
fun NotificationManager.createMediaNotification(
|
fun NotificationManager.createMediaNotification(
|
||||||
context: Context,
|
context: Context,
|
||||||
mediaSession: MediaSessionCompat
|
mediaSession: MediaSessionCompat
|
||||||
|
|
|
@ -157,9 +157,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
// Release everything that could cause a memory leak if left around
|
// Release everything that could cause a memory leak if left around
|
||||||
player.release()
|
player.release()
|
||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
serviceJob.cancel()
|
|
||||||
playbackManager.removeCallback(this)
|
playbackManager.removeCallback(this)
|
||||||
|
|
||||||
|
serviceScope.launch {
|
||||||
|
playbackManager.saveStateToDatabase(this@PlaybackService)
|
||||||
|
serviceJob.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(this::class.simpleName, "Service destroyed.")
|
Log.d(this::class.simpleName, "Service destroyed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +217,10 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
|
|
||||||
player.setMediaItem(item)
|
player.setMediaItem(item)
|
||||||
player.prepare()
|
player.prepare()
|
||||||
player.play()
|
|
||||||
|
if (playbackManager.isPlaying) {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
|
||||||
uploadMetadataToSession(it)
|
uploadMetadataToSession(it)
|
||||||
notification.setMetadata(playbackManager.song!!, this) {
|
notification.setMetadata(playbackManager.song!!, this) {
|
||||||
|
@ -272,6 +279,14 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
player.seekTo(position)
|
player.seekTo(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNeedContextToRestoreState() {
|
||||||
|
Log.d(this::class.simpleName, "Giving context to PlaybackStateManager")
|
||||||
|
|
||||||
|
serviceScope.launch {
|
||||||
|
playbackManager.getStateFromDatabase(this@PlaybackService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- OTHER FUNCTIONS ---
|
// --- OTHER FUNCTIONS ---
|
||||||
|
|
||||||
private fun restorePlayer() {
|
private fun restorePlayer() {
|
||||||
|
@ -382,7 +397,9 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// BroadcastReceiver for receiving system events [E.G Headphones connected/disconnected]
|
/**
|
||||||
|
* A [BroadcastReceiver] for receiving system events from the media notification or the headset.
|
||||||
|
*/
|
||||||
private inner class SystemEventReceiver : BroadcastReceiver() {
|
private inner class SystemEventReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
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
|
||||||
|
@ -83,6 +84,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
// state.
|
// state.
|
||||||
if (playbackManager.song != null) {
|
if (playbackManager.song != null) {
|
||||||
restorePlaybackState()
|
restorePlaybackState()
|
||||||
|
} else {
|
||||||
|
playbackManager.needContextToRestoreState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +159,19 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
||||||
playbackManager.prev()
|
playbackManager.prev()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun goto(value: Boolean) {
|
||||||
|
Log.d(
|
||||||
|
this::class.simpleName,
|
||||||
|
QueueConverter.fromQueue(
|
||||||
|
mutableListOf<Long>().apply {
|
||||||
|
forEach {
|
||||||
|
this.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove a queue OR user queue item, given a QueueAdapter index.
|
// Remove a queue OR user queue item, given a QueueAdapter index.
|
||||||
fun removeQueueItem(adapterIndex: Int, queueAdapter: QueueAdapter) {
|
fun removeQueueItem(adapterIndex: Int, queueAdapter: QueueAdapter) {
|
||||||
var index = adapterIndex.dec()
|
var index = adapterIndex.dec()
|
||||||
|
|
|
@ -16,6 +16,12 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||||
import org.oxycblt.auxio.ui.applyDivider
|
import org.oxycblt.auxio.ui.applyDivider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Fragment] that contains both the user queue and the next queue, with the ability to
|
||||||
|
* edit them as well.
|
||||||
|
*
|
||||||
|
* Instantiation is done by the navigation component, **do not instantiate this fragment manually.**
|
||||||
|
*/
|
||||||
class QueueFragment : Fragment() {
|
class QueueFragment : Fragment() {
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,14 @@ package org.oxycblt.auxio.playback.state
|
||||||
enum class LoopMode {
|
enum class LoopMode {
|
||||||
NONE, ONCE, INFINITE;
|
NONE, ONCE, INFINITE;
|
||||||
|
|
||||||
|
fun toConstant(): Int {
|
||||||
|
return when (this) {
|
||||||
|
NONE -> CONSTANT_NONE
|
||||||
|
ONCE -> CONSTANT_ONCE
|
||||||
|
INFINITE -> CONSTANT_INFINITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun increment(): LoopMode {
|
fun increment(): LoopMode {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
NONE -> ONCE
|
NONE -> ONCE
|
||||||
|
@ -10,4 +18,20 @@ enum class LoopMode {
|
||||||
INFINITE -> NONE
|
INFINITE -> NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CONSTANT_NONE = 0xA050
|
||||||
|
const val CONSTANT_ONCE = 0xA051
|
||||||
|
const val CONSTANT_INFINITE = 0xA052
|
||||||
|
|
||||||
|
fun fromConstant(constant: Int): LoopMode? {
|
||||||
|
return when (constant) {
|
||||||
|
CONSTANT_NONE -> NONE
|
||||||
|
CONSTANT_ONCE -> ONCE
|
||||||
|
CONSTANT_INFINITE -> INFINITE
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,31 @@ package org.oxycblt.auxio.playback.state
|
||||||
// IN_ALBUM -> Play from the songs of the album
|
// IN_ALBUM -> Play from the songs of the album
|
||||||
enum class PlaybackMode {
|
enum class PlaybackMode {
|
||||||
IN_ARTIST, IN_GENRE, IN_ALBUM, ALL_SONGS;
|
IN_ARTIST, IN_GENRE, IN_ALBUM, ALL_SONGS;
|
||||||
|
|
||||||
|
fun toConstant(): Int {
|
||||||
|
return when (this) {
|
||||||
|
IN_ARTIST -> CONSTANT_IN_ARTIST
|
||||||
|
IN_GENRE -> CONSTANT_IN_GENRE
|
||||||
|
IN_ALBUM -> CONSTANT_IN_ALBUM
|
||||||
|
ALL_SONGS -> CONSTANT_ALL_SONGS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CONSTANT_IN_ARTIST = 0xA040
|
||||||
|
const val CONSTANT_IN_GENRE = 0xA041
|
||||||
|
const val CONSTANT_IN_ALBUM = 0x4042
|
||||||
|
const val CONSTANT_ALL_SONGS = 0x4043
|
||||||
|
|
||||||
|
fun fromConstant(constant: Int): PlaybackMode? {
|
||||||
|
return when (constant) {
|
||||||
|
CONSTANT_IN_ARTIST -> IN_ARTIST
|
||||||
|
CONSTANT_IN_ALBUM -> IN_ALBUM
|
||||||
|
CONSTANT_IN_GENRE -> IN_GENRE
|
||||||
|
CONSTANT_ALL_SONGS -> ALL_SONGS
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package org.oxycblt.auxio.playback.state
|
package org.oxycblt.auxio.playback.state
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.oxycblt.auxio.database.PlaybackState
|
||||||
|
import org.oxycblt.auxio.database.PlaybackStateDatabase
|
||||||
|
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
|
||||||
|
@ -348,11 +354,10 @@ class PlaybackStateManager private constructor() {
|
||||||
// can be restored when its started again.
|
// 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 a seed of $newSeed.")
|
|
||||||
|
|
||||||
mShuffleSeed = newSeed
|
mShuffleSeed = newSeed
|
||||||
|
|
||||||
mQueue.shuffle(Random(newSeed))
|
Log.d(this::class.simpleName, "Shuffling queue with a seed of $mShuffleSeed.")
|
||||||
|
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.
|
||||||
|
@ -380,6 +385,8 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mIndex = mQueue.indexOf(mSong)
|
mIndex = mQueue.indexOf(mSong)
|
||||||
|
|
||||||
|
forceQueueUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- STATE FUNCTIONS ---
|
// --- STATE FUNCTIONS ---
|
||||||
|
@ -441,6 +448,106 @@ class PlaybackStateManager private constructor() {
|
||||||
return final
|
return final
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- PERSISTENCE FUNCTIONS ---
|
||||||
|
// TODO: Persist queue edits?
|
||||||
|
// FIXME: Calling genShuffle without knowing the original queue edit from keepSong will cause issues.
|
||||||
|
|
||||||
|
fun needContextToRestoreState() {
|
||||||
|
callbacks.forEach { it.onNeedContextToRestoreState() }
|
||||||
|
}
|
||||||
|
|
||||||
|
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.")
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val database = PlaybackStateDatabase.getInstance(context)
|
||||||
|
val states = database.playbackStateDAO.getAll()
|
||||||
|
|
||||||
|
if (states.isEmpty()) {
|
||||||
|
Log.d(this::class.simpleName, "Nothing here. Not restoring.")
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
|
val state = states[0]
|
||||||
|
|
||||||
|
Log.d(this::class.simpleName, "Old state found, $state")
|
||||||
|
|
||||||
|
database.playbackStateDAO.clear()
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
|
mSong = musicStore.songs.find { it.id == state.songId }
|
||||||
|
mPosition = state.position
|
||||||
|
mParent = musicStore.parents.find { it.id == state.parentId }
|
||||||
|
mUserQueue = QueueConverter.fromString(state.userQueueIds)
|
||||||
|
mMode = PlaybackMode.fromConstant(state.mode) ?: PlaybackMode.ALL_SONGS
|
||||||
|
mLoopMode = LoopMode.fromConstant(state.loopMode) ?: LoopMode.NONE
|
||||||
|
mIsShuffling = state.isShuffling
|
||||||
|
mShuffleSeed = state.shuffleSeed
|
||||||
|
mIsInUserQueue = state.inUserQueue
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsShuffling) {
|
||||||
|
Log.d(this::class.simpleName, "You stupid fucking retard. JUST FUNCTION.")
|
||||||
|
mQueue.shuffle(Random(mShuffleSeed))
|
||||||
|
}
|
||||||
|
|
||||||
|
mIndex = state.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update PlaybackService outside of the main thread since its special for some reason
|
||||||
|
callbacks.forEach {
|
||||||
|
it.onSeekConfirm(mPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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],
|
||||||
|
@ -458,6 +565,7 @@ class PlaybackStateManager private constructor() {
|
||||||
fun onShuffleUpdate(isShuffling: Boolean) {}
|
fun onShuffleUpdate(isShuffling: Boolean) {}
|
||||||
fun onLoopUpdate(mode: LoopMode) {}
|
fun onLoopUpdate(mode: LoopMode) {}
|
||||||
fun onSeekConfirm(position: Long) {}
|
fun onSeekConfirm(position: Long) {}
|
||||||
|
fun onNeedContextToRestoreState() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
Loading…
Reference in a new issue