fix(plex): a seek during a quiet reclaim is no longer undone

A quiet request leaves `live` alone, so a seek inside the old session's
range went native and the answer then jumped back to where it asked from.
`seek` now also goes by `pending`; the server-away retry had the same hole.

Comments that still named 404 for a lost session say 410, and the hls.js
retry comment now tells a finished playlist from a growing one.
This commit is contained in:
2026-11-03 21:10:51 +01:00
parent 469c8aa481
commit 3f6638bf96
6 changed files with 50 additions and 23 deletions
+11 -8
View File
@@ -285,7 +285,7 @@ export class HlsEngine {
/** (Re)start `id` at `startAt` seconds. Another item's request goes out at once; one for the item
* already being asked for waits for that answer (see the header). `quiet`: the attached stream is
* still playing from its buffer (the server went away under it), so it stays the player's until
* still playing from its buffer (the server went away or lost its session), so it stays the player's until
* the answer replaces it — no "Loading…" over a running picture, its clock still true. */
start(id: string, startAt: number, play = true, quiet = false): void {
if (!this.deps.media()) return;
@@ -467,12 +467,14 @@ export class HlsEngine {
// Except a session the server no longer has (410): gone, not broken. Either the server reaped it
// or restarted, or it was superseded before it ever played (two requests for one item: the
// server's LAST spawn wins), and the reclaim is the asking again that answer wants. It does not
// wait for hls.js to give up: with one video variant it has nothing to switch to, so it retries
// even a 4xx, six times over about half a minute (measured: seven requests, then the reclaim), and
// in a throttled page once a minute (four minutes). Quiet, since the buffer is still playing.
// wait for hls.js to give up. On a finished playlist, with one video variant and nothing to
// switch to, it retries even a 4xx, six times over about half a minute (measured: seven requests,
// then the reclaim), and in a throttled page once a minute (four minutes). On a growing one it
// takes the segment for a gap and moves on, so the error is never fatal. Either way the first
// answer is reported. Quiet, since the buffer is still playing.
//
// And except a fatal 404: a segment still not there after those retries, so a transcode has fallen
// behind, and asking again is all that is left.
// And except a fatal 404: hls.js gave up on something the server still does not have, and asking
// again is all that is left.
//
// Both are bounded by the budget, not by whether the session got as far as playing: that belongs to
// ONE session, and every restart builds a fresh one, so it could never bound a loop. A transcode
@@ -565,9 +567,10 @@ export class HlsEngine {
const rel = T - this.sessionStart.current;
// Native only when the attached media IS the current session. While one is still being asked
// for, `sessionStart` and `seekable` describe the OLD one: a native seek there left the pending
// restart current, and its answer then jumped to the earlier target.
// restart current, and its answer then jumped to the earlier target. A quiet request leaves
// `live` alone, so `pending` is what says one is out.
const end = media.seekable.length ? media.seekable.end(0) : 0;
if (this.live && rel >= 0 && rel <= end + 0.5) {
if (this.live && !this.pending && rel >= 0 && rel <= end + 0.5) {
media.currentTime = rel; // keeps playing on its own
if (play) media.play().catch(() => {});
} else {