mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
006635de4b
Three domain-eng modules peel ~316 LOC of fetch/mapping out of `api/subsonic.ts`: - `subsonicLibrary.ts` — browse + random + per-song fetch (`getMusicDirectory`, `getMusicIndexes`, `getMusicFolders`, `getRandomAlbums`, `getAlbumList`, `getRandomSongs`, `getRandomSongsFiltered`, `getSong`, `getAlbum`, `filterSongsToActiveLibrary`, `similarSongsRequestCount`, plus the private `albumIdsInActiveLibraryScope` cache). - `subsonicArtists.ts` — artist endpoints (`getArtists`, `getArtist`, `getArtistInfo`, `getTopSongs`, `getSimilarSongs2`, `getSimilarSongs`). Uses Library's `filterSongsToActiveLibrary` and `similarSongsRequestCount` for the per-library scoping fallback. - `subsonicRatings.ts` — `parseSubsonicEntityStarRating` parser plus `prefetchArtistUserRatings` and `prefetchAlbumUserRatings` workers with the shared 7-min cache. Calls back into Library/Artists for the per-id fetch. 51 external call sites migrated to direct imports. No re-export shims in `subsonic.ts`. Statistics endpoints still in `subsonic.ts` keep their `RATING_CACHE_TTL` constant locally (same 7-min window). subsonic.ts: 1078 → 762 LOC (−316).
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { getSimilarSongs2, getTopSongs } from '../api/subsonicArtists';
|
|
import { getRandomSongs } from '../api/subsonicLibrary';
|
|
import type { Track } from '../store/playerStoreTypes';
|
|
import {
|
|
enrichSongsForMixRatingFilter,
|
|
getMixMinRatingsConfigFromAuth,
|
|
passesMixMinRatings,
|
|
} from './mixRatingFilter';
|
|
import { shuffleArray } from './shuffleArray';
|
|
import { songToTrack } from './songToTrack';
|
|
/**
|
|
* Infinite queue source strategy (Instant Mix-like):
|
|
* 1) Prefer artist-driven candidates (Top + Similar) around the current track.
|
|
* 2) Fallback to random songs when artist-driven fetches are empty.
|
|
*/
|
|
export async function buildInfiniteQueueCandidates(
|
|
seedTrack: Track | null,
|
|
existingIds: Set<string>,
|
|
count = 5,
|
|
): Promise<Track[]> {
|
|
const RANDOM_TOPUP_BATCH_SIZE = Math.max(10, count * 2);
|
|
const RANDOM_TOPUP_MAX_BATCHES = 8;
|
|
const artistId = seedTrack?.artistId?.trim() || null;
|
|
const artistName = seedTrack?.artist?.trim() || null;
|
|
|
|
const [similar, top] = await Promise.all([
|
|
artistId ? getSimilarSongs2(artistId).catch(() => []) : Promise.resolve([]),
|
|
artistName ? getTopSongs(artistName).catch(() => []) : Promise.resolve([]),
|
|
]);
|
|
|
|
const seedId = seedTrack?.id ?? null;
|
|
const mixCfg = getMixMinRatingsConfigFromAuth();
|
|
const mixedSources = [...top, ...similar];
|
|
const filteredMixedSongs = mixCfg.enabled
|
|
? (await enrichSongsForMixRatingFilter(mixedSources, mixCfg)).filter(s => passesMixMinRatings(s, mixCfg))
|
|
: mixedSources;
|
|
const out: Track[] = shuffleArray(
|
|
filteredMixedSongs
|
|
.map(songToTrack)
|
|
.filter(t => t.id !== seedId && !existingIds.has(t.id)),
|
|
)
|
|
.slice(0, count)
|
|
.map(t => ({ ...t, autoAdded: true as const }));
|
|
|
|
const seenIds = new Set<string>([...existingIds, ...out.map(t => t.id)]);
|
|
for (let b = 0; out.length < count && b < RANDOM_TOPUP_MAX_BATCHES; b++) {
|
|
const random = await getRandomSongs(RANDOM_TOPUP_BATCH_SIZE, seedTrack?.genre).catch(() => []);
|
|
if (!random.length) break;
|
|
const filteredRandomSongs = mixCfg.enabled
|
|
? (await enrichSongsForMixRatingFilter(random, mixCfg)).filter(s => passesMixMinRatings(s, mixCfg))
|
|
: random;
|
|
for (const track of shuffleArray(filteredRandomSongs.map(songToTrack))) {
|
|
if (track.id === seedId || seenIds.has(track.id)) continue;
|
|
out.push({ ...track, autoAdded: true as const });
|
|
seenIds.add(track.id);
|
|
if (out.length >= count) break;
|
|
}
|
|
}
|
|
|
|
return out.slice(0, count);
|
|
}
|