playback: fix position polling bug
Fix a bug where position polling would stop when the audio focus state changed.
This commit is contained in:
parent
bcec9272d3
commit
3d03bf9b7e
1 changed files with 22 additions and 43 deletions
|
@ -47,9 +47,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.conflate
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.IntegerTable
|
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
|
* TODO: Move all external exposal from passing around PlaybackStateManager to passing around the
|
||||||
* MediaMetadata instance. Generally makes it easier to encapsulate this class.
|
* MediaMetadata instance. Generally makes it easier to encapsulate this class.
|
||||||
*
|
|
||||||
* TODO: Move restore and file opening to service
|
|
||||||
*/
|
*/
|
||||||
class PlaybackService :
|
class PlaybackService :
|
||||||
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||||
|
@ -103,7 +98,8 @@ class PlaybackService :
|
||||||
private var isForeground = false
|
private var isForeground = false
|
||||||
|
|
||||||
private val serviceJob = Job()
|
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 ---
|
// --- SERVICE OVERRIDES ---
|
||||||
|
|
||||||
|
@ -128,13 +124,13 @@ class PlaybackService :
|
||||||
|
|
||||||
player = newPlayer()
|
player = newPlayer()
|
||||||
player.addListener(this@PlaybackService)
|
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 ---
|
// --- SYSTEM SETUP ---
|
||||||
|
|
||||||
widgets = WidgetController(this)
|
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
|
// 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: 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
|
saveScope.launch {
|
||||||
// at it? Would help prevent state saving issues to an extent.
|
|
||||||
serviceScope.launch {
|
|
||||||
playbackManager.saveStateToDatabase(this@PlaybackService)
|
playbackManager.saveStateToDatabase(this@PlaybackService)
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
}
|
}
|
||||||
|
@ -219,7 +213,6 @@ class PlaybackService :
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(state: Int) {
|
override fun onPlaybackStateChanged(state: Int) {
|
||||||
when (state) {
|
when (state) {
|
||||||
Player.STATE_READY -> startPolling()
|
|
||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
if (playbackManager.loopMode == LoopMode.TRACK) {
|
if (playbackManager.loopMode == LoopMode.TRACK) {
|
||||||
playbackManager.loop()
|
playbackManager.loop()
|
||||||
|
@ -236,7 +229,11 @@ class PlaybackService :
|
||||||
playbackManager.next()
|
playbackManager.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPositionDiscontinuity(reason: Int) {
|
override fun onPositionDiscontinuity(
|
||||||
|
oldPosition: Player.PositionInfo,
|
||||||
|
newPosition: Player.PositionInfo,
|
||||||
|
reason: Int
|
||||||
|
) {
|
||||||
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
||||||
playbackManager.setPosition(player.currentPosition)
|
playbackManager.setPosition(player.currentPosition)
|
||||||
}
|
}
|
||||||
|
@ -282,13 +279,7 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayingUpdate(isPlaying: Boolean) {
|
override fun onPlayingUpdate(isPlaying: Boolean) {
|
||||||
if (isPlaying && !player.isPlaying) {
|
player.playWhenReady = isPlaying
|
||||||
player.play()
|
|
||||||
startPolling()
|
|
||||||
} else {
|
|
||||||
player.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
notification.setPlaying(isPlaying)
|
notification.setPlaying(isPlaying)
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
}
|
}
|
||||||
|
@ -371,6 +362,12 @@ class PlaybackService :
|
||||||
return ExoPlayer.Builder(this, audioRenderer)
|
return ExoPlayer.Builder(this, audioRenderer)
|
||||||
.setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory))
|
.setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory))
|
||||||
.setWakeMode(C.WAKE_MODE_LOCAL)
|
.setWakeMode(C.WAKE_MODE_LOCAL)
|
||||||
|
.setAudioAttributes(
|
||||||
|
AudioAttributes.Builder()
|
||||||
|
.setUsage(C.USAGE_MEDIA)
|
||||||
|
.setContentType(C.CONTENT_TYPE_MUSIC)
|
||||||
|
.build(),
|
||||||
|
true)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,24 +387,6 @@ class PlaybackService :
|
||||||
widgets.update()
|
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.
|
* Bring the service into the foreground and show the notification, or refresh the notification.
|
||||||
*/
|
*/
|
||||||
|
@ -519,7 +498,7 @@ class PlaybackService :
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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_LOOP = BuildConfig.APPLICATION_ID + ".action.LOOP"
|
||||||
const val ACTION_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE"
|
const val ACTION_SHUFFLE = BuildConfig.APPLICATION_ID + ".action.SHUFFLE"
|
||||||
|
|
Loading…
Reference in a new issue