fix(r5): address the fourth /code-review high round — 6 findings
- Feed memoizes onClose (like Playlists already did): the inline closure re-ran
PlayerModal's keydown effect on every Feed render, which since the debounce
landed also flushed the volume write and re-stole focus
- the player reads its volume with readAccountMerged again — a partial stored
object made the level undefined -> NaN -> a persisted null; applyVolume also
refuses a non-finite level outright
- that read moved into a lazy useState initializer (was running per render)
- the two RSS except branches share one tail, so a counter can't drift again
- run_ffmpeg tests cover the SIGTERM->SIGKILL escalation and the lingering-
process path; dropped an assertion that only restated the fixture
- storage.ts documents the actual { volume } shape (0 IS muted)
This commit is contained in:
@@ -35,7 +35,7 @@ import {
|
||||
} from "../lib/format";
|
||||
import { renderDescription } from "../lib/descriptionLinks";
|
||||
import { useBackToClose } from "../lib/history";
|
||||
import { LS, readAccount, writeAccount } from "../lib/storage";
|
||||
import { LS, readAccountMerged, writeAccount } from "../lib/storage";
|
||||
import { useScrollFade } from "../lib/useScrollFade";
|
||||
|
||||
// Experiment (branch experiment/inline-player): play the video in-app via the
|
||||
@@ -188,7 +188,14 @@ export default function PlayerModal({
|
||||
// (the on-player flash has its own `volumeUi` state), the once-bound wheel/key listeners need a
|
||||
// ref anyway, and the hook's write lives inside a `setState` updater — which is both a second
|
||||
// write on every settle and not something React must run while the component is unmounting.
|
||||
const volumeRef = useRef(readAccount(LS.ytPlayerPrefs, { volume: 100 }).volume);
|
||||
// `readAccountMerged` (not readAccount) so a partial/older stored object still gets the default
|
||||
// — a missing `volume` would otherwise travel as undefined into the arithmetic below. Read via
|
||||
// a lazy `useState` initializer so it happens once, not on every render (the house pattern —
|
||||
// see the frozen queue above).
|
||||
const [storedVolume] = useState(
|
||||
() => readAccountMerged(LS.ytPlayerPrefs, { volume: 100 }).volume,
|
||||
);
|
||||
const volumeRef = useRef(storedVolume);
|
||||
const volSaveTimerRef = useRef<number | undefined>(undefined);
|
||||
// When the last volume gesture happened — see nudgeVolume (a spin must not re-read the player).
|
||||
const lastNudgeAtRef = useRef(0);
|
||||
@@ -221,7 +228,9 @@ export default function PlayerModal({
|
||||
/** Push a level (0–100) to the player and remember it; 0 also mutes (a player left at zero
|
||||
* volume but unmuted shows YouTube's speaker icon as ON, which reads as broken). */
|
||||
const applyVolume = (level: number, flash = true) => {
|
||||
const next = Math.max(0, Math.min(100, Math.round(level)));
|
||||
// A non-finite level (a corrupt stored value, a player that answered NaN) must not become a
|
||||
// clamped NaN: that would mute the player AND persist `null`, poisoning every later session.
|
||||
const next = Number.isFinite(level) ? Math.max(0, Math.min(100, Math.round(level))) : 100;
|
||||
const p = playerRef.current;
|
||||
if (p && typeof p.setVolume === "function") {
|
||||
if (next > 0) p.unMute?.();
|
||||
|
||||
Reference in New Issue
Block a user