android: make playback and prefetch recover instead of stall - #11
Merged
Merged
Conversation
ExoBackend reports Paused only when the player no longer means to play (playWhenReady false); a stall while it still does is Buffering, so a rebuffer, a retried load or an error being recovered from no longer shows the item paused and no longer lets the service count the player as idle. Errors a later prepare() can fix are widened from connection failures to timeouts, 5xx statuses, a stuck player and core stream handles that went away under the player; while offline the retry waits instead of spending attempts and the network coming back (NetworkMonitor -> onConnectivityChanged) retries at once. A handle the core closed is retried by the load-error policy (a fresh open at the position, served by the cache). The service's idle stop and task-removed stop never release a player that is still buffering or recovering. Core: an item adopted at a gapless boundary that the backend never confirms with TransitionedToNext (a SetNext still in flight when the player ran out, a preload it dropped) is loaded explicitly after two seconds instead of leaving the session on an item nothing plays; a preloaded follow-up that fails before the transition is forgotten rather than restarting the current item from the top. Prefetch retries failed fetches with a growing backoff, at once when the network comes back, and treats a read that left no cache entry as a failure instead of starting it over immediately. The stream reader drops reqwest's read timeout, which also ran while ExoPlayer's full buffer left the body unread for minutes, and waits for the server only while a read is actually pending. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnhqP7bbeXKtuNP9dkcrPy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnhqP7bbeXKtuNP9dkcrPy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The Android app sometimes stopped playing without saying why, and sometimes did not cache the upcoming tracks. A pass over the ExoPlayer bridge, the service lifecycle and the core's playback and prefetch handling found several ways to end up silently stuck or to give up on caching.
What changed
ExoBackend (Android)
Pausedis reported only when the player no longer means to play (playWhenReadyfalse). A stall with it still set (a rebuffer, a retried load, an error being recovered from) isBuffering. Before, such stalls showed the track as paused and let the service count the player as idle and stop itself mid-recovery.prepare()can fix are widened from connection failures/timeouts to 5xx statuses, a stuck player and core stream handles that went away under the player (the core closes idle handles after ten minutes, and ExoPlayer legitimately idles for minutes once its buffer is full).NetworkMonitor->ExoBackend.onConnectivityChanged) retries at once with a fresh schedule.CoreStreamLoadErrorPolicyretries a closed/reaped handle with a fresh open at the same position, which the cache serves.ExoBackend.isBusy).Core
TransitionedToNext(aSetNextstill in flight when the player ran out, a preload it dropped) is loaded explicitly after two seconds instead of leaving the session on an item nothing plays.Tests
cargo test -p hocket-core --features sim(three new actor tests: adoption fallback, prefetch backoff retry, prefetch retry on network return); clippy and fmt clean.:coreand:playbackunit tests (four new: error classification, stall reporting, busy state, closed-handle retry policy); the real-core end-to-end JVM test against the host build of the core.🤖 Generated with Claude Code
https://claude.ai/code/session_01DnhqP7bbeXKtuNP9dkcrPy
Generated by Claude Code