refactor(player): share the volume core across both players + extract PlexPlayerSettings (R12 S5 frontend)
Volume-consolidation (conservative — no behaviour or prefs-format change): - playerVolume.ts is now scale-generic (a VolumeScale parameterises range / default / integer rounding). YT_SCALE (0-100 ints) and PLEX_SCALE (0..1 float) bind the same tested core; the YouTube wrappers keep the original API so the existing tests are unchanged. - The Plex usePlayerVolume hook drops its bespoke clamp01 arithmetic for the shared normalizeVolumeIn / stepVolumeIn at PLEX_SCALE. Side effect (a latent bug fix): a corrupt persisted volume now falls back to the default instead of a bare clamp silently muting it. The separate .muted flag stays Plex-only. - clamp01 stays for the seek-bar fraction (its non-volume use). - Tests: the generic core is covered at both scales (float no-round, corrupt -> default, step/clamp) — the Plex-side arithmetic finally has unit coverage. PlexPlayerSettings extraction (verbatim JSX move, behaviour-equivalent): - The ~240-line gear/tuning panel (sync / playback / subtitle / clock tabs) moves out of PlexPlayer into PlexPlayerSettings.tsx (a pure prefs+patchPrefs view owning no state). PlexPlayer shrinks 1402 -> 1173 lines; Slider/Toggle imports it no longer uses are dropped. Gate green (tsc / eslint-0 / prettier / knip / vitest 83 / pytest 234). Dev server smoke: app builds + loads clean, no console errors. The interactive settings panel needs a playing Plex video (unreachable on localdev) -> owed to the user's UAT.
This commit is contained in:
@@ -17,7 +17,8 @@ import {
|
||||
} from "./fullscreen";
|
||||
import { api, type PlexItemDetail, type PlexMarker } from "./api";
|
||||
import { qk } from "./queryKeys";
|
||||
import { clamp01, type PlexPlayerPrefs } from "./plexPlayerPrefs";
|
||||
import { type PlexPlayerPrefs } from "./plexPlayerPrefs";
|
||||
import { PLEX_SCALE, normalizeVolumeIn, stepVolumeIn } from "./playerVolume";
|
||||
|
||||
// A live wall clock for the top bar, re-rendered every 15s (minute resolution is enough for the
|
||||
// HH:MM display, and a coarse tick keeps the whole player from re-rendering each second).
|
||||
@@ -138,9 +139,10 @@ export function usePlexProgress(opts: {
|
||||
|
||||
// Volume/mute for the Plex player's HTML5 <video> (0..1 float, independent .muted flag). Persists to
|
||||
// the per-account prefs (survives F5) and re-applies on each fresh session (the element resets to its
|
||||
// default volume after a load/seek-restart). NOTE: this is the Plex-side model; the YouTube player has
|
||||
// its own 0-100 / no-mute-flag arithmetic in playerVolume.ts. Sharing the tested pure core across both
|
||||
// is a separate R12 consolidation sprint — this hook is the seam it will rework internally.
|
||||
// default volume after a load/seek-restart). The 0..1 arithmetic is the shared, unit-tested core in
|
||||
// playerVolume.ts bound to PLEX_SCALE (the YouTube player binds the same core to YT_SCALE, 0–100); the
|
||||
// separate .muted flag stays here since it's Plex-only. Using normalizeVolumeIn (not a bare clamp)
|
||||
// also means a corrupt persisted volume falls back to the default instead of silently muting.
|
||||
export function usePlayerVolume(opts: {
|
||||
videoRef: RefObject<HTMLVideoElement | null>;
|
||||
volume: number;
|
||||
@@ -160,7 +162,7 @@ export function usePlayerVolume(opts: {
|
||||
(vol: number, mute: boolean) => {
|
||||
const v = videoRef.current;
|
||||
if (!v) return;
|
||||
v.volume = clamp01(vol);
|
||||
v.volume = normalizeVolumeIn(vol, undefined, PLEX_SCALE);
|
||||
v.muted = mute;
|
||||
},
|
||||
[videoRef],
|
||||
@@ -169,12 +171,15 @@ export function usePlayerVolume(opts: {
|
||||
const setVol = useCallback(
|
||||
(vol: number, mute: boolean) => {
|
||||
applyVolume(vol, mute);
|
||||
patchPrefs({ volume: clamp01(vol), muted: mute });
|
||||
patchPrefs({ volume: normalizeVolumeIn(vol, undefined, PLEX_SCALE), muted: mute });
|
||||
},
|
||||
[applyVolume, patchPrefs],
|
||||
);
|
||||
const nudgeVolume = useCallback(
|
||||
(d: number) => setVol(clamp01(volume + d), clamp01(volume + d) === 0),
|
||||
(d: number) => {
|
||||
const next = stepVolumeIn(volume, { delta: d, fallback: volume }, PLEX_SCALE);
|
||||
setVol(next, next === 0);
|
||||
},
|
||||
[volume, setVol],
|
||||
);
|
||||
const toggleMute = useCallback(() => setVol(volume, !muted), [volume, muted, setVol]);
|
||||
|
||||
Reference in New Issue
Block a user