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,7 +134,9 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
playbackModel.positionAsProgress.observe(viewLifecycleOwner) { playbackModel.positionAsProgress.observe(viewLifecycleOwner) {
binding.playbackSeekBar.progress = it if (!playbackModel.isSeeking.value!!) {
binding.playbackSeekBar.progress = it
}
} }
Log.d(this::class.simpleName, "Fragment Created.") Log.d(this::class.simpleName, "Fragment Created.")
@ -145,7 +147,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
// Seeking callbacks // Seeking callbacks
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) { if (fromUser) {
playbackModel.updatePositionWithProgress(progress) playbackModel.updatePositionDisplay(progress)
} }
} }
@ -155,5 +157,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
override fun onStopTrackingTouch(seekBar: SeekBar) { override fun onStopTrackingTouch(seekBar: SeekBar) {
playbackModel.setSeekingStatus(false) 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.PlaybackStateCallback
import org.oxycblt.auxio.playback.state.PlaybackStateManager 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 { class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
private val player: SimpleExoPlayer by lazy { private val player: SimpleExoPlayer by lazy {
val p = SimpleExoPlayer.Builder(applicationContext).build() val p = SimpleExoPlayer.Builder(applicationContext).build()
@ -54,8 +56,16 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
super.onDestroy() super.onDestroy()
player.release() player.release()
playbackManager.removeCallback(this)
serviceJob.cancel() 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?) { 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() { inner class LocalBinder : Binder() {
fun getService() = this@PlaybackService 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.PlaybackStateCallback
import org.oxycblt.auxio.playback.state.PlaybackStateManager 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 { class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackStateCallback {
// Playback // Playback
private val mSong = MutableLiveData<Song>() private val mSong = MutableLiveData<Song>()
@ -88,14 +91,17 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- PLAYING FUNCTIONS --- // --- PLAYING FUNCTIONS ---
// Play a song
fun playSong(song: Song, mode: PlaybackMode) { fun playSong(song: Song, mode: PlaybackMode) {
playbackManager.playSong(song, mode) playbackManager.playSong(song, mode)
} }
// Play all songs
fun shuffleAll() { fun shuffleAll() {
playbackManager.shuffleAll() playbackManager.shuffleAll()
} }
// Play an album
fun playAlbum(album: Album, shuffled: Boolean) { fun playAlbum(album: Album, shuffled: Boolean) {
if (album.songs.isEmpty()) { if (album.songs.isEmpty()) {
Log.e(this::class.simpleName, "Album is empty, Not playing.") 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) playbackManager.playParentModel(album, shuffled)
} }
// Play an artist
fun playArtist(artist: Artist, shuffled: Boolean) { fun playArtist(artist: Artist, shuffled: Boolean) {
if (artist.songs.isEmpty()) { if (artist.songs.isEmpty()) {
Log.e(this::class.simpleName, "Artist is empty, Not playing.") 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) playbackManager.playParentModel(artist, shuffled)
} }
// Play a genre
fun playGenre(genre: Genre, shuffled: Boolean) { fun playGenre(genre: Genre, shuffled: Boolean) {
if (genre.songs.isEmpty()) { if (genre.songs.isEmpty()) {
Log.e(this::class.simpleName, "Genre is empty, Not playing.") Log.e(this::class.simpleName, "Genre is empty, Not playing.")
@ -128,7 +136,15 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- POSITION FUNCTIONS --- // --- 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()) playbackManager.setPosition(progress.toLong())
playbackService.doSeek(progress.toLong()) playbackService.doSeek(progress.toLong())
@ -136,14 +152,17 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- QUEUE FUNCTIONS --- // --- QUEUE FUNCTIONS ---
// Skip to next song.
fun skipNext() { fun skipNext() {
playbackManager.skipNext() playbackManager.next()
} }
// Skip to last song.
fun skipPrev() { fun skipPrev() {
playbackManager.skipPrev() playbackManager.prev()
} }
// Remove a queue item, given a QueueAdapter index.
fun removeQueueItem(adapterIndex: Int) { fun removeQueueItem(adapterIndex: Int) {
// Translate the adapter indices into the correct queue indices // Translate the adapter indices into the correct queue indices
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
@ -153,6 +172,7 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
playbackManager.removeQueueItem(index) playbackManager.removeQueueItem(index)
} }
// Move queue items, given QueueAdapter indices.
fun moveQueueItems(adapterFrom: Int, adapterTo: Int) { fun moveQueueItems(adapterFrom: Int, adapterTo: Int) {
// Translate the adapter indices into the correct queue indices // Translate the adapter indices into the correct queue indices
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
@ -165,12 +185,14 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
// --- STATUS FUNCTIONS --- // --- STATUS FUNCTIONS ---
// Flip the playing status.
fun invertPlayingStatus() { fun invertPlayingStatus() {
mCanAnimate = true mCanAnimate = true
playbackManager.setPlayingStatus(!playbackManager.isPlaying) playbackManager.setPlayingStatus(!playbackManager.isPlaying)
} }
// Flip the shuffle status.
fun invertShuffleStatus() { fun invertShuffleStatus() {
playbackManager.setShuffleStatus(!playbackManager.isShuffling) playbackManager.setShuffleStatus(!playbackManager.isShuffling)
} }
@ -198,7 +220,9 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
} }
override fun onPositionUpdate(position: Long) { override fun onPositionUpdate(position: Long) {
mPosition.value = position if (!mIsSeeking.value!!) {
mPosition.value = position
}
} }
override fun onQueueUpdate(queue: MutableList<Song>) { override fun onQueueUpdate(queue: MutableList<Song>) {

View file

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