From 75093458c90c2eb39f159bedf69d34c3d2026db1 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Thu, 8 Oct 2026 02:59:13 +0200 Subject: [PATCH] feat(plex): subtitles rise above the control bar while it shows On an iPad the bar covered the text whenever it was up: the browser places a native cue by its line, which nothing animates. The track now stays loaded as hidden and PlexSubtitles draws the active cues, measured against the bar and lifted above it in 200 ms, back down when it hides. The viewer's size, colour, background and outline settings style the text directly. Also fixes a test typing slip from the search pill change. --- docs/DESIGN.md | 1 + frontend/src/components/PlexPlayer.tsx | 27 +-- .../src/components/PlexSubtitles.dom.test.tsx | 109 ++++++++++++ frontend/src/components/PlexSubtitles.tsx | 157 ++++++++++++++++++ .../src/components/SearchBar.dom.test.tsx | 9 +- .../lib/plexPlayerHooks.subtitle.dom.test.tsx | 35 +++- frontend/src/lib/plexPlayerHooks.ts | 45 +---- frontend/src/lib/plexPlayerPrefs.ts | 8 +- 8 files changed, 333 insertions(+), 58 deletions(-) create mode 100644 frontend/src/components/PlexSubtitles.dom.test.tsx create mode 100644 frontend/src/components/PlexSubtitles.tsx diff --git a/docs/DESIGN.md b/docs/DESIGN.md index df11803a..486a228e 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -713,3 +713,4 @@ of a full-width panel and nothing on the right. | 2026-10-06 | `player` focus | A dialog that is a player opens focused on its panel (the YouTube player's rule, now `Modal`'s too); on the media element itself, Space and the arrows stay the element's own | `close`, or the media element | On the ✕ Space closed the player; on the element the browser's own controls acted on the same key as the player's (one Space played and paused, measured in Chrome) | | 2026-10-08 | Captions on touch | A `Tooltip` opens for a mouse and a key's focus only, never for a finger | A caption on tap; a long-press caption | On an iPad the tap that showed the caption was taken as a hover and lost its click (every rail button took two taps), and no finger sends the mouseleave that hides it; the rail's labels setting names the buttons on touch | | 2026-10-08 | Header search on touch | No key cap on a touch screen; the pill grows to 52 px so a ring shows round the 44 px Go; Go drops to its icon by the column's width | Keep the key cap; a 44 px Go overhanging a 40 px pill | On a tablet the 44 px key cap and Go sat on the pill's edge and cut the placeholder; an overhanging Go breaks the nested radii | +| 2026-10-08 | Plex subtitles and the control bar | The player draws the cues itself; while the bar shows they rise above it (200 ms), then settle back | The browser's own cues, moved by `line` | On an iPad the bar covered the text; a native cue cannot be animated or measured, and our layer behaves the same on every engine | diff --git a/frontend/src/components/PlexPlayer.tsx b/frontend/src/components/PlexPlayer.tsx index 1c84e3b9..3ec37ba0 100644 --- a/frontend/src/components/PlexPlayer.tsx +++ b/frontend/src/components/PlexPlayer.tsx @@ -45,7 +45,6 @@ import { DEFAULT_PREFS, fmt, subOrdForLang, - subShadowCss, type PlexPlayerPrefs, } from "../lib/plexPlayerPrefs"; import { Ctrl, PanelHead } from "./plexPlayerControls"; @@ -84,6 +83,7 @@ import { LS, useAccountPersistedObject } from "../lib/storage"; import { useDismiss } from "../lib/useDismiss"; import { useScrollFade } from "../lib/useScrollFade"; import { useBackToClose } from "../lib/history"; +import PlexSubtitles from "./PlexSubtitles"; import { lockBodyScroll, trapTab } from "./ui/dialogFocus"; import { pushLayer, @@ -150,6 +150,8 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } const videoRef = useRef(null); const wrapRef = useRef(null); + // The bottom band, which the subtitles keep above while it shows. + const bottomBarRef = useRef(null); // A control that goes while it holds the focus — Next onto the last item or a series finale — // hands it to its neighbour instead of . The player is portaled outside the // page scroller, whose keeper does not watch it. @@ -418,7 +420,6 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } detail, sessionK, subOffset: prefs.subOffset, - subPos: prefs.subPos, restartSession, setAudioTrack: (track: number) => engine.setAudioTrack(track), patchPrefs, @@ -1130,12 +1131,6 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false } }), }} > - {/* Per-user subtitle appearance (font size / colour / background). ::cue is global, but this - player owns the only