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:
@@ -1,6 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { DEFAULT_VOLUME, normalizeVolume, stepVolume } from "./playerVolume";
|
||||
import {
|
||||
DEFAULT_VOLUME,
|
||||
PLEX_SCALE,
|
||||
YT_SCALE,
|
||||
normalizeVolume,
|
||||
normalizeVolumeIn,
|
||||
stepVolume,
|
||||
stepVolumeIn,
|
||||
} from "./playerVolume";
|
||||
|
||||
describe("normalizeVolume", () => {
|
||||
it("keeps a valid level", () => {
|
||||
@@ -56,3 +64,34 @@ describe("stepVolume", () => {
|
||||
expect(stepVolume(NaN, { delta: -5, fallback: 90 })).toBe(85);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scale-generic core (shared by both players)", () => {
|
||||
it("keeps 0..1 floats WITHOUT rounding on the Plex scale", () => {
|
||||
// PLEX_SCALE.round is false — the HTML5 <video> takes a float, so 0.37 must stay 0.37 (the YT
|
||||
// scale would round it to a whole percent).
|
||||
expect(normalizeVolumeIn(0.37, undefined, PLEX_SCALE)).toBeCloseTo(0.37, 10);
|
||||
expect(normalizeVolumeIn(1.5, undefined, PLEX_SCALE)).toBe(1); // clamps to max
|
||||
expect(normalizeVolumeIn(-0.2, undefined, PLEX_SCALE)).toBe(0); // clamps to min
|
||||
});
|
||||
|
||||
it("falls back to the scale default on a corrupt Plex volume instead of muting", () => {
|
||||
// The Plex-side latent bug this consolidation fixes: a bare clamp turned a corrupt persisted
|
||||
// volume (null / "0.5" / NaN) into 0 — silently muting — where the shared core falls back to the
|
||||
// last-known level, else PLEX_SCALE.default (1).
|
||||
expect(normalizeVolumeIn(null, undefined, PLEX_SCALE)).toBe(PLEX_SCALE.default);
|
||||
expect(normalizeVolumeIn("0.5", 0.3, PLEX_SCALE)).toBeCloseTo(0.3, 10);
|
||||
expect(normalizeVolumeIn(NaN, NaN, PLEX_SCALE)).toBe(PLEX_SCALE.default);
|
||||
});
|
||||
|
||||
it("steps and clamps on the Plex scale", () => {
|
||||
expect(stepVolumeIn(0.5, { delta: 0.05, fallback: 0.5 }, PLEX_SCALE)).toBeCloseTo(0.55, 10);
|
||||
expect(stepVolumeIn(0.98, { delta: 0.05, fallback: 1 }, PLEX_SCALE)).toBe(1); // clamps at max
|
||||
expect(stepVolumeIn(0.03, { delta: -0.05, fallback: 1 }, PLEX_SCALE)).toBe(0); // clamps at min
|
||||
});
|
||||
|
||||
it("still rounds to whole percents on the YouTube scale via the generic core", () => {
|
||||
expect(normalizeVolumeIn(42.6, undefined, YT_SCALE)).toBe(43);
|
||||
expect(YT_SCALE.round).toBe(true);
|
||||
expect(PLEX_SCALE.round).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user