refactor(player): E.12 — extract skip-to-1★ helper (#575)

`applySkipStarOnManualNext` — the helper that records a manual `next()`
into the per-track skip counter and auto-rates the track 1★ once the
configured threshold crosses — moves into `src/store/skipStarRating.ts`.
File-private with one call site; no caller-side changes outside
playerStore's own import.

10 focused tests pin each early-return branch (manual=false, null
track, threshold not crossed, recordSkipStarManualAdvance returning
null, already-rated via override / queue / passed-track) plus the
happy path that calls setRating(1) + the state update for the queue,
currentTrack, and override map. Promise rejections are verified to
be swallowed.

playerStore 3291 → 3263 LOC.
This commit is contained in:
Frank Stellmacher
2026-05-12 14:22:38 +02:00
committed by GitHub
parent ddead24678
commit 85df3e42c1
3 changed files with 170 additions and 29 deletions
+1 -29
View File
@@ -74,6 +74,7 @@ import {
getWaveformRefreshGen,
} from './waveformRefreshGen';
import { touchHotCacheOnPlayback } from './hotCacheTouch';
import { applySkipStarOnManualNext } from './skipStarRating';
// Re-export the playback-progress public surface so existing call sites
// (PlayerBar, FullscreenPlayer, WaveformSeek, LyricsPane, MobilePlayerView,
@@ -533,35 +534,6 @@ function scheduleSeekFallbackRetry(trackId: string, seconds: number) {
// Guard against rapid double-click play/pause sending two state transitions
// to the Rust backend before it has finished the previous one.
let togglePlayLock = false;
/**
* Skip → 1★: counts in `authStore.skipStarManualSkipCountsByKey` (persisted).
* Only user-initiated `next()` increments. Natural track end (incl. gapless) clears the count;
* threshold reached clears count and sets 1★ if still unrated.
*/
function applySkipStarOnManualNext(skippedTrack: Track | null, manual: boolean): void {
if (!manual || !skippedTrack) return;
const id = skippedTrack.id;
const adv = useAuthStore.getState().recordSkipStarManualAdvance(id);
if (!adv?.crossedThreshold) return;
const live = usePlayerStore.getState();
const fromQueue = live.queue.find(t => t.id === id);
const cur =
live.userRatingOverrides[id] ??
fromQueue?.userRating ??
skippedTrack.userRating ??
0;
if (cur >= 1) return;
setRating(id, 1)
.then(() => {
usePlayerStore.setState(s => ({
queue: s.queue.map(t => (t.id === id ? { ...t, userRating: 1 } : t)),
currentTrack: s.currentTrack?.id === id ? { ...s.currentTrack, userRating: 1 } : s.currentTrack,
userRatingOverrides: { ...s.userRatingOverrides, [id]: 1 },
}));
})
.catch(() => {});
}
// ── HTML5 Radio Player ────────────────────────────────────────────────────────
// Internet radio streams are played via a native <audio> element instead of
// the Rust/Symphonia engine. This gives us browser-native reconnect logic,