playback: rework fields
Rework the playback fields. The new fields are more coherent, better-named, and less prone to state failure.
This commit is contained in:
parent
2bdbe212df
commit
c80af01d5c
10 changed files with 310 additions and 475 deletions
|
@ -80,7 +80,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
|||
|
||||
binding.playbackSkipPrev?.setOnClickListener { playbackModel.skipPrev() }
|
||||
|
||||
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlayingStatus() }
|
||||
binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlaying() }
|
||||
|
||||
binding.playbackSkipNext?.setOnClickListener { playbackModel.skipNext() }
|
||||
|
||||
|
@ -97,7 +97,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
|||
playbackModel.song.observe(viewLifecycleOwner, ::updateSong)
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner, ::updateIsPlaying)
|
||||
|
||||
playbackModel.positionSeconds.observe(viewLifecycleOwner, ::updatePosition)
|
||||
playbackModel.positionSecs.observe(viewLifecycleOwner, ::updatePosition)
|
||||
}
|
||||
|
||||
private fun updateSong(song: Song?) {
|
||||
|
|
|
@ -29,7 +29,6 @@ import kotlin.math.max
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.coil.bindAlbumCover
|
||||
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
|
||||
import org.oxycblt.auxio.detail.DetailViewModel
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.toDuration
|
||||
|
@ -56,7 +55,6 @@ class PlaybackPanelFragment :
|
|||
Slider.OnSliderTouchListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private val detailModel: DetailViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
FragmentPlaybackPanelBinding.inflate(inflater)
|
||||
|
@ -114,26 +112,26 @@ class PlaybackPanelFragment :
|
|||
.stateList
|
||||
}
|
||||
|
||||
binding.playbackLoop.setOnClickListener { playbackModel.incrementLoopStatus() }
|
||||
binding.playbackLoop.setOnClickListener { playbackModel.incrementLoop() }
|
||||
binding.playbackSkipPrev.setOnClickListener { playbackModel.skipPrev() }
|
||||
|
||||
binding.playbackPlayPause.apply {
|
||||
// Abuse the play/pause FAB (see style definition for more info)
|
||||
post { binding.playbackPlayPause.stateListAnimator = null }
|
||||
setOnClickListener { playbackModel.invertPlayingStatus() }
|
||||
setOnClickListener { playbackModel.invertPlaying() }
|
||||
}
|
||||
|
||||
binding.playbackSkipNext.setOnClickListener { playbackModel.skipNext() }
|
||||
binding.playbackShuffle.setOnClickListener { playbackModel.invertShuffleStatus() }
|
||||
binding.playbackShuffle.setOnClickListener { playbackModel.invertShuffled() }
|
||||
|
||||
// --- VIEWMODEL SETUP --
|
||||
|
||||
playbackModel.song.observe(viewLifecycleOwner, ::updateSong)
|
||||
playbackModel.parent.observe(viewLifecycleOwner, ::updateParent)
|
||||
playbackModel.positionSeconds.observe(viewLifecycleOwner, ::updatePosition)
|
||||
playbackModel.positionSecs.observe(viewLifecycleOwner, ::updatePosition)
|
||||
playbackModel.loopMode.observe(viewLifecycleOwner, ::updateLoop)
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner, ::updatePlayPause)
|
||||
playbackModel.isShuffling.observe(viewLifecycleOwner, ::updateShuffle)
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner, ::updatePlaying)
|
||||
playbackModel.isShuffled.observe(viewLifecycleOwner, ::updateShuffled)
|
||||
|
||||
playbackModel.nextUp.observe(viewLifecycleOwner) { nextUp ->
|
||||
// The queue icon uses a selector that will automatically tint the icon as active or
|
||||
|
@ -206,11 +204,11 @@ class PlaybackPanelFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun updatePlayPause(isPlaying: Boolean) {
|
||||
private fun updatePlaying(isPlaying: Boolean) {
|
||||
requireBinding().playbackPlayPause.isActivated = isPlaying
|
||||
}
|
||||
|
||||
private fun updateShuffle(isShuffling: Boolean) {
|
||||
requireBinding().playbackShuffle.isActivated = isShuffling
|
||||
private fun updateShuffled(isShuffled: Boolean) {
|
||||
requireBinding().playbackShuffle.isActivated = isShuffled
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,16 +61,14 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
// States
|
||||
private val mIsPlaying = MutableLiveData(false)
|
||||
private val mIsShuffling = MutableLiveData(false)
|
||||
private val mPositionSecs = MutableLiveData(0L)
|
||||
private val mLoopMode = MutableLiveData(LoopMode.NONE)
|
||||
private val mPositionSeconds = MutableLiveData(0L)
|
||||
private val mIsShuffled = MutableLiveData(false)
|
||||
|
||||
// Queue
|
||||
private val mNextUp = MutableLiveData(listOf<Song>())
|
||||
private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS)
|
||||
|
||||
// Other
|
||||
// TODO: Move URI management to PlaybackService (more capable of taking commands)
|
||||
private var mIntentUri: Uri? = null
|
||||
|
||||
/** The current song. */
|
||||
|
@ -82,21 +80,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
val isPlaying: LiveData<Boolean>
|
||||
get() = mIsPlaying
|
||||
val isShuffling: LiveData<Boolean>
|
||||
get() = mIsShuffling
|
||||
/** The current playback position, in seconds */
|
||||
val positionSecs: LiveData<Long>
|
||||
get() = mPositionSecs
|
||||
/** The current repeat mode, see [LoopMode] for more information */
|
||||
val loopMode: LiveData<LoopMode>
|
||||
get() = mLoopMode
|
||||
/** The current playback position, in seconds */
|
||||
val positionSeconds: LiveData<Long>
|
||||
get() = mPositionSeconds
|
||||
val isShuffled: LiveData<Boolean>
|
||||
get() = mIsShuffled
|
||||
|
||||
/** The queue, without the previous items. */
|
||||
val nextUp: LiveData<List<Song>>
|
||||
get() = mNextUp
|
||||
/** The current [PlaybackMode] that also determines the queue */
|
||||
val playbackMode: LiveData<PlaybackMode>
|
||||
get() = mMode
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
|
@ -117,7 +112,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
* mode of the user if not specified.
|
||||
*/
|
||||
fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
|
||||
playbackManager.playSong(song, mode)
|
||||
playbackManager.play(song, mode)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,7 +126,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.playParent(album, shuffled)
|
||||
playbackManager.play(album, shuffled)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +140,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.playParent(artist, shuffled)
|
||||
playbackManager.play(artist, shuffled)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,7 +154,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.playParent(genre, shuffled)
|
||||
playbackManager.play(genre, shuffled)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,7 +187,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
|
||||
/** Update the position and push it to [PlaybackStateManager] */
|
||||
fun setPosition(progress: Long) {
|
||||
playbackManager.seekTo((progress * 1000))
|
||||
playbackManager.seekTo(progress * 1000)
|
||||
}
|
||||
|
||||
// --- QUEUE FUNCTIONS ---
|
||||
|
@ -229,7 +224,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
val to = adapterTo + delta
|
||||
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
|
||||
apply()
|
||||
playbackManager.moveQueueItems(from, to)
|
||||
playbackManager.moveQueueItem(from, to)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -259,18 +254,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
// --- STATUS FUNCTIONS ---
|
||||
|
||||
/** Flip the playing status, e.g from playing to paused */
|
||||
fun invertPlayingStatus() {
|
||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
fun invertPlaying() {
|
||||
playbackManager.isPlaying = !playbackManager.isPlaying
|
||||
}
|
||||
|
||||
/** Flip the shuffle status, e.g from on to off. Will keep song by default. */
|
||||
fun invertShuffleStatus() {
|
||||
playbackManager.setShuffling(!playbackManager.isShuffling, true)
|
||||
fun invertShuffled() {
|
||||
playbackManager.reshuffle(!playbackManager.isShuffled)
|
||||
}
|
||||
|
||||
/** Increment the loop status, e.g from off to loop once */
|
||||
fun incrementLoopStatus() {
|
||||
playbackManager.setLoopMode(playbackManager.loopMode.increment())
|
||||
fun incrementLoop() {
|
||||
playbackManager.loopMode = playbackManager.loopMode.increment()
|
||||
}
|
||||
|
||||
// --- SAVE/RESTORE FUNCTIONS ---
|
||||
|
@ -314,12 +309,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
private fun restorePlaybackState() {
|
||||
logD("Attempting to restore playback state")
|
||||
|
||||
onSongChanged(playbackManager.song)
|
||||
onPositionChanged(playbackManager.position)
|
||||
onParentChanged(playbackManager.parent)
|
||||
onQueueChanged(playbackManager.queue, playbackManager.index)
|
||||
onPositionChanged(playbackManager.positionMs)
|
||||
onPlayingChanged(playbackManager.isPlaying)
|
||||
onShuffleChanged(playbackManager.isShuffling)
|
||||
onShuffledChanged(playbackManager.isShuffled)
|
||||
onLoopModeChanged(playbackManager.loopMode)
|
||||
}
|
||||
|
||||
|
@ -329,28 +321,32 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
playbackManager.removeCallback(this)
|
||||
}
|
||||
|
||||
override fun onSongChanged(song: Song?) {
|
||||
mSong.value = song
|
||||
override fun onIndexMoved(index: Int) {
|
||||
mSong.value = playbackManager.song
|
||||
mNextUp.value = playbackManager.queue.slice(index.inc() until playbackManager.queue.size)
|
||||
}
|
||||
|
||||
override fun onParentChanged(parent: MusicParent?) {
|
||||
mParent.value = parent
|
||||
}
|
||||
|
||||
override fun onPositionChanged(position: Long) {
|
||||
mPositionSeconds.value = position / 1000
|
||||
}
|
||||
|
||||
override fun onQueueChanged(queue: List<Song>, index: Int) {
|
||||
override fun onQueueChanged(index: Int, queue: List<Song>) {
|
||||
mSong.value = playbackManager.song
|
||||
mNextUp.value = queue.slice(index.inc() until queue.size)
|
||||
}
|
||||
|
||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||
mParent.value = playbackManager.parent
|
||||
mSong.value = playbackManager.song
|
||||
mNextUp.value = queue.slice(index.inc() until queue.size)
|
||||
}
|
||||
|
||||
override fun onPositionChanged(positionMs: Long) {
|
||||
mPositionSecs.value = positionMs / 1000
|
||||
}
|
||||
|
||||
override fun onPlayingChanged(isPlaying: Boolean) {
|
||||
mIsPlaying.value = isPlaying
|
||||
}
|
||||
|
||||
override fun onShuffleChanged(isShuffling: Boolean) {
|
||||
mIsShuffling.value = isShuffling
|
||||
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||
mIsShuffled.value = isShuffled
|
||||
}
|
||||
|
||||
override fun onLoopModeChanged(loopMode: LoopMode) {
|
||||
|
|
|
@ -82,7 +82,7 @@ class PlaybackStateDatabase(context: Context) :
|
|||
.append("${StateColumns.COLUMN_PARENT_HASH} LONG,")
|
||||
.append("${StateColumns.COLUMN_QUEUE_INDEX} INTEGER NOT NULL,")
|
||||
.append("${StateColumns.COLUMN_PLAYBACK_MODE} INTEGER NOT NULL,")
|
||||
.append("${StateColumns.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
|
||||
.append("${StateColumns.COLUMN_IS_SHUFFLED} BOOLEAN NOT NULL,")
|
||||
.append("${StateColumns.COLUMN_LOOP_MODE} INTEGER NOT NULL)")
|
||||
|
||||
return command
|
||||
|
@ -118,7 +118,7 @@ class PlaybackStateDatabase(context: Context) :
|
|||
val parentIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PARENT_HASH)
|
||||
val indexIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_QUEUE_INDEX)
|
||||
val modeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PLAYBACK_MODE)
|
||||
val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLING)
|
||||
val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLED)
|
||||
val loopModeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_LOOP_MODE)
|
||||
|
||||
cursor.moveToFirst()
|
||||
|
@ -141,11 +141,11 @@ class PlaybackStateDatabase(context: Context) :
|
|||
state =
|
||||
SavedState(
|
||||
song = song,
|
||||
position = cursor.getLong(posIndex),
|
||||
positionMs = cursor.getLong(posIndex),
|
||||
parent = parent,
|
||||
queueIndex = cursor.getInt(indexIndex),
|
||||
playbackMode = mode,
|
||||
isShuffling = cursor.getInt(shuffleIndex) == 1,
|
||||
isShuffled = cursor.getInt(shuffleIndex) == 1,
|
||||
loopMode = LoopMode.fromIntCode(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
|
||||
)
|
||||
|
||||
|
@ -168,11 +168,11 @@ class PlaybackStateDatabase(context: Context) :
|
|||
ContentValues(10).apply {
|
||||
put(StateColumns.COLUMN_ID, 0)
|
||||
put(StateColumns.COLUMN_SONG_HASH, state.song?.id)
|
||||
put(StateColumns.COLUMN_POSITION, state.position)
|
||||
put(StateColumns.COLUMN_POSITION, state.positionMs)
|
||||
put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
|
||||
put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
|
||||
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.intCode)
|
||||
put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
|
||||
put(StateColumns.COLUMN_IS_SHUFFLED, state.isShuffled)
|
||||
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.intCode)
|
||||
}
|
||||
|
||||
|
@ -257,11 +257,11 @@ class PlaybackStateDatabase(context: Context) :
|
|||
|
||||
data class SavedState(
|
||||
val song: Song?,
|
||||
val position: Long,
|
||||
val positionMs: Long,
|
||||
val parent: MusicParent?,
|
||||
val queueIndex: Int,
|
||||
val playbackMode: PlaybackMode,
|
||||
val isShuffling: Boolean,
|
||||
val isShuffled: Boolean,
|
||||
val loopMode: LoopMode,
|
||||
)
|
||||
|
||||
|
@ -272,7 +272,7 @@ class PlaybackStateDatabase(context: Context) :
|
|||
const val COLUMN_PARENT_HASH = "parent"
|
||||
const val COLUMN_QUEUE_INDEX = "queue_index"
|
||||
const val COLUMN_PLAYBACK_MODE = "playback_mode"
|
||||
const val COLUMN_IS_SHUFFLING = "is_shuffling"
|
||||
const val COLUMN_IS_SHUFFLED = "is_shuffling"
|
||||
const val COLUMN_LOOP_MODE = "loop_mode"
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.oxycblt.auxio.playback.state
|
||||
|
||||
import android.content.Context
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -28,7 +29,6 @@ import org.oxycblt.auxio.music.MusicStore
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logE
|
||||
|
||||
/**
|
||||
* Master class (and possible god object) for the playback state.
|
||||
|
@ -47,54 +47,43 @@ class PlaybackStateManager private constructor() {
|
|||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
// Playback
|
||||
private var mSong: Song? = null
|
||||
private var mParent: MusicParent? = null
|
||||
|
||||
// Queue
|
||||
private var mQueue = mutableListOf<Song>()
|
||||
private var mIndex = 0
|
||||
|
||||
// State
|
||||
private var mIsPlaying = false
|
||||
private var mPosition: Long = 0
|
||||
private var mIsShuffling = false
|
||||
private var mLoopMode = LoopMode.NONE
|
||||
|
||||
private var mIsRestored = false
|
||||
private var mHasPlayed = false
|
||||
private var mutableQueue = mutableListOf<Song>()
|
||||
|
||||
/** The currently playing song. Null if there isn't one */
|
||||
val song: Song?
|
||||
get() = mSong
|
||||
/** The parent the queue is based on, null if all_songs */
|
||||
val parent: MusicParent?
|
||||
get() = mParent
|
||||
val song
|
||||
get() = queue.getOrNull(index)
|
||||
/** The parent the queue is based on, null if all songs */
|
||||
var parent: MusicParent? = null
|
||||
private set
|
||||
/** The current queue determined by [parent] */
|
||||
val queue: List<Song>
|
||||
get() = mQueue
|
||||
val queue
|
||||
get() = mutableQueue
|
||||
/** The current position in the queue */
|
||||
val index: Int
|
||||
get() = mIndex
|
||||
var index = -1
|
||||
private set
|
||||
|
||||
/** Whether playback is paused or not */
|
||||
val isPlaying: Boolean
|
||||
get() = mIsPlaying
|
||||
/** Whether playback is playing or not */
|
||||
var isPlaying = false
|
||||
set(value) {
|
||||
field = value
|
||||
notifyPlayingChanged()
|
||||
}
|
||||
/** The current playback progress */
|
||||
val position: Long
|
||||
get() = mPosition
|
||||
var positionMs = 0L
|
||||
private set
|
||||
/** The current [LoopMode] */
|
||||
val loopMode: LoopMode
|
||||
get() = mLoopMode
|
||||
var loopMode = LoopMode.NONE
|
||||
set(value) {
|
||||
field = value
|
||||
notifyLoopModeChanged()
|
||||
}
|
||||
/** Whether the queue is shuffled */
|
||||
val isShuffling: Boolean
|
||||
get() = mIsShuffling
|
||||
var isShuffled = false
|
||||
private set
|
||||
|
||||
/** Whether this instance has already been restored */
|
||||
val isRestored: Boolean
|
||||
get() = mIsRestored
|
||||
/** Whether playback has begun in this instance during **PlaybackService's Lifecycle.** */
|
||||
val hasPlayed: Boolean
|
||||
get() = mHasPlayed
|
||||
var isRestored = false
|
||||
private set
|
||||
|
||||
// --- CALLBACKS ---
|
||||
|
||||
|
@ -117,84 +106,46 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
/**
|
||||
* Play a [song].
|
||||
* @param mode The [PlaybackMode] to construct the queue off of.
|
||||
* @param playbackMode The [PlaybackMode] to construct the queue off of.
|
||||
*/
|
||||
fun playSong(song: Song, mode: PlaybackMode) {
|
||||
logD("Updating song to ${song.rawName} and mode to $mode")
|
||||
fun play(song: Song, playbackMode: PlaybackMode) {
|
||||
val library = musicStore.library ?: return
|
||||
|
||||
when (mode) {
|
||||
PlaybackMode.ALL_SONGS -> {
|
||||
val musicStore = musicStore.library ?: return
|
||||
mParent = null
|
||||
mQueue = musicStore.songs.toMutableList()
|
||||
}
|
||||
PlaybackMode.IN_GENRE -> {
|
||||
mParent = song.genre
|
||||
mQueue = song.genre.songs.toMutableList()
|
||||
}
|
||||
PlaybackMode.IN_ARTIST -> {
|
||||
mParent = song.album.artist
|
||||
mQueue = song.album.artist.songs.toMutableList()
|
||||
}
|
||||
PlaybackMode.IN_ALBUM -> {
|
||||
mParent = song.album
|
||||
mQueue = song.album.songs.toMutableList()
|
||||
}
|
||||
parent =
|
||||
when (playbackMode) {
|
||||
PlaybackMode.ALL_SONGS -> null
|
||||
PlaybackMode.IN_ALBUM -> song.album
|
||||
PlaybackMode.IN_ARTIST -> song.album.artist
|
||||
PlaybackMode.IN_GENRE -> song.genre
|
||||
}
|
||||
|
||||
notifyParentChanged()
|
||||
|
||||
updatePlayback(song)
|
||||
// Keep shuffle on, if enabled
|
||||
setShuffling(settingsManager.keepShuffle && isShuffling, keepSong = true)
|
||||
applyNewQueue(library, settingsManager.keepShuffle && isShuffled, song, true)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
isPlaying = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a [parent], such as an artist or album.
|
||||
* @param shuffled Whether the queue is shuffled or not
|
||||
*/
|
||||
fun playParent(parent: MusicParent, shuffled: Boolean) {
|
||||
logD("Playing ${parent.rawName}")
|
||||
|
||||
mParent = parent
|
||||
notifyParentChanged()
|
||||
mIndex = 0
|
||||
|
||||
mQueue =
|
||||
when (parent) {
|
||||
is Album -> {
|
||||
parent.songs.toMutableList()
|
||||
}
|
||||
is Artist -> {
|
||||
parent.songs.toMutableList()
|
||||
}
|
||||
is Genre -> {
|
||||
parent.songs.toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
setShuffling(shuffled, keepSong = false)
|
||||
updatePlayback(mQueue[0])
|
||||
fun play(parent: MusicParent?, shuffled: Boolean) {
|
||||
val library = musicStore.library ?: return
|
||||
this.parent = parent
|
||||
applyNewQueue(library, shuffled, null, true)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
isPlaying = true
|
||||
}
|
||||
|
||||
/** Shuffle all songs. */
|
||||
fun shuffleAll() {
|
||||
val library = musicStore.library ?: return
|
||||
|
||||
mQueue = library.songs.toMutableList()
|
||||
mParent = null
|
||||
|
||||
setShuffling(true, keepSong = false)
|
||||
updatePlayback(mQueue[0])
|
||||
}
|
||||
|
||||
/** Update the playback to a new [song], doing all the required logic. */
|
||||
private fun updatePlayback(song: Song, shouldPlay: Boolean = true) {
|
||||
mSong = song
|
||||
mPosition = 0
|
||||
notifySongChanged()
|
||||
notifyPositionChanged()
|
||||
setPlaying(shouldPlay)
|
||||
parent = null
|
||||
applyNewQueue(library, true, null, true)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
isPlaying = true
|
||||
}
|
||||
|
||||
// --- QUEUE FUNCTIONS ---
|
||||
|
@ -203,226 +154,162 @@ class PlaybackStateManager private constructor() {
|
|||
fun next() {
|
||||
// Increment the index, if it cannot be incremented any further, then
|
||||
// loop and pause/resume playback depending on the setting
|
||||
if (mIndex < mQueue.lastIndex) {
|
||||
mIndex = mIndex.inc()
|
||||
updatePlayback(mQueue[mIndex])
|
||||
if (index < mutableQueue.lastIndex) {
|
||||
goto(++index, true)
|
||||
} else {
|
||||
mIndex = 0
|
||||
updatePlayback(mQueue[mIndex], shouldPlay = loopMode == LoopMode.ALL)
|
||||
goto(0, loopMode == LoopMode.ALL)
|
||||
}
|
||||
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/** Go to the previous song, doing any checks that are needed. */
|
||||
fun prev() {
|
||||
// If enabled, rewind before skipping back if the position is past 3 seconds [3000ms]
|
||||
if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) {
|
||||
if (settingsManager.rewindWithPrev && positionMs >= REWIND_THRESHOLD) {
|
||||
rewind()
|
||||
isPlaying = true
|
||||
} else {
|
||||
// Only decrement the index if there's a song to move back to
|
||||
if (mIndex > 0) {
|
||||
mIndex = mIndex.dec()
|
||||
}
|
||||
|
||||
updatePlayback(mQueue[mIndex])
|
||||
notifyQueueChanged()
|
||||
goto(max(--index, 0), true)
|
||||
}
|
||||
}
|
||||
|
||||
// --- QUEUE EDITING FUNCTIONS ---
|
||||
|
||||
/** Remove a queue item at [index]. Will ignore invalid indexes. */
|
||||
fun removeQueueItem(index: Int): Boolean {
|
||||
if (index > mQueue.size || index < 0) {
|
||||
logE("Index is out of bounds, did not remove queue item")
|
||||
return false
|
||||
}
|
||||
|
||||
logD("Removing item ${mQueue[index].rawName}")
|
||||
mQueue.removeAt(index)
|
||||
notifyQueueChanged()
|
||||
return true
|
||||
}
|
||||
|
||||
/** Move a queue item at [from] to a position at [to]. Will ignore invalid indexes. */
|
||||
fun moveQueueItems(from: Int, to: Int): Boolean {
|
||||
if (from > mQueue.size || from < 0 || to > mQueue.size || to < 0) {
|
||||
logE("Indices were out of bounds, did not move queue item")
|
||||
return false
|
||||
}
|
||||
|
||||
logD("Moving item $from to position $to")
|
||||
mQueue.add(to, mQueue.removeAt(from))
|
||||
notifyQueueChanged()
|
||||
return true
|
||||
private fun goto(idx: Int, play: Boolean) {
|
||||
index = idx
|
||||
notifyIndexMoved()
|
||||
isPlaying = play
|
||||
}
|
||||
|
||||
/** Add a [song] to the top of the queue. */
|
||||
fun playNext(song: Song) {
|
||||
if (mQueue.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
mQueue.add(mIndex + 1, song)
|
||||
mutableQueue.add(++index, song)
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/** Add a list of [songs] to the top of the queue. */
|
||||
fun playNext(songs: List<Song>) {
|
||||
if (mQueue.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
mQueue.addAll(mIndex + 1, songs)
|
||||
mutableQueue.addAll(++index, songs)
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/** Add a [song] to the end of the queue. */
|
||||
fun addToQueue(song: Song) {
|
||||
mQueue.add(song)
|
||||
mutableQueue.add(song)
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/** Add a list of [songs] to the end of the queue. */
|
||||
fun addToQueue(songs: List<Song>) {
|
||||
mQueue.addAll(songs)
|
||||
mutableQueue.addAll(songs)
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
// --- SHUFFLE FUNCTIONS ---
|
||||
|
||||
/**
|
||||
* Set whether this instance is [shuffled]. Updates the queue accordingly.
|
||||
* @param keepSong Whether the current song should be kept as the queue is shuffled/un-shuffled
|
||||
*/
|
||||
fun setShuffling(shuffled: Boolean, keepSong: Boolean) {
|
||||
mIsShuffling = shuffled
|
||||
notifyShufflingChanged()
|
||||
|
||||
if (mIsShuffling) {
|
||||
genShuffle(keepSong)
|
||||
} else {
|
||||
resetShuffle(keepSong)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new shuffled queue.
|
||||
* @param keepSong Whether the current song should be kept as the queue is shuffled
|
||||
*/
|
||||
private fun genShuffle(keepSong: Boolean) {
|
||||
val lastSong = mSong
|
||||
|
||||
logD("Shuffling queue")
|
||||
|
||||
mQueue.shuffle()
|
||||
mIndex = 0
|
||||
|
||||
// If specified, make the current song the first member of the queue.
|
||||
if (keepSong) {
|
||||
moveQueueItems(mQueue.indexOf(lastSong), 0)
|
||||
} else {
|
||||
// Otherwise, just start from the zeroth position in the queue.
|
||||
mSong = mQueue[0]
|
||||
}
|
||||
|
||||
/** Move a queue item at [from] to a position at [to]. Will ignore invalid indexes. */
|
||||
fun moveQueueItem(from: Int, to: Int) {
|
||||
logD("Moving item $from to position $to")
|
||||
mutableQueue.add(to, mutableQueue.removeAt(from))
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the queue to its normal, ordered state.
|
||||
* @param keepSong Whether the current song should be kept as the queue is un-shuffled
|
||||
*/
|
||||
private fun resetShuffle(keepSong: Boolean) {
|
||||
/** Remove a queue item at [index]. Will ignore invalid indexes. */
|
||||
fun removeQueueItem(index: Int) {
|
||||
logD("Removing item ${mutableQueue[index].rawName}")
|
||||
mutableQueue.removeAt(index)
|
||||
notifyQueueChanged()
|
||||
}
|
||||
|
||||
/** Set whether this instance is [shuffled]. Updates the queue accordingly. */
|
||||
fun reshuffle(shuffled: Boolean) {
|
||||
val library = musicStore.library ?: return
|
||||
val lastSong = mSong
|
||||
|
||||
val parent = parent
|
||||
mQueue =
|
||||
when (parent) {
|
||||
null -> settingsManager.libSongSort.songs(library.songs).toMutableList()
|
||||
is Album -> settingsManager.detailAlbumSort.album(parent).toMutableList()
|
||||
is Artist -> settingsManager.detailArtistSort.artist(parent).toMutableList()
|
||||
is Genre -> settingsManager.detailGenreSort.genre(parent).toMutableList()
|
||||
}
|
||||
|
||||
if (keepSong) {
|
||||
mIndex = mQueue.indexOf(lastSong)
|
||||
}
|
||||
|
||||
val song = song ?: return
|
||||
applyNewQueue(library, shuffled, song, false)
|
||||
notifyQueueChanged()
|
||||
notifyShuffledChanged()
|
||||
}
|
||||
|
||||
private fun applyNewQueue(
|
||||
library: MusicStore.Library,
|
||||
shuffled: Boolean,
|
||||
keep: Song?,
|
||||
regenShuffledQueue: Boolean
|
||||
) {
|
||||
if (shuffled) {
|
||||
if (regenShuffledQueue) {
|
||||
mutableQueue =
|
||||
parent
|
||||
.let { parent ->
|
||||
when (parent) {
|
||||
null -> library.songs
|
||||
is Album -> parent.songs
|
||||
is Artist -> parent.songs
|
||||
is Genre -> parent.songs
|
||||
}
|
||||
}
|
||||
.toMutableList()
|
||||
}
|
||||
|
||||
mutableQueue.shuffle()
|
||||
|
||||
if (keep != null) {
|
||||
mutableQueue.add(0, mutableQueue.removeAt(mutableQueue.indexOf(keep)))
|
||||
}
|
||||
|
||||
index = 0
|
||||
} else {
|
||||
mutableQueue =
|
||||
parent
|
||||
.let { parent ->
|
||||
when (parent) {
|
||||
null -> settingsManager.libSongSort.songs(library.songs)
|
||||
is Album -> settingsManager.detailAlbumSort.album(parent)
|
||||
is Artist -> settingsManager.detailArtistSort.artist(parent)
|
||||
is Genre -> settingsManager.detailGenreSort.genre(parent)
|
||||
}
|
||||
}
|
||||
.toMutableList()
|
||||
|
||||
index = keep?.let(queue::indexOf) ?: 0
|
||||
}
|
||||
|
||||
isShuffled = shuffled
|
||||
}
|
||||
|
||||
// --- STATE FUNCTIONS ---
|
||||
|
||||
/** Set whether this instance is currently [playing]. */
|
||||
fun setPlaying(playing: Boolean) {
|
||||
if (mIsPlaying != playing) {
|
||||
if (playing) {
|
||||
mHasPlayed = true
|
||||
}
|
||||
|
||||
mIsPlaying = playing
|
||||
|
||||
for (callback in callbacks) {
|
||||
callback.onPlayingChanged(playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current [position]. Will not notify listeners of a seek event.
|
||||
* @param position The new position in millis.
|
||||
* Update the current [positionMs]. Will not notify listeners of a seek event.
|
||||
* @param positionMs The new position in millis.
|
||||
* @see seekTo
|
||||
*/
|
||||
fun synchronizePosition(position: Long) {
|
||||
mSong?.let { song ->
|
||||
fun synchronizePosition(positionMs: Long) {
|
||||
// Don't accept any bugged positions that are over the duration of the song.
|
||||
if (position <= song.duration) {
|
||||
mPosition = position
|
||||
val maxDuration = song?.duration ?: -1
|
||||
if (positionMs <= maxDuration) {
|
||||
this.positionMs = positionMs
|
||||
notifyPositionChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **Seek** to a [position], this calls [PlaybackStateManager.Callback.onSeek] to notify
|
||||
* **Seek** to a [positionMs], this calls [PlaybackStateManager.Callback.onSeek] to notify
|
||||
* elements that rely on that.
|
||||
* @param position The position to seek to in millis.
|
||||
* @param positionMs The position to seek to in millis.
|
||||
*/
|
||||
fun seekTo(position: Long) {
|
||||
mPosition = position
|
||||
fun seekTo(positionMs: Long) {
|
||||
this.positionMs = positionMs
|
||||
notifySeekEvent()
|
||||
notifyPositionChanged()
|
||||
callbacks.forEach { it.onSeek(position) }
|
||||
}
|
||||
|
||||
/** Rewind to the beginning of a song. */
|
||||
fun rewind() {
|
||||
seekTo(0)
|
||||
setPlaying(true)
|
||||
}
|
||||
fun rewind() = seekTo(0)
|
||||
|
||||
/** Loop playback around to the beginning. */
|
||||
fun loop() {
|
||||
seekTo(0)
|
||||
setPlaying(!settingsManager.pauseOnLoop)
|
||||
}
|
||||
fun loop() = seekTo(0)
|
||||
|
||||
/** Set the [LoopMode] to [mode]. */
|
||||
fun setLoopMode(mode: LoopMode) {
|
||||
mLoopMode = mode
|
||||
notifyLoopModeChanged()
|
||||
}
|
||||
|
||||
/** Mark whether this instance has played or not */
|
||||
fun setHasPlayed(hasPlayed: Boolean) {
|
||||
mHasPlayed = hasPlayed
|
||||
}
|
||||
// TODO: Rework these methods eventually
|
||||
|
||||
/** Mark this instance as restored. */
|
||||
fun markRestored() {
|
||||
mIsRestored = true
|
||||
isRestored = true
|
||||
}
|
||||
|
||||
// --- PERSISTENCE FUNCTIONS ---
|
||||
|
@ -450,15 +337,15 @@ class PlaybackStateManager private constructor() {
|
|||
database.writeState(
|
||||
PlaybackStateDatabase.SavedState(
|
||||
song,
|
||||
position,
|
||||
positionMs,
|
||||
parent,
|
||||
index,
|
||||
playbackMode,
|
||||
isShuffling,
|
||||
isShuffled,
|
||||
loopMode,
|
||||
))
|
||||
|
||||
database.writeQueue(mQueue)
|
||||
database.writeQueue(mutableQueue)
|
||||
|
||||
this@PlaybackStateManager.logD(
|
||||
"State save completed successfully in ${System.currentTimeMillis() - start}ms")
|
||||
|
@ -487,11 +374,16 @@ class PlaybackStateManager private constructor() {
|
|||
// Get off the IO coroutine since it will cause LiveData updates to throw an exception
|
||||
|
||||
if (playbackState != null) {
|
||||
unpackFromPlaybackState(playbackState)
|
||||
mQueue = queue
|
||||
notifyQueueChanged()
|
||||
doParentSanityCheck(playbackState.playbackMode)
|
||||
doIndexSanityCheck()
|
||||
parent = playbackState.parent
|
||||
mutableQueue = queue
|
||||
index = playbackState.queueIndex
|
||||
loopMode = playbackState.loopMode
|
||||
isShuffled = playbackState.isShuffled
|
||||
|
||||
notifyNewPlayback()
|
||||
seekTo(playbackState.positionMs)
|
||||
notifyLoopModeChanged()
|
||||
notifyShuffledChanged()
|
||||
}
|
||||
|
||||
logD("State load completed successfully in ${System.currentTimeMillis() - start}ms")
|
||||
|
@ -499,105 +391,35 @@ class PlaybackStateManager private constructor() {
|
|||
markRestored()
|
||||
}
|
||||
|
||||
/** Unpack a [playbackState] into this instance. */
|
||||
private fun unpackFromPlaybackState(playbackState: PlaybackStateDatabase.SavedState) {
|
||||
// Do queue setup first
|
||||
mParent = playbackState.parent
|
||||
mIndex = playbackState.queueIndex
|
||||
|
||||
// Then set up the current state
|
||||
mSong = playbackState.song
|
||||
mLoopMode = playbackState.loopMode
|
||||
mIsShuffling = playbackState.isShuffling
|
||||
|
||||
notifySongChanged()
|
||||
notifyParentChanged()
|
||||
seekTo(playbackState.position)
|
||||
notifyShufflingChanged()
|
||||
notifyLoopModeChanged()
|
||||
}
|
||||
|
||||
/** Do a sanity check to make sure the parent was not lost in the restore process. */
|
||||
private fun doParentSanityCheck(playbackMode: PlaybackMode) {
|
||||
// Check if the parent was lost while in the DB.
|
||||
if (mSong != null && mParent == null && playbackMode != PlaybackMode.ALL_SONGS) {
|
||||
logD("Parent lost, attempting restore")
|
||||
|
||||
mParent =
|
||||
when (playbackMode) {
|
||||
PlaybackMode.ALL_SONGS -> null
|
||||
PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album
|
||||
PlaybackMode.IN_ARTIST -> mQueue.firstOrNull()?.album?.artist
|
||||
PlaybackMode.IN_GENRE -> mQueue.firstOrNull()?.genre
|
||||
}
|
||||
|
||||
notifyParentChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/** Do a sanity check to make sure that the index lines up with the current song. */
|
||||
private fun doIndexSanityCheck() {
|
||||
// Be careful with how we handle the queue since a possible index de-sync
|
||||
// could easily result in an OOB crash.
|
||||
if (mSong != null && mSong != mQueue.getOrNull(mIndex)) {
|
||||
val correctedIndex = mQueue.wobblyIndexOfFirst(mIndex, mSong)
|
||||
if (correctedIndex > -1) {
|
||||
logD("Correcting malformed index to $correctedIndex")
|
||||
mIndex = correctedIndex
|
||||
notifyQueueChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of an item through a sort-of "wobbly" search where it progressively searches
|
||||
* for item away from the [start] index, instead of from position zero. This is useful, as it
|
||||
* increases the likelihood that the correct index was found instead of the index of a
|
||||
* duplicate.
|
||||
*/
|
||||
private fun <T> List<T>.wobblyIndexOfFirst(start: Int, item: T): Int {
|
||||
if (start !in indices) {
|
||||
return -1
|
||||
}
|
||||
|
||||
var idx = start
|
||||
var multiplier = -1
|
||||
var delta = -1
|
||||
|
||||
while (true) {
|
||||
idx += delta
|
||||
|
||||
if (idx !in indices) {
|
||||
if (-idx !in indices) {
|
||||
return -1
|
||||
}
|
||||
} else if (this.getOrNull(idx) == item) {
|
||||
return idx
|
||||
}
|
||||
|
||||
delta = -delta
|
||||
multiplier = -multiplier
|
||||
delta += multiplier
|
||||
}
|
||||
}
|
||||
|
||||
// --- CALLBACKS ---
|
||||
|
||||
private fun notifySongChanged() {
|
||||
private fun notifyIndexMoved() {
|
||||
for (callback in callbacks) {
|
||||
callback.onSongChanged(song)
|
||||
callback.onIndexMoved(index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyParentChanged() {
|
||||
private fun notifyQueueChanged() {
|
||||
for (callback in callbacks) {
|
||||
callback.onParentChanged(parent)
|
||||
callback.onQueueChanged(index, queue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyNewPlayback() {
|
||||
for (callback in callbacks) {
|
||||
callback.onNewPlayback(index, queue, parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyPlayingChanged() {
|
||||
for (callback in callbacks) {
|
||||
callback.onPlayingChanged(isPlaying)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyPositionChanged() {
|
||||
for (callback in callbacks) {
|
||||
callback.onPositionChanged(position)
|
||||
callback.onPositionChanged(positionMs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -607,16 +429,15 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun notifyShufflingChanged() {
|
||||
private fun notifyShuffledChanged() {
|
||||
for (callback in callbacks) {
|
||||
callback.onShuffleChanged(isShuffling)
|
||||
callback.onShuffledChanged(isShuffled)
|
||||
}
|
||||
}
|
||||
|
||||
/** Force any callbacks to receive a queue update. */
|
||||
private fun notifyQueueChanged() {
|
||||
private fun notifySeekEvent() {
|
||||
for (callback in callbacks) {
|
||||
callback.onQueueChanged(mQueue, mIndex)
|
||||
callback.onSeek(positionMs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -625,14 +446,16 @@ class PlaybackStateManager private constructor() {
|
|||
* [PlaybackStateManager] using [addCallback], remove them on destruction with [removeCallback].
|
||||
*/
|
||||
interface Callback {
|
||||
fun onSongChanged(song: Song?) {}
|
||||
fun onParentChanged(parent: MusicParent?) {}
|
||||
fun onPositionChanged(position: Long) {}
|
||||
fun onQueueChanged(queue: List<Song>, index: Int) {}
|
||||
fun onIndexMoved(index: Int) {}
|
||||
fun onQueueChanged(index: Int, queue: List<Song>) {}
|
||||
fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {}
|
||||
|
||||
fun onPlayingChanged(isPlaying: Boolean) {}
|
||||
fun onShuffleChanged(isShuffling: Boolean) {}
|
||||
fun onPositionChanged(positionMs: Long) {}
|
||||
fun onLoopModeChanged(loopMode: LoopMode) {}
|
||||
fun onSeek(position: Long) {}
|
||||
fun onShuffledChanged(isShuffled: Boolean) {}
|
||||
|
||||
fun onSeek(positionMs: Long) {}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -95,13 +95,13 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
|
|||
}
|
||||
|
||||
/** Update the first action to reflect the [loopMode] given. */
|
||||
fun setLoop(loopMode: LoopMode) {
|
||||
fun setLoopMode(loopMode: LoopMode) {
|
||||
mActions[0] = buildLoopAction(context, loopMode)
|
||||
}
|
||||
|
||||
/** Update the first action to reflect whether the queue is shuffled or not */
|
||||
fun setShuffle(isShuffling: Boolean) {
|
||||
mActions[0] = buildShuffleAction(context, isShuffling)
|
||||
fun setShuffled(isShuffled: Boolean) {
|
||||
mActions[0] = buildShuffleAction(context, isShuffled)
|
||||
}
|
||||
|
||||
/** Apply the current [parent] to the header of the notification. */
|
||||
|
|
|
@ -73,6 +73,8 @@ import org.oxycblt.auxio.widgets.WidgetProvider
|
|||
*
|
||||
* TODO: Move all external exposal from passing around PlaybackStateManager to passing around the
|
||||
* MediaMetadata instance. Generally makes it easier to encapsulate this class.
|
||||
*
|
||||
* TODO: Move hasPlayed to here as well.
|
||||
*/
|
||||
class PlaybackService :
|
||||
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||
|
@ -131,6 +133,7 @@ class PlaybackService :
|
|||
delay(POS_POLL_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
// --- SYSTEM SETUP ---
|
||||
|
||||
widgets = WidgetController(this)
|
||||
|
@ -161,9 +164,7 @@ class PlaybackService :
|
|||
|
||||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||
|
||||
playbackManager.setHasPlayed(playbackManager.isPlaying)
|
||||
playbackManager.addCallback(this)
|
||||
|
||||
if (playbackManager.song != null || playbackManager.isRestored) {
|
||||
restore()
|
||||
}
|
||||
|
@ -190,7 +191,7 @@ class PlaybackService :
|
|||
settingsManager.removeCallback(this)
|
||||
|
||||
// Pause just in case this destruction was unexpected.
|
||||
playbackManager.setPlaying(false)
|
||||
playbackManager.isPlaying = false
|
||||
|
||||
// The service coroutines last job is to save the state to the DB, before terminating itself
|
||||
// FIXME: This is a terrible idea, move this to when the user closes the notification
|
||||
|
@ -207,7 +208,7 @@ class PlaybackService :
|
|||
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
||||
super.onPlayWhenReadyChanged(playWhenReady, reason)
|
||||
if (playbackManager.isPlaying != playWhenReady) {
|
||||
playbackManager.setPlaying(playWhenReady)
|
||||
playbackManager.isPlaying = playWhenReady
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,10 +235,8 @@ class PlaybackService :
|
|||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
||||
playbackManager.synchronizePosition(player.currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
|
||||
super.onTracksInfoChanged(tracksInfo)
|
||||
|
@ -258,7 +257,15 @@ class PlaybackService :
|
|||
|
||||
// --- PLAYBACK STATE CALLBACK OVERRIDES ---
|
||||
|
||||
override fun onSongChanged(song: Song?) {
|
||||
override fun onIndexMoved(index: Int) {
|
||||
onSongChanged(playbackManager.song)
|
||||
}
|
||||
|
||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||
onSongChanged(playbackManager.song)
|
||||
}
|
||||
|
||||
private fun onSongChanged(song: Song?) {
|
||||
if (song != null) {
|
||||
logD("Setting player to ${song.rawName}")
|
||||
player.setMediaItem(MediaItem.fromUri(song.uri))
|
||||
|
@ -273,11 +280,6 @@ class PlaybackService :
|
|||
stopForegroundAndNotification()
|
||||
}
|
||||
|
||||
override fun onParentChanged(parent: MusicParent?) {
|
||||
notification.setParent(parent)
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
|
||||
override fun onPlayingChanged(isPlaying: Boolean) {
|
||||
player.playWhenReady = isPlaying
|
||||
notification.setPlaying(isPlaying)
|
||||
|
@ -286,20 +288,20 @@ class PlaybackService :
|
|||
|
||||
override fun onLoopModeChanged(loopMode: LoopMode) {
|
||||
if (!settingsManager.useAltNotifAction) {
|
||||
notification.setLoop(loopMode)
|
||||
notification.setLoopMode(loopMode)
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleChanged(isShuffling: Boolean) {
|
||||
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||
if (settingsManager.useAltNotifAction) {
|
||||
notification.setShuffle(isShuffling)
|
||||
notification.setShuffled(isShuffled)
|
||||
startForegroundOrNotify()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSeek(position: Long) {
|
||||
player.seekTo(position)
|
||||
override fun onSeek(positionMs: Long) {
|
||||
player.seekTo(positionMs)
|
||||
}
|
||||
|
||||
// --- SETTINGSMANAGER OVERRIDES ---
|
||||
|
@ -313,9 +315,9 @@ class PlaybackService :
|
|||
|
||||
override fun onNotifActionUpdate(useAltAction: Boolean) {
|
||||
if (useAltAction) {
|
||||
notification.setShuffle(playbackManager.isShuffling)
|
||||
notification.setShuffled(playbackManager.isShuffled)
|
||||
} else {
|
||||
notification.setLoop(playbackManager.loopMode)
|
||||
notification.setLoopMode(playbackManager.loopMode)
|
||||
}
|
||||
|
||||
startForegroundOrNotify()
|
||||
|
@ -375,13 +377,10 @@ class PlaybackService :
|
|||
private fun restore() {
|
||||
logD("Restoring the service state")
|
||||
|
||||
// Re-call existing callbacks with the current values to restore everything
|
||||
onParentChanged(playbackManager.parent)
|
||||
onPlayingChanged(playbackManager.isPlaying)
|
||||
onShuffleChanged(playbackManager.isShuffling)
|
||||
onLoopModeChanged(playbackManager.loopMode)
|
||||
onSongChanged(playbackManager.song)
|
||||
onSeek(playbackManager.position)
|
||||
onSeek(playbackManager.positionMs)
|
||||
onShuffledChanged(playbackManager.isShuffled)
|
||||
onLoopModeChanged(playbackManager.loopMode)
|
||||
|
||||
// Notify other classes that rely on this service to also update.
|
||||
widgets.update()
|
||||
|
@ -391,7 +390,7 @@ class PlaybackService :
|
|||
* Bring the service into the foreground and show the notification, or refresh the notification.
|
||||
*/
|
||||
private fun startForegroundOrNotify() {
|
||||
if (playbackManager.hasPlayed && playbackManager.song != null) {
|
||||
if (/*playbackManager.hasPlayed &&*/ playbackManager.song != null) {
|
||||
logD("Starting foreground/notifying")
|
||||
|
||||
if (!isForeground) {
|
||||
|
@ -450,14 +449,13 @@ class PlaybackService :
|
|||
AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pauseFromPlug()
|
||||
|
||||
// --- AUXIO EVENTS ---
|
||||
ACTION_PLAY_PAUSE -> playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
ACTION_LOOP -> playbackManager.setLoopMode(playbackManager.loopMode.increment())
|
||||
ACTION_SHUFFLE ->
|
||||
playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
|
||||
ACTION_PLAY_PAUSE -> playbackManager.isPlaying = !playbackManager.isPlaying
|
||||
ACTION_LOOP -> playbackManager.loopMode = playbackManager.loopMode.increment()
|
||||
ACTION_SHUFFLE -> playbackManager.reshuffle(!playbackManager.isShuffled)
|
||||
ACTION_SKIP_PREV -> playbackManager.prev()
|
||||
ACTION_SKIP_NEXT -> playbackManager.next()
|
||||
ACTION_EXIT -> {
|
||||
playbackManager.setPlaying(false)
|
||||
playbackManager.isPlaying = false
|
||||
stopForegroundAndNotification()
|
||||
}
|
||||
WidgetProvider.ACTION_WIDGET_UPDATE -> widgets.update()
|
||||
|
@ -478,7 +476,7 @@ class PlaybackService :
|
|||
settingsManager.headsetAutoplay &&
|
||||
initialHeadsetPlugEventHandled) {
|
||||
logD("Device connected, resuming")
|
||||
playbackManager.setPlaying(true)
|
||||
playbackManager.isPlaying = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,7 +484,7 @@ class PlaybackService :
|
|||
private fun pauseFromPlug() {
|
||||
if (playbackManager.song != null) {
|
||||
logD("Device disconnected, pausing")
|
||||
playbackManager.setPlaying(false)
|
||||
playbackManager.isPlaying = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.support.v4.media.session.MediaSessionCompat
|
|||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import com.google.android.exoplayer2.Player
|
||||
import org.oxycblt.auxio.coil.loadBitmap
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
@ -59,11 +60,11 @@ class PlaybackSessionConnector(
|
|||
// --- MEDIASESSION CALLBACKS ---
|
||||
|
||||
override fun onPlay() {
|
||||
playbackManager.setPlaying(true)
|
||||
playbackManager.isPlaying = true
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
playbackManager.setPlaying(false)
|
||||
playbackManager.isPlaying = false
|
||||
}
|
||||
|
||||
override fun onSkipToNext() {
|
||||
|
@ -80,25 +81,23 @@ class PlaybackSessionConnector(
|
|||
|
||||
override fun onRewind() {
|
||||
playbackManager.rewind()
|
||||
playbackManager.isPlaying = true
|
||||
}
|
||||
|
||||
override fun onSetRepeatMode(repeatMode: Int) {
|
||||
val mode =
|
||||
playbackManager.loopMode =
|
||||
when (repeatMode) {
|
||||
PlaybackStateCompat.REPEAT_MODE_ALL -> LoopMode.ALL
|
||||
PlaybackStateCompat.REPEAT_MODE_GROUP -> LoopMode.ALL
|
||||
PlaybackStateCompat.REPEAT_MODE_ONE -> LoopMode.TRACK
|
||||
else -> LoopMode.NONE
|
||||
}
|
||||
|
||||
playbackManager.setLoopMode(mode)
|
||||
}
|
||||
|
||||
override fun onSetShuffleMode(shuffleMode: Int) {
|
||||
playbackManager.setShuffling(
|
||||
playbackManager.reshuffle(
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
|
||||
true)
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
@ -108,7 +107,19 @@ class PlaybackSessionConnector(
|
|||
|
||||
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
|
||||
|
||||
override fun onSongChanged(song: Song?) {
|
||||
override fun onIndexMoved(index: Int) {
|
||||
onSongChanged(playbackManager.song)
|
||||
}
|
||||
|
||||
override fun onQueueChanged(index: Int, queue: List<Song>) {
|
||||
onSongChanged(playbackManager.song)
|
||||
}
|
||||
|
||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||
onSongChanged(playbackManager.song)
|
||||
}
|
||||
|
||||
fun onSongChanged(song: Song?) {
|
||||
if (song == null) {
|
||||
mediaSession.setMetadata(emptyMetadata)
|
||||
return
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.content.Context
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.LoopMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
@ -61,7 +62,15 @@ class WidgetController(private val context: Context) :
|
|||
|
||||
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
|
||||
|
||||
override fun onSongChanged(song: Song?) {
|
||||
override fun onIndexMoved(index: Int) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onQueueChanged(index: Int, queue: List<Song>) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
|
@ -69,7 +78,7 @@ class WidgetController(private val context: Context) :
|
|||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
override fun onShuffleChanged(isShuffling: Boolean) {
|
||||
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||
widget.update(context, playbackManager)
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class WidgetProvider : AppWidgetProvider() {
|
|||
song,
|
||||
bitmap,
|
||||
playbackManager.isPlaying,
|
||||
playbackManager.isShuffling,
|
||||
playbackManager.isShuffled,
|
||||
playbackManager.loopMode)
|
||||
|
||||
// Map each widget form to the cells where it would look at least okay.
|
||||
|
|
Loading…
Reference in a new issue