fix: fall back at once when a music app refuses the alarm
The start waited for PLAYING and nothing else, so an app that answered the play request with an error (YouTube without Premium) left the alarm vibrating in silence for the full 15 s START_TIMEOUT before the default sound took over. An error stamped at or after the request, or a lost connection, now ends the wait straight away. An error left over from the user's own listening still doesn't count.
This commit is contained in:
1 parent
6637554c9d
commit
d5016183ad
3 files changed
+36
-1
No files matched your search
@@ -60,6 +60,17 @@ object MediaRingPolicy {
|
||||
|
||||
fun shouldFallBack(stall: Stall): Boolean = stall.stopped >= STALL_GRACE || stall.notPlaying >= BUFFER_GRACE
|
||||
|
||||
/**
|
||||
* Whether the app turned down the ring's play request, so the start can
|
||||
* fall back now instead of waiting out [START_TIMEOUT]. YouTube without
|
||||
* Premium does this at once. Only an error reported at or after
|
||||
* [requestedAtMillis] counts: one left over from the user's own listening
|
||||
* says nothing about this request. Both times are `elapsedRealtime`, which
|
||||
* is what `PlaybackState` stamps its updates with.
|
||||
*/
|
||||
fun refusedStart(isError: Boolean, updatedAtMillis: Long, requestedAtMillis: Long): Boolean =
|
||||
isError && updatedAtMillis >= requestedAtMillis
|
||||
|
||||
/**
|
||||
* The media-stream index a fully ramped ring plays at.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.media.AudioManager
|
||||
import android.media.browse.MediaBrowser
|
||||
import android.media.session.MediaController
|
||||
import android.media.session.PlaybackState
|
||||
import android.os.SystemClock
|
||||
import de.jeanlucmakiola.clockula.data.ringtones.connectMediaBrowser
|
||||
import de.jeanlucmakiola.clockula.domain.MediaSourceRef
|
||||
import de.jeanlucmakiola.clockula.domain.ring.MediaPlayback
|
||||
@@ -86,6 +87,7 @@ internal class MediaAppRing(private val context: Context, private val ref: Media
|
||||
}
|
||||
}
|
||||
if (stopped) return false
|
||||
val requestedAt = SystemClock.elapsedRealtime()
|
||||
runCatching {
|
||||
if (ref.mediaId != null) controls.playFromMediaId(ref.mediaId, null) else controls.play()
|
||||
}.onFailure { return false }
|
||||
@@ -93,11 +95,23 @@ internal class MediaAppRing(private val context: Context, private val ref: Media
|
||||
return withTimeoutOrNull(MediaRingPolicy.START_TIMEOUT) {
|
||||
// Strictly PLAYING here: an app stuck buffering a stream it cannot
|
||||
// reach must not count as a ring that started.
|
||||
while (!stopped && playbackState() != PlaybackState.STATE_PLAYING) delay(POLL_MILLIS)
|
||||
while (!stopped && playbackState() != PlaybackState.STATE_PLAYING) {
|
||||
if (connectionLost || refused(requestedAt)) return@withTimeoutOrNull false
|
||||
delay(POLL_MILLIS)
|
||||
}
|
||||
!stopped
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun refused(requestedAt: Long): Boolean {
|
||||
val state = runCatching { controller?.playbackState }.getOrNull() ?: return false
|
||||
return MediaRingPolicy.refusedStart(
|
||||
isError = state.state == PlaybackState.STATE_ERROR,
|
||||
updatedAtMillis = state.lastPositionUpdateTime,
|
||||
requestedAtMillis = requestedAt,
|
||||
)
|
||||
}
|
||||
|
||||
fun playback(): MediaPlayback {
|
||||
if (connectionLost) return MediaPlayback.STOPPED
|
||||
return when (playbackState()) {
|
||||
|
||||
@@ -29,6 +29,16 @@ class MediaRingPolicyTest {
|
||||
assertThat(MediaRingPolicy.shouldFallBack(long)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an error after the play request ends the start, an older one does not`() {
|
||||
val requestedAt = 10_000L
|
||||
|
||||
assertThat(MediaRingPolicy.refusedStart(isError = true, updatedAtMillis = 10_050L, requestedAtMillis = requestedAt)).isTrue()
|
||||
assertThat(MediaRingPolicy.refusedStart(isError = true, updatedAtMillis = requestedAt, requestedAtMillis = requestedAt)).isTrue()
|
||||
assertThat(MediaRingPolicy.refusedStart(isError = true, updatedAtMillis = 9_000L, requestedAtMillis = requestedAt)).isFalse()
|
||||
assertThat(MediaRingPolicy.refusedStart(isError = false, updatedAtMillis = 10_050L, requestedAtMillis = requestedAt)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buffering forever is a failure too`() {
|
||||
val brief = run(*repeat(MediaPlayback.TRANSITIONING, MediaRingPolicy.BUFFER_GRACE - tick))
|
||||
|
||||
Reference in new issue
Block a user