Add playback updates to PlaybackService

Add the ability for PlaybackService to move to the next song when the current one ends, update the duration, and also be played/paused.
This commit is contained in:
OxygenCobalt 2020-10-26 11:04:22 -06:00
parent 42325c456e
commit 1afaae7b0a
5 changed files with 63 additions and 7 deletions

View file

@ -13,8 +13,8 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailSongAdapter import org.oxycblt.auxio.detail.adapters.DetailSongAdapter
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.disable import org.oxycblt.auxio.theme.disable

View file

@ -25,8 +25,8 @@ import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.theme.applyColor import org.oxycblt.auxio.theme.applyColor
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.resolveAttr import org.oxycblt.auxio.theme.resolveAttr

View file

@ -8,6 +8,15 @@ import android.os.IBinder
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.SimpleExoPlayer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.music.toURI
import org.oxycblt.auxio.playback.state.PlaybackStateCallback import org.oxycblt.auxio.playback.state.PlaybackStateCallback
@ -26,6 +35,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
private val mBinder = LocalBinder() private val mBinder = LocalBinder()
private val playbackManager = PlaybackStateManager.getInstance() private val playbackManager = PlaybackStateManager.getInstance()
private val serviceJob = Job()
private val serviceScope = CoroutineScope(
serviceJob + Dispatchers.Main
)
override fun onBind(intent: Intent): IBinder { override fun onBind(intent: Intent): IBinder {
return mBinder return mBinder
} }
@ -41,17 +55,59 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
player.release() player.release()
playbackManager.removeCallback(this) playbackManager.removeCallback(this)
serviceJob.cancel()
} }
override fun onSongUpdate(song: Song?) { override fun onSongUpdate(song: Song?) {
song?.let { song?.let { song ->
val item = MediaItem.fromUri(it.id.toURI()) val item = MediaItem.fromUri(song.id.toURI())
player.setMediaItem(item) player.setMediaItem(item)
player.prepare() player.prepare()
player.play() player.play()
} }
} }
override fun onPlayingUpdate(isPlaying: Boolean) {
if (isPlaying) {
player.play()
startPollingPosition()
} else {
player.pause()
}
}
fun doSeek(position: Long) {
player.seekTo(position * 1000)
}
// Awful Hack to get position polling to work, as exoplayer does not provide any
// onPositionChanged callback for some inane reason.
// FIXME: Consider using exoplayer UI elements here, don't be surprised if this causes problems.
private fun pollCurrentPosition() = flow {
while (player.currentPosition <= player.duration) {
emit(player.currentPosition)
delay(500)
}
}.conflate()
private fun startPollingPosition() {
serviceScope.launch {
pollCurrentPosition().takeWhile { true }.collect {
playbackManager.setPosition(it / 1000)
}
}
}
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

@ -82,9 +82,7 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
playbackIntent = Intent(context, PlaybackService::class.java).also { playbackIntent = Intent(context, PlaybackService::class.java).also {
context.bindService(it, connection, Context.BIND_AUTO_CREATE) context.bindService(it, connection, Context.BIND_AUTO_CREATE)
} }
}
init {
playbackManager.addCallback(this) playbackManager.addCallback(this)
} }
@ -132,6 +130,8 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
fun updatePositionWithProgress(progress: Int) { fun updatePositionWithProgress(progress: Int) {
playbackManager.setPosition(progress.toLong()) playbackManager.setPosition(progress.toLong())
playbackService.doSeek(progress.toLong())
} }
// --- QUEUE FUNCTIONS --- // --- QUEUE FUNCTIONS ---

View file

@ -10,8 +10,8 @@ import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSongsBinding import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
class SongsFragment : Fragment() { class SongsFragment : Fragment() {