Fix issues with PlaybackFragment seekbar

Fix bugs where PlaybackFragment's seekbar would update the player on the fly/flip out if PlaybackService updated the position while it was seeking.
This commit is contained in:
OxygenCobalt 2020-10-26 12:06:17 -06:00
parent 1afaae7b0a
commit ac5e6ba140
4 changed files with 49 additions and 18 deletions

View file

@ -134,8 +134,10 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
}
playbackModel.positionAsProgress.observe(viewLifecycleOwner) {
if (!playbackModel.isSeeking.value!!) {
binding.playbackSeekBar.progress = it
}
}
Log.d(this::class.simpleName, "Fragment Created.")
@ -145,7 +147,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// Seeking callbacks
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
playbackModel.updatePositionWithProgress(progress)
playbackModel.updatePositionDisplay(progress)
}
}
@ -155,5 +157,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
override fun onStopTrackingTouch(seekBar: SeekBar) {
playbackModel.setSeekingStatus(false)
playbackModel.updatePosition(seekBar.progress)
}
}

View file

@ -22,6 +22,8 @@ import org.oxycblt.auxio.music.toURI
import org.oxycblt.auxio.playback.state.PlaybackStateCallback
import org.oxycblt.auxio.playback.state.PlaybackStateManager
// A Service that manages the single ExoPlayer instance and [attempts] to keep
// persistence if the app closes.
class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
private val player: SimpleExoPlayer by lazy {
val p = SimpleExoPlayer.Builder(applicationContext).build()
@ -54,8 +56,16 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
super.onDestroy()
player.release()
playbackManager.removeCallback(this)
serviceJob.cancel()
playbackManager.removeCallback(this)
}
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) {
playbackManager.next()
} else if (state == Player.STATE_READY) {
startPollingPosition()
}
}
override fun onSongUpdate(song: Song?) {
@ -100,14 +110,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
}
}
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) {
playbackManager.skipNext()
} else if (state == Player.STATE_READY) {
startPollingPosition()
}
}
inner class LocalBinder : Binder() {
fun getService() = this@PlaybackService
}

View file

@ -20,7 +20,10 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateCallback
import org.oxycblt.auxio.playback.state.PlaybackStateManager
// The UI frontend for PlaybackStateManager.
// A ViewModel that acts as an intermediary between the UI and PlaybackStateManager
// TODO: Implement Looping Modes
// TODO: Implement User Queue
// TODO: Implement Persistence through Bundles/Databases/Idk
class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackStateCallback {
// Playback
private val mSong = MutableLiveData<Song>()
@ -88,14 +91,17 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- PLAYING FUNCTIONS ---
// Play a song
fun playSong(song: Song, mode: PlaybackMode) {
playbackManager.playSong(song, mode)
}
// Play all songs
fun shuffleAll() {
playbackManager.shuffleAll()
}
// Play an album
fun playAlbum(album: Album, shuffled: Boolean) {
if (album.songs.isEmpty()) {
Log.e(this::class.simpleName, "Album is empty, Not playing.")
@ -106,6 +112,7 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
playbackManager.playParentModel(album, shuffled)
}
// Play an artist
fun playArtist(artist: Artist, shuffled: Boolean) {
if (artist.songs.isEmpty()) {
Log.e(this::class.simpleName, "Artist is empty, Not playing.")
@ -116,6 +123,7 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
playbackManager.playParentModel(artist, shuffled)
}
// Play a genre
fun playGenre(genre: Genre, shuffled: Boolean) {
if (genre.songs.isEmpty()) {
Log.e(this::class.simpleName, "Genre is empty, Not playing.")
@ -128,7 +136,15 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- POSITION FUNCTIONS ---
fun updatePositionWithProgress(progress: Int) {
// Update the position without pushing the change to playbackManager.
// This is used during seek events to give the user an idea of where they're seeking to.
fun updatePositionDisplay(progress: Int) {
mPosition.value = progress.toLong()
}
// Update the position and push the change the playbackManager.
// This is done when the seek is confirmed to make playbackService seek to the position.
fun updatePosition(progress: Int) {
playbackManager.setPosition(progress.toLong())
playbackService.doSeek(progress.toLong())
@ -136,14 +152,17 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- QUEUE FUNCTIONS ---
// Skip to next song.
fun skipNext() {
playbackManager.skipNext()
playbackManager.next()
}
// Skip to last song.
fun skipPrev() {
playbackManager.skipPrev()
playbackManager.prev()
}
// Remove a queue item, given a QueueAdapter index.
fun removeQueueItem(adapterIndex: Int) {
// Translate the adapter indices into the correct queue indices
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
@ -153,6 +172,7 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
playbackManager.removeQueueItem(index)
}
// Move queue items, given QueueAdapter indices.
fun moveQueueItems(adapterFrom: Int, adapterTo: Int) {
// Translate the adapter indices into the correct queue indices
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
@ -165,12 +185,14 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- STATUS FUNCTIONS ---
// Flip the playing status.
fun invertPlayingStatus() {
mCanAnimate = true
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
}
// Flip the shuffle status.
fun invertShuffleStatus() {
playbackManager.setShuffleStatus(!playbackManager.isShuffling)
}
@ -198,8 +220,10 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
}
override fun onPositionUpdate(position: Long) {
if (!mIsSeeking.value!!) {
mPosition.value = position
}
}
override fun onQueueUpdate(queue: MutableList<Song>) {
mQueue.value = queue

View file

@ -166,7 +166,7 @@ class PlaybackStateManager {
// --- QUEUE FUNCTIONS ---
fun skipNext() {
fun next() {
if (mIndex < mQueue.size) {
mIndex = mIndex.inc()
}
@ -176,7 +176,7 @@ class PlaybackStateManager {
forceQueueUpdate()
}
fun skipPrev() {
fun prev() {
if (mIndex > 0) {
mIndex = mIndex.dec()
}
@ -215,6 +215,7 @@ class PlaybackStateManager {
forceQueueUpdate()
}
// Force any callbacks to update when the queue is changed.
private fun forceQueueUpdate() {
mQueue = mQueue
}