fix(plex): a finished HLS session outlives a throttled heartbeat
This commit is contained in:
+1
-1
@@ -151,7 +151,7 @@ async def _reap_hls_sessions() -> None:
|
||||
somebody pressed play again or the process restarted.
|
||||
|
||||
A FINISHED session is reaped on the short rule here, not kept for the full idle window: an open
|
||||
player checkpoints every 10 s and that route touches the session, so a paused viewer is no longer
|
||||
player keeps checkpointing and that route touches the session, so a paused viewer is no longer
|
||||
invisible to this pass (see `reap_idle`). Keeping every finished session for ten minutes instead
|
||||
held one whole segmented copy of a film per item on the download volume."""
|
||||
while True:
|
||||
|
||||
@@ -1160,9 +1160,13 @@ def sweep_orphans(older_than: float | None = None) -> int:
|
||||
return dropped
|
||||
|
||||
|
||||
# How long a session whose ffmpeg has FINISHED may go unfetched before a new session request reaps
|
||||
# it (see reap_idle).
|
||||
_FINISHED_IDLE_S = 30
|
||||
# The slowest heartbeat a live player sends: a browser cuts the timers of a hidden page that makes no
|
||||
# sound to one a minute (measured on Chromium: a paused and a muted player, about a minute after
|
||||
# hiding; a hidden player with sound keeps its 10 s).
|
||||
_THROTTLED_HEARTBEAT_S = 60
|
||||
# How long a session whose ffmpeg has FINISHED may go without a signal before `reap_idle` takes it:
|
||||
# the beat before the reaper looks, one lost on the way, and the time the next one takes to arrive.
|
||||
_FINISHED_IDLE_S = 3 * _THROTTLED_HEARTBEAT_S
|
||||
|
||||
|
||||
def reap_idle(finished_idle_s: float = _FINISHED_IDLE_S) -> int:
|
||||
@@ -1171,15 +1175,17 @@ def reap_idle(finished_idle_s: float = _FINISHED_IDLE_S) -> int:
|
||||
|
||||
A finished session is also what a PAUSED viewer has: a remux runs far faster than realtime, so
|
||||
ffmpeg is done a minute or two in and writes ENDLIST; hls.js then stops asking for the playlist,
|
||||
and with its forward buffer full a paused player fetches nothing at all. With the 30 s rule that
|
||||
killed a one-minute pause, and playback died ~30 s after resuming.
|
||||
and with its forward buffer full a paused player fetches nothing at all.
|
||||
|
||||
What makes the short rule safe again is that fetching is no longer the only signal: a player
|
||||
parked MID-ITEM checkpoints its position every 10 s, and that route calls `touch_session`, so a
|
||||
viewer who is merely paused says so far more often than this rule asks. The heartbeat
|
||||
What makes the short rule safe is that fetching is not the only signal: a player parked MID-ITEM
|
||||
checkpoints its position and that route calls `touch_session`. That beat is every 10 s only while
|
||||
the page is visible or making sound; a hidden, silent one sends it once a minute, which a 30 s
|
||||
rule killed whenever the reaper happened to run more than 30 s after the beat (measured: a paused
|
||||
session gone that way, and one that lived on the same cadence because its phase was luckier).
|
||||
So the rule is sized to the throttled beat (`_THROTTLED_HEARTBEAT_S`). The heartbeat
|
||||
stops once the item is finished or before a position is known (`usePlexProgress.flush`) — both
|
||||
states where reaping is correct, and where the player's 404 → reclaim recovers a viewer who
|
||||
scrubs back in anyway. Do not restate this as "every 10 s whether or not it is playing":
|
||||
scrubs back in anyway. Do not restate this as "whether or not it is playing":
|
||||
a review found that claim in two comments and not in the code. An earlier blanket
|
||||
`reap_abandoned` bought the same safety by keeping every finished session for ten minutes — each
|
||||
one a whole segmented copy of the film on a volume shared with the download center."""
|
||||
|
||||
@@ -323,7 +323,8 @@ def item_progress(
|
||||
`_should_push_resume`). `final` (sent on pause / seek / pagehide / unmount) pushes immediately and
|
||||
resets the throttle window, so Plex sees a settled position the instant the user pauses or seeks."""
|
||||
it = item_or_404(db, rating_key)
|
||||
# A player parked mid-item — playing or PAUSED — checkpoints every 10 s, so this is the signal
|
||||
# A player parked mid-item — playing or PAUSED — keeps checkpointing (every 10 s, once a minute
|
||||
# in a hidden silent page), so this is the signal
|
||||
# that such a viewer still wants their HLS session: the one thing `last_access` could not see,
|
||||
# since it moved only on a file fetch and a paused player fetches nothing (see
|
||||
# stream.reap_idle). It is NOT every open player: `usePlexProgress.flush` sends nothing once the
|
||||
|
||||
Reference in New Issue
Block a user