fix(plex): a link too slow for the stream resumes in place, and says so

A request hls.js got no answer to in time (no status) was taken for a server
away: a new session, the same first segment lost again, a restart every half
minute that never ended, since every session answer reset the away window.
Measured on prod: a work network at ~4 Mbit/s against 11-16 MB remux segments.

A timeout now resumes loading in the same session; the away window ends only
when media arrives (or a direct stream answers), and runs out in a 'too slow'
error. A segment gets 30 s to its first byte. After 5 s of stalling on a link
measured slower than the stream, the player says so with both rates.
This commit is contained in:
2026-11-07 15:09:22 +01:00
parent 25aed6635f
commit c0682aa572
13 changed files with 495 additions and 30 deletions
+22
View File
@@ -188,12 +188,18 @@ interface FragSample {
type: string; // hls.js fragment type: "main" (video, and muxed audio) / "audio" / "subtitle"
bytes: number;
durationS: number; // MEDIA seconds the fragment carries (not how long it took to download)
loading?: { start: number; end: number }; // when its download ran (ms), when hls.js knew
}
export interface FragMeter {
add(sample: FragSample): void;
/** Bits per second of media, or undefined while nothing has been measured yet. */
bitrate(): number | undefined;
/** Bits per second the link delivered them at, or undefined while no download time is known: every
* timed fragment's bytes over the time ANY of them was downloading. Not per type and summed like
* `bitrate` — the renditions share one link, and their downloads overlap (measured: summing read
* 2.3 Mbit/s through a 1.6 Mbit/s link). */
arrival(): number | undefined;
reset(): void;
}
@@ -222,6 +228,22 @@ export function createFragMeter(window = 10): FragMeter {
}
return total > 0 ? total : undefined;
},
arrival() {
const timed = [...byType.values()]
.flat()
.filter((f) => f.loading && f.loading.end > f.loading.start)
.sort((a, b) => a.loading!.start - b.loading!.start);
let busyMs = 0;
let until = -Infinity;
for (const { loading } of timed) {
const { start, end } = loading!;
if (end <= until) continue;
busyMs += end - Math.max(start, until);
until = end;
}
const bytes = timed.reduce((n, f) => n + f.bytes, 0);
return busyMs > 0 ? (bytes * 8) / (busyMs / 1000) : undefined;
},
reset() {
byType.clear();
},