fix(plex): subtitles draw only the player's own track, lifted in the player's own pixels

A direct-played file's own subtitle stream (WebKit lists an mp4's mov_text) was switched to hidden and drawn under any choice, Off included: only the player's <track> is kept hidden and read, other subtitle tracks are switched off. The lift was measured in screen pixels and applied inside the x1.25 windowed player, and re-measured off a box carrying its own (or a mid-slide) transform: an untransformed outer box is measured, the distance divided by the scale, and a cue that just appeared is placed, not slid from where the last one was left.
This commit is contained in:
2026-10-08 21:18:48 +02:00
parent 57a81c5643
commit fadb4f17a9
5 changed files with 191 additions and 62 deletions
+22 -14
View File
@@ -18,6 +18,7 @@ import { qk } from "./queryKeys";
import { useLatest } from "./useLatest";
import i18n, { plexLang } from "../i18n";
import { markerSkip } from "./plexMarkers";
import { ownTextTracks } from "./plexSubTracks";
import { type PlexPlayerPrefs } from "./plexPlayerPrefs";
import { PLEX_SCALE, normalizeVolumeIn, stepVolumeIn } from "./playerVolume";
@@ -852,23 +853,30 @@ export function usePlexTracks(opts: {
// sessionK is already lag-compensated (see HlsEngine), so it is the true content-zero the VTT
// must be shifted by; the user offset nudges on top.
const subTrackOffset = sessionK - subOffApplied;
// Keep every track "hidden": loaded and its cues tracked, but drawn by PlexSubtitles, not by the
// browser. Not once per selection: the <track> is remounted on its own key (a grown window, a
// retry), and the browser switches a fresh `default` track to "showing" a task later — which drew
// the cues a second time, natively, under ours. So it is re-applied on every track the list gains
// and on every mode change the browser makes.
// Keep our own track "hidden": loaded and its cues tracked, but drawn by PlexSubtitles, not by the
// browser. Any other subtitle track (a direct-played file's own, see ownTextTracks) is switched
// off, so neither the browser nor PlexSubtitles draws it. Not once per selection: the <track> is
// remounted on its own key (a grown window, a retry), and the browser switches a fresh `default`
// track to "showing" a task later — which drew the cues a second time, natively, under ours. So it
// is re-applied on every track the list gains and on every mode change the browser makes.
useEffect(() => {
const tracks = videoRef.current?.textTracks;
if (!tracks) return;
const hide = () => {
for (const tr of Array.from(tracks)) if (tr.mode !== "hidden") tr.mode = "hidden";
const video = videoRef.current;
if (!video) return;
const tracks = video.textTracks;
const settle = () => {
const own = ownTextTracks(video);
for (const tr of Array.from(tracks)) {
const subtitle = tr.kind === "subtitles" || tr.kind === "captions";
const want = own.has(tr) ? "hidden" : subtitle ? "disabled" : tr.mode;
if (tr.mode !== want) tr.mode = want;
}
};
hide();
tracks.addEventListener("addtrack", hide);
tracks.addEventListener("change", hide);
settle();
tracks.addEventListener("addtrack", settle);
tracks.addEventListener("change", settle);
return () => {
tracks.removeEventListener("addtrack", hide);
tracks.removeEventListener("change", hide);
tracks.removeEventListener("addtrack", settle);
tracks.removeEventListener("change", settle);
};
}, [subOrd, id, videoRef]);