Files
psysonic/src/hooks/useFsDynamicAccent.ts
T
Frank Stellmacher 463d3e0c5b refactor(fullscreen-player): H5 — split FullscreenPlayer.tsx 911 → 228 LOC across 11 files (#668)
* refactor(fullscreen-player): H5 — extract FsLyricsApple + FsLyricsRail + useWordLyricsSync

The two lyrics views become own files under components/fullscreenPlayer/.
Their identical word-sync imperative DOM-update useEffect (only differing in
the .fsa-/.fsr- class prefix) collapses into the shared useWordLyricsSync
hook with a classPrefix arg.

FullscreenPlayer.tsx: 911 → 613 LOC.

* refactor(fullscreen-player): H5 — extract FsArt + FsPortrait + FsSeekbar

The three visual subcomponents move into own files. formatTime moves into
utils/fullscreenPlayerHelpers.ts so FsSeekbar keeps using it.

FullscreenPlayer.tsx: 613 → 421 LOC.

* refactor(fullscreen-player): H5 — extract FsLyricsMenu + FsPlayBtn

The lyrics-settings popover and the isolated play/pause button move into
own files. FullscreenPlayer drops the now-unused lucide-react icons
(Play/Pause/Moon/Sunrise/Music) and the PlaybackDelayModal +
PlaybackScheduleBadge imports — both used only by FsPlayBtn now.

FullscreenPlayer.tsx: 421 → 304 LOC.

* refactor(fullscreen-player): H5 — extract useFsDynamicAccent + useFsArtistPortrait + useFsIdleFade

Pulls three state+effect islands out of FullscreenPlayer:
  - useFsDynamicAccent owns the cover-blob fetch + extractCoverColors call
    plus the module-level artKey → accent cache that makes same-album song
    switches instant.
  - useFsArtistPortrait fetches getArtistInfo().largeImageUrl for the right-
    side portrait, returning '' until resolved (or when no artistId).
  - useFsIdleFade flips isIdle true after 3 s of inactivity, exposes a
    throttled mousemove handler, and binds Escape to the provided callback.

FullscreenPlayer.tsx: 304 → 228 LOC.
2026-05-13 22:52:08 +02:00

44 lines
1.7 KiB
TypeScript

import { useEffect, useState } from 'react';
import { extractCoverColors } from '../utils/dynamicColors';
// Module-level cache: artKey → accent color string.
// Survives track changes so same-album songs reuse the extracted color instantly.
const coverAccentCache = new Map<string, string>();
/** Extract a dominant accent color from the current cover art and cache it by
* artKey. Cache hits return instantly; cache misses fetch the cover blob,
* run extractCoverColors, then cache + apply the result. Keeps the previous
* color visible until extraction completes so the UI doesn't flash to default. */
export function useFsDynamicAccent(artUrl: string, artKey: string): string | null {
const [dynamicAccent, setDynamicAccent] = useState<string | null>(null);
useEffect(() => {
if (!artKey || !artUrl) { setDynamicAccent(null); return; }
const cached = coverAccentCache.get(artKey);
if (cached) { setDynamicAccent(cached); return; }
let cancelled = false;
let blobUrl = '';
(async () => {
try {
const resp = await fetch(artUrl);
if (cancelled) return;
const blob = await resp.blob();
if (cancelled) return;
blobUrl = URL.createObjectURL(blob);
const colors = await extractCoverColors(blobUrl);
if (cancelled) return;
if (colors.accent) {
coverAccentCache.set(artKey, colors.accent);
setDynamicAccent(colors.accent);
}
} catch { /* ignore */ } finally {
if (blobUrl) URL.revokeObjectURL(blobUrl);
}
})();
return () => { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [artKey]);
return dynamicAccent;
}