mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fix(home): sync Mainstage hero backdrop with album on fast nav (#1021)
* fix(home): sync Mainstage hero backdrop with album on fast nav Bind hero background URL to the current slide and drop stale crossfade layers so rapid carousel clicks no longer show the previous album's art. * docs(changelog): note Mainstage hero backdrop sync fix (PR #1021)
This commit is contained in:
@@ -136,6 +136,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
|
||||
|
||||
### Mainstage — hero backdrop stays in sync when skipping albums quickly
|
||||
|
||||
**By [@cucadmuh](https://github.com/cucadmuh), reported by Asra on the Psysonic Discord, PR [#1021](https://github.com/Psychotoxical/psysonic/pull/1021)**
|
||||
|
||||
* Rapid prev/next clicks on the Mainstage hero no longer leave the blurred cover-art background on the previous album while the foreground cover and metadata already show the next one.
|
||||
|
||||
|
||||
|
||||
## [1.47.0]
|
||||
|
||||
> **🙏 Thank you to our amazing Discord community.** This release would not have been possible without your tireless support, quality checks, bug reports and all-round collaboration. Every report, every repro and every bit of feedback shaped what shipped here — thank you. Come join us: [discord.gg/AMnDRErm4u](https://discord.gg/AMnDRErm4u)
|
||||
|
||||
+25
-11
@@ -34,14 +34,25 @@ function HeroBg({ url }: { url: string }) {
|
||||
const [layers, setLayers] = useState<Array<{ url: string; id: number; visible: boolean }>>(() =>
|
||||
url ? [{ url, id: 0, visible: true }] : []
|
||||
);
|
||||
const counter = useRef(1);
|
||||
const counter = useRef(url ? 1 : 0);
|
||||
const latestUrlRef = useRef(url);
|
||||
latestUrlRef.current = url;
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) return;
|
||||
if (!url) {
|
||||
setLayers([]);
|
||||
return;
|
||||
}
|
||||
const id = counter.current++;
|
||||
setLayers(prev => [...prev, { url, id, visible: false }]);
|
||||
const t1 = setTimeout(() => setLayers(prev => prev.map(l => ({ ...l, visible: l.id === id }))), 20);
|
||||
const t2 = setTimeout(() => setLayers(prev => prev.filter(l => l.id === id)), 900);
|
||||
const t1 = setTimeout(() => {
|
||||
if (latestUrlRef.current !== url) return;
|
||||
setLayers(prev => prev.map(l => ({ ...l, visible: l.id === id })));
|
||||
}, 20);
|
||||
const t2 = setTimeout(() => {
|
||||
if (latestUrlRef.current !== url) return;
|
||||
setLayers(prev => prev.filter(l => l.id === id));
|
||||
}, 900);
|
||||
return () => { clearTimeout(t1); clearTimeout(t2); };
|
||||
}, [url]);
|
||||
|
||||
@@ -280,17 +291,20 @@ export default function Hero({ albums: albumsProp }: HeroProps = {}) {
|
||||
ensurePriority: 'high',
|
||||
});
|
||||
|
||||
// Keep the last known good URL so HeroBg never receives '' during a cache-miss
|
||||
// transition (which would cause the background to flash empty before fading in).
|
||||
const stableBgUrl = useRef('');
|
||||
// Per-album fallback so a cache miss on the current slide does not flash empty,
|
||||
// but never reuse another album's art (that caused bg/foreground desync on fast nav).
|
||||
const stableBgByAlbum = useRef<Record<string, string>>({});
|
||||
const albumId = album?.id;
|
||||
useEffect(() => {
|
||||
if (bgHandle.src && albumId) {
|
||||
stableBgByAlbum.current[albumId] = bgHandle.src;
|
||||
}
|
||||
}, [bgHandle.src, albumId]);
|
||||
const heroBgUrl = bgHandle.src || (albumId ? stableBgByAlbum.current[albumId] ?? '' : '');
|
||||
const { isHolding, pressBind } = useLongPressAction({
|
||||
onShortPress: () => { if (albumId) playAlbum(albumId); },
|
||||
onLongPress: () => { if (albumId) playAlbumShuffled(albumId); },
|
||||
});
|
||||
useEffect(() => {
|
||||
if (bgHandle.src) stableBgUrl.current = bgHandle.src;
|
||||
}, [bgHandle.src, albumId]);
|
||||
|
||||
if (!album) return <div className="hero-placeholder" />;
|
||||
|
||||
@@ -303,7 +317,7 @@ export default function Hero({ albums: albumsProp }: HeroProps = {}) {
|
||||
onClick={() => navigateToAlbum(album.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{enableCoverArtBackground && !perfFlags.disableMainstageHeroBackdrop && heroInView && <HeroBg url={stableBgUrl.current} />}
|
||||
{enableCoverArtBackground && !perfFlags.disableMainstageHeroBackdrop && heroInView && <HeroBg url={heroBgUrl} />}
|
||||
{enableCoverArtBackground && !perfFlags.disableMainstageHeroBackdrop && heroInView && <div className="hero-overlay" aria-hidden="true" />}
|
||||
|
||||
{/* key causes re-mount → animate-fade-in triggers on each album change */}
|
||||
|
||||
Reference in New Issue
Block a user