playback: fix position polling bug

Fix a bug where position polling would stop when the audio focus
state changed.
This commit is contained in:
OxygenCobalt 2022-04-28 20:04:10 -06:00
parent bcec9272d3
commit 3d03bf9b7e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47

View file

@ -47,9 +47,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.IntegerTable
@ -76,8 +73,6 @@ import org.oxycblt.auxio.widgets.WidgetProvider
*
* TODO: Move all external exposal from passing around PlaybackStateManager to passing around the
* MediaMetadata instance. Generally makes it easier to encapsulate this class.
*
* TODO: Move restore and file opening to service
*/
class PlaybackService :
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
@ -103,7 +98,8 @@ class PlaybackService :
private var isForeground = false
private val serviceJob = Job()
private val serviceScope = CoroutineScope(serviceJob + Dispatchers.Main)
private val positionScope = CoroutineScope(serviceJob + Dispatchers.Main)
private val saveScope = CoroutineScope(serviceJob + Dispatchers.Main)
// --- SERVICE OVERRIDES ---
@ -128,13 +124,13 @@ class PlaybackService :
player = newPlayer()
player.addListener(this@PlaybackService)
player.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build(),
true)
positionScope.launch {
while (true) {
playbackManager.setPosition(player.currentPosition)
delay(POS_POLL_INTERVAL)
}
}
// --- SYSTEM SETUP ---
widgets = WidgetController(this)
@ -198,9 +194,7 @@ class PlaybackService :
// 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: Why not also encourage the user to disable battery optimizations while were
// at it? Would help prevent state saving issues to an extent.
serviceScope.launch {
saveScope.launch {
playbackManager.saveStateToDatabase(this@PlaybackService)
serviceJob.cancel()
}
@ -219,7 +213,6 @@ class PlaybackService :
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_READY -> startPolling()
Player.STATE_ENDED -> {
if (playbackManager.loopMode == LoopMode.TRACK) {
playbackManager.loop()
@ -236,7 +229,11 @@ class PlaybackService :
playbackManager.next()
}
override fun onPositionDiscontinuity(reason: Int) {
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
playbackManager.setPosition(player.currentPosition)
}
@ -282,13 +279,7 @@ class PlaybackService :
}
override fun onPlayingUpdate(isPlaying: Boolean) {
if (isPlaying && !player.isPlaying) {
player.play()
startPolling()
} else {
player.pause()
}
player.playWhenReady = isPlaying
notification.setPlaying(isPlaying)
startForegroundOrNotify()
}
@ -371,6 +362,12 @@ class PlaybackService :
return ExoPlayer.Builder(this, audioRenderer)
.setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory))
.setWakeMode(C.WAKE_MODE_LOCAL)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build(),
true)
.build()
}
@ -390,24 +387,6 @@ class PlaybackService :
widgets.update()
}
/** Start polling the position on a coroutine. */
private fun startPolling() {
val pollFlow =
flow {
while (true) {
emit(player.currentPosition)
delay(POS_POLL_INTERVAL)
}
}
.conflate()
serviceScope.launch {
pollFlow.takeWhile { player.isPlaying }.collect { pos ->
playbackManager.setPosition(pos)
}
}
}
/**
* Bring the service into the foreground and show the notification, or refresh the notification.
*/
@ -519,7 +498,7 @@ class PlaybackService :
}
companion object {
private const val POS_POLL_INTERVAL = 500L
private const val POS_POLL_INTERVAL = 1000L
const val ACTION_LOOP = BuildConfig.APPLICATION_ID + ".action.LOOP"
const val ACTION_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE"