fix(now-playing): split artist links and About the Artist tabs (#960)

* fix(now-playing): split artist links and About the Artist tabs

OpenSubsonic artists[] now drives per-artist navigation on Now Playing
hero and the queue current-track row (matching player bar). About the
Artist loads bio for each performer via tabs when a track has multiple
artist ids; queue Info uses the primary ref for bio/tour fetch.

Reported by zunoz on the Psysonic Discord (v1.47.0-rc.3).

* docs: note Now Playing multi-artist fix in CHANGELOG (PR #960)
This commit is contained in:
cucadmuh
2026-06-03 22:16:06 +03:00
committed by GitHub
parent a142bb1dab
commit 4c70408bd6
12 changed files with 379 additions and 48 deletions
+85
View File
@@ -0,0 +1,85 @@
import { useEffect, useMemo, useState } from 'react';
import { getArtistInfoForServer } from '../api/subsonicArtists';
import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '../api/subsonicTypes';
import { makeCache } from '../utils/cache/nowPlayingCache';
const artistInfoCache = makeCache<SubsonicArtistInfo | null>();
function cacheKey(serverId: string, artistId: string): string {
return `${serverId}:${artistId}`;
}
/**
* Fetches `getArtistInfo` for each ref with an id. Returns `undefined` for ids
* still loading, `null` when fetch finished with no info.
*/
export function useArtistInfoBatch(
serverId: string | undefined,
refs: SubsonicOpenArtistRef[],
similarArtistCount?: number,
): Record<string, SubsonicArtistInfo | null | undefined> {
const ids = useMemo(
() => [...new Set(refs.map(r => r.id?.trim()).filter((id): id is string => Boolean(id)))],
[refs],
);
const idsKey = ids.join('\x1e');
const [byId, setById] = useState<Record<string, SubsonicArtistInfo | null | undefined>>(() => {
if (!serverId || ids.length === 0) return {};
const seed: Record<string, SubsonicArtistInfo | null | undefined> = {};
for (const id of ids) {
const cached = artistInfoCache.get(cacheKey(serverId, id));
if (cached !== undefined) seed[id] = cached;
}
return seed;
});
useEffect(() => {
if (!serverId || ids.length === 0) {
setById({});
return;
}
const next: Record<string, SubsonicArtistInfo | null | undefined> = {};
const pending: string[] = [];
for (const id of ids) {
const cached = artistInfoCache.get(cacheKey(serverId, id));
if (cached !== undefined) {
next[id] = cached;
} else {
next[id] = undefined;
pending.push(id);
}
}
setById(next);
if (pending.length === 0) return;
let cancelled = false;
void Promise.all(
pending.map(async id => {
try {
const info = await getArtistInfoForServer(serverId, id, {
similarArtistCount: similarArtistCount,
});
artistInfoCache.set(cacheKey(serverId, id), info ?? null);
return [id, info ?? null] as const;
} catch {
artistInfoCache.set(cacheKey(serverId, id), null);
return [id, null] as const;
}
}),
).then(results => {
if (cancelled) return;
setById(prev => {
const merged = { ...prev };
for (const [id, info] of results) merged[id] = info;
return merged;
});
});
return () => { cancelled = true; };
}, [serverId, idsKey, similarArtistCount]);
return byId;
}
+5 -2
View File
@@ -12,6 +12,7 @@ import { prewarmNowPlayingFetchers } from './useNowPlayingFetchers';
import { useAuthStore } from '../store/authStore';
import { usePlayerStore } from '../store/playerStore';
import { usePlaybackServerId } from './usePlaybackServerId';
import { primaryTrackArtistRef } from '../utils/playback/trackArtistRefs';
const NOW_PLAYING_COVER_CSS_PX = 800;
@@ -52,11 +53,12 @@ export function useNowPlayingPrewarm(): void {
useEffect(() => {
if (!currentTrack || !playbackServerId) return;
const primary = primaryTrackArtistRef(currentTrack);
void prewarmNowPlayingFetchers({
songId: currentTrack.id,
artistId: currentTrack.artistId,
artistId: primary.id,
albumId: currentTrack.albumId,
artistName: currentTrack.artist,
artistName: primary.name ?? currentTrack.artist,
enableBandsintown,
audiomuseNavidromeEnabled,
lastfmUsername,
@@ -81,6 +83,7 @@ export function useNowPlayingPrewarm(): void {
}, [
currentTrack?.id,
currentTrack?.artistId,
currentTrack?.artists,
currentTrack?.albumId,
currentTrack?.coverArt,
currentTrack?.artist,