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