mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
fix(playback): harden external-subtitle handling from PR #1471 review
Addresses the confirmed code-review findings: - Deselect the hls.js engine track BEFORE activating an external subtitle track: hls.js reacts to subtitleTrack=-1 by disabling every subtitle-kind TextTrack, which undid the just-selected external track. - Track ownership of every TextTrack the session ever created (addTextTrack tracks cannot leave the element), so stale or attach-failed tracks stay excluded from the native enumeration instead of reappearing as ghost engine tracks after source changes; failed attaches are silenced and their partial cues removed. - Guard the file pick with a source generation so a pick that outlives a stream change (Up Next, zapping, failover) is discarded instead of attaching the previous stream's subtitles to the next one. - Decode picked files encoding-aware (UTF-16 BOMs, strict UTF-8, then a Windows-1251/1252 heuristic) instead of Blob.text()'s silent UTF-8 substitution that rendered legacy-encoded SRT files as mojibake. - Gate the delay row on an external track being SELECTED, not merely loaded, so it can no longer sit enabled while visually inert. - Keep real (possibly negative) cue times under a negative delay instead of clamping pre-roll cues into a simultaneous stack at t=0. - Fix subtitleDelayLabel returning a signed negative zero for sub-tenth values. - Hoist the canonical PlayerSubtitleStyle shape plus clamp/normalize rules into @iptvnator/shared/interfaces (subtitle-style.util.ts); the renderer and the Electron main process now share one implementation, removing the triplicated literals and the toLowerCase divergence. Adds regression coverage for each fix; updates the player-controls contract doc and CLAUDE.md accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
404deb92b0
commit
10bd5acd6f
16 files changed
+481
-119
No files matched your search
@@ -618,23 +618,44 @@ as the shared `volume` key, and is normalized/clamped on every read and write.
|
||||
The delay and any loaded file are deliberately per-session/per-source — they
|
||||
correct one specific stream.
|
||||
|
||||
The canonical `PlayerSubtitleStyle` shape and the clamp/normalize rules
|
||||
(delay limit, size bounds, color validation) live in
|
||||
`@iptvnator/shared/interfaces` (`subtitle-style.util.ts`). The renderer
|
||||
applies them to user input and the Electron main process re-applies the exact
|
||||
same implementation to untrusted IPC payloads — deliberate defense-in-depth
|
||||
with a single source of truth, so widening a limit on one side cannot
|
||||
silently re-clamp on the other.
|
||||
|
||||
Per-engine implementations:
|
||||
|
||||
- **HTML5 + ArtPlayer (shared-controls mode, neutral source bridge).** The
|
||||
picker is a renderer-side DOM file input (`.srt`/`.vtt` only; works in the
|
||||
PWA and Electron alike, and no filesystem path ever enters the app).
|
||||
`WebVideoExternalSubtitles` parses the file
|
||||
(`external-subtitle-cues.util.ts`) and renders it through a native
|
||||
PWA and Electron alike, and no filesystem path ever enters the app). File
|
||||
bytes are decoded encoding-aware (`decodeExternalSubtitleBytes`: UTF-16
|
||||
BOMs, strict UTF-8, then a Windows-1251/1252 heuristic keyed on high-byte
|
||||
density), because `Blob.text()`'s silent UTF-8 substitution turns common
|
||||
legacy-encoded SRT files into mojibake. `WebVideoExternalSubtitles` parses
|
||||
the file (`external-subtitle-cues.util.ts`) and renders it through a native
|
||||
`TextTrack` on the video element, so it works under every source kind. The
|
||||
native track enumeration excludes externally owned tracks, and
|
||||
`WebVideoSourceTracks` merges them into the subtitle listing with IDs from
|
||||
100000 up, routing selection so exactly one owner (engine or external) is
|
||||
active. The delay capability is runtime-gated on a loaded file: only owned
|
||||
cues can be re-timed exactly, while engine/stream cues arrive incrementally
|
||||
and are left untouched. Style applies through a scoped `::cue` rule
|
||||
(`WebVideoSubtitleStyle`), which covers embedded, hls.js-managed, and
|
||||
external native cues. ASS rendering would need libass and is out of scope
|
||||
for the web engines.
|
||||
native track enumeration excludes externally owned tracks — ownership is
|
||||
tracked for every track the session EVER created, because `addTextTrack`
|
||||
tracks cannot leave the element and per-source ownership would let stale or
|
||||
attach-failed tracks reappear as ghost engine tracks. `WebVideoSourceTracks`
|
||||
merges external tracks into the subtitle listing with IDs from 100000 up,
|
||||
routing selection so exactly one owner (engine or external) is active;
|
||||
external selection deselects the engine BEFORE setting track modes, since
|
||||
hls.js reacts to `subtitleTrack = -1` by disabling every subtitle-kind
|
||||
`TextTrack` on the element. A pick captures the source generation and is
|
||||
discarded if the stream changed while the dialog was open (mirroring the
|
||||
Embedded MPV runner's session recheck). The delay capability is
|
||||
runtime-gated on an external track being the SELECTED one — only owned cues
|
||||
can be re-timed exactly, and with an engine track active the row would be
|
||||
enabled yet visually inert. Negatively shifted cues keep their real
|
||||
(possibly negative) times, which are valid and simply never active;
|
||||
clamping them to t≈0 would stack every pre-roll cue at playback start.
|
||||
Style applies through a scoped `::cue` rule (`WebVideoSubtitleStyle`),
|
||||
which covers embedded, hls.js-managed, and external native cues. ASS
|
||||
rendering would need libass and is out of scope for the web engines.
|
||||
- **Embedded MPV frame-copy.** The helper protocol gained `sub-add`,
|
||||
`sub-delay`, `sub-scale`, and `sub-color` commands. The picker is a
|
||||
main-process open dialog (`.srt/.ass/.ssa/.vtt/.sub` — mpv renders ASS
|
||||
|
||||
Reference in new issue
Block a user