Fix subtitle selection timing out: hls.js's default timeouts too tight
Reported: selecting a subtitle track (only subtitles, not audio/quality) made the request "time out". Confirmed from the browser's own error: hls.js gave up with a fatal fragLoadTimeOut after 4 retries, each hitting its default 10s fragment-load timeout - not a hang, and not a repeat of the earlier "override doesn't reach Emby" bug (see docs/architecture.md's Player-track-selection section) - the same error's frag.baseurl already shows SubtitleMethod=Encode correctly present in the request Emby received, so the override chain works end to end. It's just slow: burning a subtitle in forces a real re-encode (not a rendition, not a remux), which measurably takes Emby longer than 10s to produce a first segment for. Fixed by giving both players' Hls instances explicit longer timeouts (manifestLoadingTimeOut/levelLoadingTimeOut: 30s, fragLoadingTimeOut: 60s) instead of hls.js's untouched defaults - applied to musicBar/MusicBar.ts too, not just the video player, since music tracks go through the same PlaybackInfo/transcode pipeline and could hit the same latency for a quality change even without subtitles being involved there today. global.d.ts's HlsStatic constructor gained an optional HlsConfig parameter (only the four properties actually used) so this doesn't need an `as any`/unsafe cast to pass config through. Verified the property names are real and actually honored by hls.js 1.5.17 (the exact pinned CDN build), not silently ignored: constructed a real Hls instance with this config and read back hls.config.* - all four values came back as set, not as hls.js's own (tighter) defaults. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
4eab0323d6
commit
fff0b7a984
4 changed files with 83 additions and 3 deletions
12
client/src/global.d.ts
vendored
12
client/src/global.d.ts
vendored
|
|
@ -46,8 +46,18 @@ interface HlsInstance {
|
|||
destroy(): void;
|
||||
}
|
||||
|
||||
/** Only the loading-timeout knobs Player.ts/musicBar actually override -
|
||||
* see their own HLS_CONFIG comment for why the defaults are too tight
|
||||
* for this app's real transcode latency. */
|
||||
interface HlsConfig {
|
||||
manifestLoadingTimeOut?: number;
|
||||
manifestLoadingMaxRetry?: number;
|
||||
levelLoadingTimeOut?: number;
|
||||
fragLoadingTimeOut?: number;
|
||||
}
|
||||
|
||||
interface HlsStatic {
|
||||
new (): HlsInstance;
|
||||
new (config?: HlsConfig): HlsInstance;
|
||||
isSupported(): boolean;
|
||||
Events: { MANIFEST_PARSED: string; ERROR: string };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,19 @@ import type { MusicCurrentPublic, MusicPublicState, PartyPublicState, SocketMemb
|
|||
* for music parties.
|
||||
*/
|
||||
|
||||
// See views/player/Player.ts's own HLS_CONFIG for why these are raised
|
||||
// well above hls.js's defaults (10s manifest/level/frag load timeouts) -
|
||||
// every track here also goes through CatalogService.proxyStream into a
|
||||
// fresh Emby transcode, same latency risk as the video player. Duplicated
|
||||
// rather than shared - the two players' hls.js setup is already
|
||||
// independently duplicated for the same reason (see loadTrack below).
|
||||
const HLS_CONFIG: HlsConfig = {
|
||||
manifestLoadingTimeOut: 30000,
|
||||
manifestLoadingMaxRetry: 2,
|
||||
levelLoadingTimeOut: 30000,
|
||||
fragLoadingTimeOut: 60000
|
||||
};
|
||||
|
||||
let barEl: HTMLElement | null = null;
|
||||
let activePartyId: string | null = null;
|
||||
let conn: PartyConnection | null = null;
|
||||
|
|
@ -168,7 +181,7 @@ function loadTrack(track: MusicCurrentPublic, elapsedSeconds: number, isPaused:
|
|||
// skip vote is the actual recovery path, not a crashed bar.
|
||||
try {
|
||||
if (window.Hls && window.Hls.isSupported()) {
|
||||
hls = new window.Hls();
|
||||
hls = new window.Hls(HLS_CONFIG);
|
||||
hls.on(window.Hls.Events.MANIFEST_PARSED, resume);
|
||||
hls.on(window.Hls.Events.ERROR, (_event, data) => {
|
||||
if (data.fatal) console.error('[watchparty musicBar] HLS error', data);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,26 @@ const DRIFT_TOLERANCE_SECONDS = 1.5;
|
|||
const PAUSE_REQUEST_COOLDOWN_MS = 5000; // matches the server's own throttle in SocketGateway
|
||||
const PAUSE_REQUEST_HIGHLIGHT_MS = 4000; // how long the banner + the orange badge stay up
|
||||
|
||||
// hls.js's own defaults (10s manifest/level/frag load timeouts) assume a
|
||||
// file server or a source already being direct-played/remuxed - too tight
|
||||
// for this app, where every request chains through our own proxy into a
|
||||
// FRESH Emby transcode (see CatalogService.proxyStream/applyPlaybackOverrides).
|
||||
// Confirmed for real: picking a subtitle track forces SubtitleMethod=Encode
|
||||
// (a real re-encode, to actually burn the subtitle in - see
|
||||
// applyPlaybackOverrides's own comment), which measurably took Emby longer
|
||||
// than 10s to produce a first segment for, and hls.js gave up with a fatal
|
||||
// fragLoadTimeOut after its default 4 retries - not a hang, a real but slow
|
||||
// transcode start being mistaken for a dead server. Same config duplicated
|
||||
// in musicBar/MusicBar.ts (its own HLS_CONFIG) rather than shared - the two
|
||||
// players' hls.js setup is already independently duplicated for the same
|
||||
// reason (see that file's own loadTrack).
|
||||
const HLS_CONFIG: HlsConfig = {
|
||||
manifestLoadingTimeOut: 30000,
|
||||
manifestLoadingMaxRetry: 2,
|
||||
levelLoadingTimeOut: 30000,
|
||||
fragLoadingTimeOut: 60000
|
||||
};
|
||||
|
||||
// "Quality" is really "max transcoding bitrate", the same simplification
|
||||
// Emby's/Jellyfin's own web clients use - not true adaptive-bitrate
|
||||
// switching. See docs/architecture.md.
|
||||
|
|
@ -241,7 +261,7 @@ function renderVideoPlayer(root: HTMLElement, partyId: string, party: PartyPubli
|
|||
};
|
||||
|
||||
if (window.Hls && window.Hls.isSupported()) {
|
||||
hls = new window.Hls();
|
||||
hls = new window.Hls(HLS_CONFIG);
|
||||
hls.on(window.Hls.Events.MANIFEST_PARSED, resume);
|
||||
hls.on(window.Hls.Events.ERROR, (_event, data) => {
|
||||
if (!data.fatal) return;
|
||||
|
|
|
|||
|
|
@ -446,6 +446,43 @@ once this log line confirms `subtitleMethod=Encode` really was sent, the
|
|||
fault has been narrowed to Emby's own handling of that parameter rather
|
||||
than anything before it in this app.
|
||||
|
||||
**Round 3, after a real retest: the override reaches Emby correctly - it's
|
||||
just slow.** Not a repeat of Round 2's bug. Confirmed directly from the
|
||||
browser's own error report this time (`SubtitleMethod%3DEncode` present,
|
||||
URL-decoded, right there in the failing fragment's own request URL - even
|
||||
better evidence than the server-side stream log, since it's the literal
|
||||
resolved URL rather than what this app computed before sending it), so the
|
||||
override chain genuinely works end to end: Emby *is* being asked to burn
|
||||
the subtitle in, and does. The failure is downstream of that, in a place
|
||||
Round 1/2 had no reason to look at: `hls.js` is constructed with no config
|
||||
at all (`new Hls()`, defaults only), and its default fragment-load timeout
|
||||
is 10 seconds - fine for a source Emby can remux almost instantly, too
|
||||
tight for a source Emby has to fully re-encode from scratch, which
|
||||
`SubtitleMethod=Encode` always requires (see the "not a rendition" reasoning
|
||||
above - burning a subtitle in is fundamentally a heavier operation than
|
||||
picking an audio track or even changing quality, neither of which
|
||||
necessarily forces a re-encode the same way). hls.js retried 4 times, each
|
||||
timing out the same way, then gave up with a fatal `fragLoadTimeOut` -
|
||||
correctly reported by the existing error handling, just for a cause
|
||||
("Emby hasn't finished transcoding yet") this player had never actually
|
||||
hit before subtitles specifically forced a re-encode on what was probably,
|
||||
until now, mostly direct-play/remux traffic.
|
||||
|
||||
Fixed by giving both players' `Hls` instances explicit, longer timeouts
|
||||
(`manifestLoadingTimeOut`/`levelLoadingTimeOut`: 30s, `fragLoadingTimeOut`:
|
||||
60s - `views/player/Player.ts`'s `HLS_CONFIG`, duplicated in
|
||||
`musicBar/MusicBar.ts` for the same reason the rest of their hls.js setup
|
||||
already is) rather than only patching the video player: music tracks go
|
||||
through the exact same `PlaybackInfo` → transcode pipeline (see "Music:
|
||||
participative playlist" above), so a slow-to-start transcode is a latent
|
||||
risk there too, quality changes included, even though only video actually
|
||||
has subtitles to trigger it today. Verified the config values are actually
|
||||
honored, not silently ignored by a wrong property name, by constructing a
|
||||
real `Hls` instance from the exact pinned CDN build
|
||||
(`hls.js/1.5.17/hls.min.js`) with this config and reading back
|
||||
`hls.config.fragLoadingTimeOut` etc. - all four came back as set, not as
|
||||
hls.js's own defaults.
|
||||
|
||||
## Assumptions to verify
|
||||
|
||||
**Confirmed against the real server** (not a guess — actually exercised,
|
||||
|
|
|
|||
Loading…
Reference in a new issue