Expand description
Telling a finished stream apart from a truncated one.
TRACES: UR-040 | DR-129 | UT-117
Background audio-only playback of a video item streams a progressive mp3
transcode over plain HTTP (see
OnlineRepository::build_audio_only_stream_url_for_video). That response has
no reliable length — a live transcode is chunked — so when the connection
drops mid-episode the data source simply sees end-of-input. ExoPlayer cannot
distinguish that from the real end of the media and reports
Player.STATE_ENDED, which the app then treats as “the episode finished”.
The user-visible damage is not the missed advance itself. Playback parks in
ExoPlayer’s STATE_ENDED, and the next play intent from the lockscreen,
notification or a Bluetooth reconnect goes through media3’s
Util.handlePlayButtonAction, which seeks an ENDED player to its default
position before playing — so the episode starts over from 0:00. On a
flaky connection that reads as “it randomly restarts the episode”.
The player itself has no way to know; the duration does. Jellyfin gives us the item’s real runtime, so an end reported well short of it is a truncation, not a finish — and the right response is to re-open the stream where it died, which is the “buffer and resume” the user expects.
Structs§
- Observed
Time - The last playback time actually observed while media was loaded.
- Resume
Tracker - Budget for consecutive resume attempts that make no progress.
Constants§
- MAX_
STALLED_ RESUME_ ATTEMPTS - Consecutive resume attempts allowed at the same position before giving up.
- RESUME_
PROGRESS_ 🔒EPSILON_ SECS - Position change that counts as “this is a different playback context” — either the resume made progress, or a different item is loaded.
- TRUNCATED_
STREAM_ TOLERANCE_ SECS - How far short of the item’s runtime a stream may end and still count as a natural finish.
Functions§
- is_
audio_ only_ video - A video item played through the native audio path — i.e. the background audio-only handoff, the only place a length-less progressive transcode is used. Jellyfin’s item-type taxonomy stays in Rust (CLAUDE.md).
- is_
truncated_ end - Did this end-of-stream happen far enough short of the item’s runtime to be a truncation rather than a finish?
- player_
retry_ restarts_ stream - Would the player’s own load-error retry restart this stream from its
beginning? If so the retry must be switched off and recovery left to
crate::player::PlayerController::recoverable_error_resume. - with_
start_ time - Rewrite an audio-only stream URL to start at
position_seconds.