diff --git a/src/store/playerStore.ts b/src/store/playerStore.ts index a7b7490d..cd0c0506 100644 --- a/src/store/playerStore.ts +++ b/src/store/playerStore.ts @@ -17,6 +17,11 @@ import { useOrbitStore } from './orbitStore'; import { estimateLivePosition } from '../api/orbit'; import { loudnessGainPlaceholderUntilCacheDb } from '../utils/loudnessPlaceholder'; import { effectiveLoudnessPreAnalysisAttenuationDb } from '../utils/loudnessPreAnalysisSlider'; +import { + enrichSongsForMixRatingFilter, + getMixMinRatingsConfigFromAuth, + passesMixMinRatings, +} from '../utils/mixRatingFilter'; export interface Track { id: string; @@ -121,6 +126,8 @@ async function buildInfiniteQueueCandidates( 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; @@ -130,22 +137,35 @@ async function buildInfiniteQueueCandidates( ]); const seedId = seedTrack?.id ?? null; - const mixCandidates = shuffleArray( - [...top, ...similar] + 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 })); - if (mixCandidates.length > 0) return mixCandidates; + 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; + } + } - const random = await getRandomSongs(count, seedTrack?.genre).catch(() => []); - return random - .map(songToTrack) - .filter(t => t.id !== seedId && !existingIds.has(t.id)) - .slice(0, count) - .map(t => ({ ...t, autoAdded: true as const })); + return out.slice(0, count); } interface PlayerState {