diff --git a/src/renderer/api/controller.ts b/src/renderer/api/controller.ts index 82ea43ac1..4fea4cfca 100644 --- a/src/renderer/api/controller.ts +++ b/src/renderer/api/controller.ts @@ -567,7 +567,11 @@ export const controller: GeneralController = { return apiController( 'getSimilarSongs', server.type, - )?.({ ...args, apiClientProps: { ...args.apiClientProps, server } }); + )?.({ + ...args, + apiClientProps: { ...args.apiClientProps, server }, + query: mergeMusicFolderId(args.query, server), + }); }, getSongDetail(args) { const server = getServerById(args.apiClientProps.serverId); diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index 22ab67c77..35f959e14 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -923,7 +923,12 @@ export const JellyfinController: InternalControllerEndpoint = { throw new Error('Failed to get server info'); } - const features = getFeatures(VERSION_INFO, res.body.Version); + const defaultFeatures = {}; + + const features = { + ...defaultFeatures, + ...getFeatures(VERSION_INFO, res.body.Version), + }; return { features, diff --git a/src/renderer/features/player/hooks/use-auto-dj.ts b/src/renderer/features/player/hooks/use-auto-dj.ts index cd2d052cb..6db619a83 100644 --- a/src/renderer/features/player/hooks/use-auto-dj.ts +++ b/src/renderer/features/player/hooks/use-auto-dj.ts @@ -8,6 +8,7 @@ import { isShuffleEnabled, mapShuffledToQueueIndex, useAutoDJSettings, + useCurrentServer, useCurrentServerId, usePlayerStore, usePlayerStoreBase, @@ -15,16 +16,21 @@ import { import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { logMsg } from '/@/renderer/utils/logger-message'; import { shuffleInPlace } from '/@/renderer/utils/shuffle'; -import { Played, SongListSort, SortOrder } from '/@/shared/types/domain-types'; +import { hasFeature } from '/@/shared/api/utils'; +import { Played, Song, SongListSort, SortOrder } from '/@/shared/types/domain-types'; +import { ServerFeature } from '/@/shared/types/features-types'; import { Play } from '/@/shared/types/types'; export const useAutoDJ = () => { const queryClient = useQueryClient(); const serverId = useCurrentServerId(); + const server = useCurrentServer(); const player = usePlayer(); const settings = useAutoDJSettings(); const isFetching = useIsPlayerFetching(); + const hasSimilarSongsMusicFolder = hasFeature(server, ServerFeature.SIMILAR_SONGS_MUSIC_FOLDER); + useEffect(() => { const unsubscribe = usePlayerStoreBase.subscribe( (state) => { @@ -61,35 +67,43 @@ export const useAutoDJ = () => { }); try { - // First, try to fetch similar songs based on the current song - const similarSongs = await queryClient.fetchQuery({ - ...songsQueries.similar({ - query: { - count: settings.itemCount, - songId: properties.song?.id, - }, - serverId, - }), - queryKey: queryKeys.player.fetch({ similarSongs: properties.song?.id }), - }); - const queue = usePlayerStore.getState().getQueue(); - const queueSongIdSet = new Set(queue.items.map((item) => item.id)); - const uniqueSimilarSongs = similarSongs.filter( - (song) => !queueSongIdSet.has(song.id), - ); + let uniqueSimilarSongs: Song[] = []; + + const hasMusicFolder = server?.musicFolderId && server.musicFolderId.length > 0; + const trySimilarSongs = hasMusicFolder && hasSimilarSongsMusicFolder; + + // Skip similar songs fetch if a music folder is selected and does not support musicFolderId on similar songs + if (trySimilarSongs) { + // First, try to fetch similar songs based on the current song + const similarSongs = await queryClient.fetchQuery({ + ...songsQueries.similar({ + query: { + count: settings.itemCount, + songId: properties.song?.id, + }, + serverId, + }), + queryKey: queryKeys.player.fetch({ similarSongs: properties.song?.id }), + }); + + uniqueSimilarSongs = similarSongs.filter( + (song) => !queueSongIdSet.has(song.id), + ); + } // If not enough songs, try to fetch more similar songs based on the genre of the current song if (uniqueSimilarSongs.length < settings.itemCount) { const genre = properties.song?.genres?.[0]; if (genre) { + const genreLimit = 50; const genreSimilarSongs = await queryClient.fetchQuery({ ...songsQueries.random({ query: { genre: genre.id, - limit: 50, + limit: genreLimit, played: Played.All, }, serverId, @@ -100,11 +114,35 @@ export const useAutoDJ = () => { }), }); - uniqueSimilarSongs.push( - ...genreSimilarSongs.items.filter( - (song) => !queueSongIdSet.has(song.id), - ), + const genreSongs = genreSimilarSongs.items.filter( + (song) => !queueSongIdSet.has(song.id), ); + + // If trySimilarSongs is false, add variation by mixing in random songs + if (!trySimilarSongs) { + // Calculate how many random songs we need: 20% or at least 1 + const randomSongCount = Math.max(1, Math.ceil(genreLimit * 0.2)); + + const randomSongs = await queryClient.fetchQuery({ + ...songsQueries.random({ + query: { limit: randomSongCount, played: Played.All }, + serverId, + }), + }); + + const uniqueRandomSongs = randomSongs.items.filter( + (song) => !queueSongIdSet.has(song.id), + ); + + // Add minimum required random songs for variation + const randomSongsToAdd = uniqueRandomSongs.slice( + 0, + randomSongCount, + ); + uniqueSimilarSongs.push(...randomSongsToAdd, ...genreSongs); + } else { + uniqueSimilarSongs.push(...genreSongs); + } } } @@ -176,9 +214,11 @@ export const useAutoDJ = () => { return () => unsubscribe(); }, [ + hasSimilarSongsMusicFolder, isFetching, player, queryClient, + server, serverId, settings.enabled, settings.itemCount, diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts index f28c7f8c4..cd90a12a6 100644 --- a/src/shared/types/domain-types.ts +++ b/src/shared/types/domain-types.ts @@ -1584,6 +1584,7 @@ export type SimilarSongsArgs = BaseEndpointArgs & { export type SimilarSongsQuery = { count?: number; + musicFolderId?: string | string[]; songId: string; }; diff --git a/src/shared/types/features-types.ts b/src/shared/types/features-types.ts index c7cfcb959..640312b5e 100644 --- a/src/shared/types/features-types.ts +++ b/src/shared/types/features-types.ts @@ -10,6 +10,7 @@ export enum ServerFeature { PUBLIC_PLAYLIST = 'publicPlaylist', SERVER_PLAY_QUEUE = 'serverPlayQueue', SHARING_ALBUM_SONG = 'sharingAlbumSong', + SIMILAR_SONGS_MUSIC_FOLDER = 'similarSongsMusicFolder', TAGS = 'tags', TRACK_ALBUM_ARTIST_SEARCH = 'trackAlbumArtistSearch', }