Skip to main content

Module stream_end

Module stream_end 

Source
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§

ObservedTime
The last playback time actually observed while media was loaded.
ResumeTracker
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.