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
+24 -5
View File
@@ -70,7 +70,9 @@ import {
usePlayerVolume,
usePlexProgress,
usePlexTracks,
useSlowLink,
useWallClock,
slowLinkRates,
} from "../../lib/plexPlayerHooks";
import { canFullscreenElements, fullscreenElement } from "../../lib/fullscreen";
import {
@@ -101,7 +103,7 @@ import { MenuCheck } from "../../components/ui/MenuCheck";
import { PopoverItem, PopoverSurface } from "../../components/ui/AnchoredPopover";
import { closeThenAct, useAnchoredPopover } from "../../components/ui/anchoredPopoverHooks";
import { Slider } from "../../components/ui/NativeControls";
import { formatUnit } from "../../lib/format";
import { formatMbps, formatUnit } from "../../lib/format";
import { trackLabel } from "../../lib/vocab";
import { msg } from "../../lib/notifications";
@@ -286,9 +288,16 @@ export default function PlexPlayer({
? t("plex.player.errTranscode")
: engineError === "notFound"
? t("plex.player.errNotFound")
: engineError
? t("plex.player.errGeneric")
: null;
: engineError === "slow"
? t("plex.player.errSlow")
: engineError
? t("plex.player.errGeneric")
: null;
// A stall that lasts, on a link measured slower than the stream: said under the spinner, with both
// numbers, so the viewer can tell their connection from a broken player.
const slowRates = useSlowLink((!ready || buffering) && !loadError, () =>
slowLinkRates(fragMeterRef.current.bitrate(), fragMeterRef.current.arrival()),
);
// Refs for values read inside stable callbacks / the keyboard handler (avoid stale closures).
const barAutoHideRef = useLatest(prefs.barAutoHide);
// The two language prefs are read only at ITEM LOAD (the detail effect below), so they go through
@@ -1326,7 +1335,17 @@ export default function PlexPlayer({
{/* Announce the stall to assistive tech. Initial load shows visible text; a mid-playback
rebuffer/seek (ready, no error) has no visible label, so give it an sr-only one via the
aria-live region so a screen reader distinguishes "buffering" from a frozen frame. */}
{!ready ? (
{slowRates ? (
<>
<span>{t("plex.player.slowLink")}</span>
<span className="text-xs text-white/60">
{t("plex.player.slowLinkRates", {
need: formatMbps(slowRates.need, i18n.language),
got: formatMbps(slowRates.got, i18n.language),
})}
</span>
</>
) : !ready ? (
<span>
{t(sessionIsTranscode ? "plex.player.preparing" : "plex.player.loading")}
</span>