refactor(player): E.6 — extract deriveNormalizationSnapshot into module (#569)

`deriveNormalizationSnapshot` — the loudness / replaygain / off branch
that the runtime uses to compute the normalization fields on every track
switch and queue rewrite — moves into src/store/normalizationSnapshot.ts.
File-private, four internal call sites updated to import from the new
module. Adds 8 focused tests pinning each branch (off, loudness with
target LUFS, replaygain enabled with tag + pre-gain, replaygain enabled
with fallback) plus neighbour-track context for album-mode resolution.

playerStore 3470 → 3434 LOC.
This commit is contained in:
Frank Stellmacher
2026-05-12 12:21:51 +02:00
committed by GitHub
parent b68bddd034
commit 81b161a418
3 changed files with 189 additions and 37 deletions
+1 -37
View File
@@ -44,6 +44,7 @@ import {
recordEnginePlayUrl,
shouldRebindPlaybackToHotCache,
} from './playbackUrlRouting';
import { deriveNormalizationSnapshot } from './normalizationSnapshot';
// Re-export the playback-progress public surface so existing call sites
// (PlayerBar, FullscreenPlayer, WaveformSeek, LyricsPane, MobilePlayerView,
@@ -432,43 +433,6 @@ function emitNormalizationDebug(step: string, details?: Record<string, unknown>)
}).catch(() => {});
}
function deriveNormalizationSnapshot(
track: Track,
queue: Track[],
queueIndex: number,
): Pick<
PlayerState,
'normalizationNowDb' | 'normalizationTargetLufs' | 'normalizationEngineLive'
> {
const auth = useAuthStore.getState();
const engine = auth.normalizationEngine;
if (engine === 'loudness') {
const target = auth.loudnessTargetLufs;
return {
// Clears stale UI until `audio:normalization-state` / refresh catches up.
normalizationNowDb: null,
normalizationTargetLufs: target,
normalizationEngineLive: 'loudness',
};
}
if (engine === 'replaygain' && auth.replayGainEnabled) {
const prev = queueIndex > 0 ? queue[queueIndex - 1] : null;
const next = queueIndex + 1 < queue.length ? queue[queueIndex + 1] : null;
const resolved = resolveReplayGainDb(track, prev, next, true, auth.replayGainMode);
const nowDb = resolved != null ? (resolved + auth.replayGainPreGainDb) : auth.replayGainFallbackDb;
return {
normalizationNowDb: nowDb,
normalizationTargetLufs: null,
normalizationEngineLive: 'replaygain',
};
}
return {
normalizationNowDb: null,
normalizationTargetLufs: null,
normalizationEngineLive: 'off',
};
}
// Debounce timer for seek slider drags.
let seekDebounce: ReturnType<typeof setTimeout> | null = null;
// Target time of the last seek — blocks stale Rust progress ticks until the