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:
parent
1afaae7b0a
commit
ac5e6ba140
4 changed files with 49 additions and 18 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue