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
@@ -440,9 +440,10 @@ describe("usePlexTracks — the debounced A/V-offset restart", () => {
// The browser no longer draws the cues: its rendering sat under the control bar on an iPad, and a cue
// placed by `line` cannot move out of the way. The track stays loaded, as `hidden`, for PlexSubtitles.
describe("usePlexTracks — who draws the cues", () => {
/** A <video> whose text-track list fires `addtrack` / `change` like a browser's. */
/** A <video> whose text-track list fires `addtrack` / `change` like a browser's. Each mode is a
* track of the player's own <track>; `mediaAdds` adds one from the media itself instead. */
function videoWithTracks(...modes: TextTrackMode[]) {
const list: { mode: TextTrackMode }[] = modes.map((mode) => ({ mode }));
const list: { kind: string; mode: TextTrackMode }[] = [];
const tracks = Object.assign(new EventTarget(), {
get length() {
return list.length;
@@ -451,10 +452,20 @@ describe("usePlexTracks — who draws the cues", () => {
});
const video = document.createElement("video");
Object.defineProperty(video, "textTracks", { value: tracks });
const add = (mode: TextTrackMode, own: boolean) => {
const track = { kind: "subtitles", mode };
if (own) {
const el = document.createElement("track");
Object.defineProperty(el, "track", { value: track });
video.appendChild(el);
}
list.push(track);
};
for (const mode of modes) add(mode, true);
// What a browser does with a freshly mounted `default` <track>: it switches it on by itself.
const browserAddsShowing = () =>
act(() => {
list.push({ mode: "showing" });
add("showing", true);
tracks.dispatchEvent(new Event("addtrack"));
});
const browserShows = () =>
@@ -462,7 +473,13 @@ describe("usePlexTracks — who draws the cues", () => {
list[0]!.mode = "showing";
tracks.dispatchEvent(new Event("change"));
});
return { video, list, browserAddsShowing, browserShows };
// A direct-played file's own subtitle stream, which WebKit lists — and may switch on.
const mediaAdds = (mode: TextTrackMode) =>
act(() => {
add(mode, false);
tracks.dispatchEvent(new Event("addtrack"));
});
return { video, list, browserAddsShowing, browserShows, mediaAdds };
}
function mountOn(video: HTMLVideoElement) {
@@ -504,4 +521,13 @@ describe("usePlexTracks — who draws the cues", () => {
expect(list[0]!.mode).toBe("hidden");
unmount();
});
it("switches off a subtitle track that is not the player's own, and leaves its own hidden", () => {
const { video, list, mediaAdds } = videoWithTracks("disabled");
const { unmount } = mountOn(video);
mediaAdds("hidden");
mediaAdds("showing");
expect(list.map((t) => t.mode)).toEqual(["hidden", "disabled", "disabled"]);
unmount();
});
});