import { getSimilarSongs2, getTopSongs } from '../../api/subsonicArtists'; import { getRandomSongs } from '../../api/subsonicLibrary'; import type { Track } from '../../store/playerStoreTypes'; import { enrichSongsForMixRatingFilter, getMixMinRatingsConfigFromAuth, passesMixMinRatings, } from '../mix/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, count = 5, ): Promise { 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([...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); }