From a0bae0ffb02aba50ea5a9d5448e64c9a6c3272fe Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 23 Jul 2026 19:51:30 -0700 Subject: [PATCH] autodj fixes and enhancements (#2271) - add setting to allow duplicates - add setting to only prefer similar sogs - fix autodj algorithm to match by the priority order instead of incorrectly grouping when limit not reached --- docs/ENV_SETTINGS.md | 2 + settings.js.template | 2 + src/i18n/locales/en.json | 4 + .../features/player/auto-dj/auto-dj-albums.ts | 182 ++++++++++++++---- .../features/player/auto-dj/auto-dj-songs.ts | 126 +++++++++--- .../features/player/auto-dj/auto-dj-utils.ts | 13 -- .../player/components/right-controls.tsx | 157 ++++++++++----- .../features/player/hooks/use-auto-dj.ts | 4 + .../components/playback/auto-dj-settings.tsx | 35 ++++ src/renderer/global.d.ts | 2 + src/renderer/store/env-settings-overrides.ts | 2 + src/renderer/store/settings.store.ts | 4 + 12 files changed, 406 insertions(+), 127 deletions(-) diff --git a/docs/ENV_SETTINGS.md b/docs/ENV_SETTINGS.md index ea451a142..a7f45c4a8 100644 --- a/docs/ENV_SETTINGS.md +++ b/docs/ENV_SETTINGS.md @@ -128,9 +128,11 @@ Applies to the default lyrics display profile (`lyricsDisplay.default`). | Setting path | Default | Env variable | Available values / Description | |-------------|---------|--------------|--------------------------------| | `autoDJ.albumStrategy` | `similar` | `FS_AUTO_DJ_ALBUM_STRATEGY` | `similar` / `library_random`. | +| `autoDJ.allowDuplicates` | `false` | `FS_AUTO_DJ_ALLOW_DUPLICATES` | `true` / `false` — Allow songs or albums already in the queue to be added again. | | `autoDJ.enabled` | `false` | `FS_AUTO_DJ_ENABLED` | `true` / `false`. | | `autoDJ.itemCount` | `5` | `FS_AUTO_DJ_ITEM_COUNT` | Number of items to add. | | `autoDJ.mode` | `songs` | `FS_AUTO_DJ_MODE` | `songs` / `albums`. | +| `autoDJ.onlySimilar` | `false` | `FS_AUTO_DJ_ONLY_SIMILAR` | `true` / `false` — Treat item count as a maximum; use the first non-empty source (similar, then genre, artist, or random) and do not fill from later sources. | | `autoDJ.songStrategy` | `similar` | `FS_AUTO_DJ_SONG_STRATEGY` | `similar` / `library_random`. | | `autoDJ.timing` | `1` | `FS_AUTO_DJ_TIMING` | Timing value (number). | diff --git a/settings.js.template b/settings.js.template index 0414d71f5..cabce7fed 100644 --- a/settings.js.template +++ b/settings.js.template @@ -93,9 +93,11 @@ window.FS_LYRICS_PADDING_LEFT = "${FS_LYRICS_PADDING_LEFT}"; window.FS_LYRICS_PADDING_RIGHT = "${FS_LYRICS_PADDING_RIGHT}"; window.FS_AUTO_DJ_ALBUM_STRATEGY = "${FS_AUTO_DJ_ALBUM_STRATEGY}"; +window.FS_AUTO_DJ_ALLOW_DUPLICATES = "${FS_AUTO_DJ_ALLOW_DUPLICATES}"; window.FS_AUTO_DJ_ENABLED = "${FS_AUTO_DJ_ENABLED}"; window.FS_AUTO_DJ_ITEM_COUNT = "${FS_AUTO_DJ_ITEM_COUNT}"; window.FS_AUTO_DJ_MODE = "${FS_AUTO_DJ_MODE}"; +window.FS_AUTO_DJ_ONLY_SIMILAR = "${FS_AUTO_DJ_ONLY_SIMILAR}"; window.FS_AUTO_DJ_SONG_STRATEGY = "${FS_AUTO_DJ_SONG_STRATEGY}"; window.FS_AUTO_DJ_TIMING = "${FS_AUTO_DJ_TIMING}"; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 95af20acb..8927babb4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -796,6 +796,10 @@ "autoDJ_songStrategy": "Song selection mode", "autoDJ_strategy_option_library_random": "Random", "autoDJ_strategy_option_similar": "Similar", + "autoDJ_allowDuplicates": "Allow duplicates", + "autoDJ_allowDuplicates_description": "Allow songs or albums already in the queue to be added again by Auto DJ", + "autoDJ_onlySimilar": "Only similar", + "autoDJ_onlySimilar_description": "Only add items that are similar to the triggering song. The item count is treated as a maximum amount of items to add instead of being filled up to the limit by other sources", "autosave": "Automatically save play queue", "autosave_description": "Enable automatically saving the play queue to your server. This is only possible when using Navidrome/Subsonic, and you cannot have a mixed play queue.", "autosaveCount": "Automatic play queue save frequency", diff --git a/src/renderer/features/player/auto-dj/auto-dj-albums.ts b/src/renderer/features/player/auto-dj/auto-dj-albums.ts index bfdf501cf..aeb41f872 100644 --- a/src/renderer/features/player/auto-dj/auto-dj-albums.ts +++ b/src/renderer/features/player/auto-dj/auto-dj-albums.ts @@ -1,6 +1,6 @@ import type { QueryClient } from '@tanstack/react-query'; -import { autoDjGenreIdsForSongGenre, autoDjPushUniqueAlbumIds } from './auto-dj-utils'; +import { autoDjGenreIdsForSongGenre } from './auto-dj-utils'; import { queryKeys } from '/@/renderer/api/query-keys'; import { albumQueries } from '/@/renderer/features/albums/api/album-api'; @@ -16,9 +16,11 @@ import { export type AutoDjAlbumCollectArgs = { albumStrategy: AutoDJStrategy; + allowDuplicates: boolean; currentSong: QueueSong; itemCount: number; musicFolderId: string | string[] | undefined; + onlySimilar: boolean; queryClient: QueryClient; queueAlbumIdSet: Set; server: null | ServerListItem | undefined; @@ -37,6 +39,19 @@ export const runAutoDjAlbumIds = async (args: AutoDjAlbumCollectArgs): Promise, + selectedAlbumIdSet: Set, +) => { + if (allowDuplicates) { + return true; + } + + return !queueAlbumIdSet.has(albumId) && !selectedAlbumIdSet.has(albumId); +}; + const collectAlbumsLibraryRandom = async (args: AutoDjAlbumCollectArgs): Promise => { const page = await args.queryClient.fetchQuery({ ...albumQueries.list({ @@ -52,14 +67,18 @@ const collectAlbumsLibraryRandom = async (args: AutoDjAlbumCollectArgs): Promise queryKey: queryKeys.player.fetch({ autoDjAlbumLibraryRandom: args.currentSong?.id }), }); - const ids = page.items.map((a) => a.id).filter((id) => id && !args.queueAlbumIdSet.has(id)); + const ids = page.items + .map((album) => album.id) + .filter( + (albumId) => albumId && (args.allowDuplicates || !args.queueAlbumIdSet.has(albumId)), + ); return shuffle(ids).slice(0, args.itemCount); }; const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise => { - const targetAlbumCount = args.itemCount; - const candidateAlbumIds: string[] = []; - const seenAlbumCandidates = new Set(); + const selectedAlbumIds: string[] = []; + const selectedAlbumIdSet = new Set(); + const remainingCount = () => args.itemCount - selectedAlbumIds.length; if (args.trySimilarSongs && args.currentSong?.id) { const similarSongsFromSimilarApi = await args.queryClient.fetchQuery({ @@ -75,24 +94,46 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise s.albumId), + const similarSlotsToFill = remainingCount(); + const shuffledSimilarAlbumIds = shuffle( + similarSongsFromSimilarApi + .map((song) => song.albumId) + .filter((albumId): albumId is string => { + if (!albumId) { + return false; + } + + return isAlbumIdAvailable( + albumId, + args.allowDuplicates, + args.queueAlbumIdSet, + selectedAlbumIdSet, + ); + }), ); + + for (const albumId of shuffledSimilarAlbumIds.slice(0, similarSlotsToFill)) { + selectedAlbumIdSet.add(albumId); + selectedAlbumIds.push(albumId); + } + + if (args.onlySimilar && selectedAlbumIds.length > 0) { + return selectedAlbumIds; + } } - if (candidateAlbumIds.length < targetAlbumCount && args.currentSong && args.server) { + if (remainingCount() > 0 && args.currentSong && args.server) { const genre = args.currentSong.genres?.[0]; + if (genre) { const genreIds = autoDjGenreIdsForSongGenre(genre, args.server.type); + const genreLimit = 50; const genreAlbums = await args.queryClient.fetchQuery({ ...albumQueries.list({ query: { genreIds, - limit: 50, + limit: genreLimit, musicFolderId: args.musicFolderId, sortBy: AlbumListSort.RANDOM, sortOrder: SortOrder.ASC, @@ -106,15 +147,8 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise album.id), - ); - if (!args.trySimilarSongs) { - const randomAlbumMixCount = Math.max(1, Math.ceil(50 * 0.2)); + const randomAlbumMixCount = Math.max(1, Math.ceil(genreLimit * 0.2)); const randomAlbumsMix = await args.queryClient.fetchQuery({ ...albumQueries.list({ query: { @@ -131,17 +165,56 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise album.id), + const spiceSlotsToFill = Math.min(randomAlbumMixCount, remainingCount()); + const shuffledSpiceAlbumIds = shuffle( + randomAlbumsMix.items + .map((album) => album.id) + .filter( + (albumId): albumId is string => + Boolean(albumId) && + isAlbumIdAvailable( + albumId, + args.allowDuplicates, + args.queueAlbumIdSet, + selectedAlbumIdSet, + ), + ), ); + + for (const albumId of shuffledSpiceAlbumIds.slice(0, spiceSlotsToFill)) { + selectedAlbumIdSet.add(albumId); + selectedAlbumIds.push(albumId); + } + } + + const genreSlotsToFill = remainingCount(); + const shuffledGenreAlbumIds = shuffle( + genreAlbums.items + .map((album) => album.id) + .filter( + (albumId): albumId is string => + Boolean(albumId) && + isAlbumIdAvailable( + albumId, + args.allowDuplicates, + args.queueAlbumIdSet, + selectedAlbumIdSet, + ), + ), + ); + + for (const albumId of shuffledGenreAlbumIds.slice(0, genreSlotsToFill)) { + selectedAlbumIdSet.add(albumId); + selectedAlbumIds.push(albumId); + } + + if (args.onlySimilar && selectedAlbumIds.length > 0) { + return selectedAlbumIds; } } } - if (candidateAlbumIds.length < targetAlbumCount && args.currentSong) { + if (remainingCount() > 0 && args.currentSong) { const albumArtist = args.currentSong.albumArtists?.[0]; if (albumArtist) { @@ -163,16 +236,34 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise album.id), + const artistSlotsToFill = remainingCount(); + const shuffledArtistAlbumIds = shuffle( + albumsByArtist.items + .map((album) => album.id) + .filter( + (albumId): albumId is string => + Boolean(albumId) && + isAlbumIdAvailable( + albumId, + args.allowDuplicates, + args.queueAlbumIdSet, + selectedAlbumIdSet, + ), + ), ); + + for (const albumId of shuffledArtistAlbumIds.slice(0, artistSlotsToFill)) { + selectedAlbumIdSet.add(albumId); + selectedAlbumIds.push(albumId); + } + + if (args.onlySimilar && selectedAlbumIds.length > 0) { + return selectedAlbumIds; + } } } - if (candidateAlbumIds.length < targetAlbumCount && args.currentSong) { + if (remainingCount() > 0 && args.currentSong) { const randomAlbumsFallback = await args.queryClient.fetchQuery({ ...albumQueries.list({ query: { @@ -189,14 +280,27 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise album.id), + const randomSlotsToFill = remainingCount(); + const shuffledRandomAlbumIds = shuffle( + randomAlbumsFallback.items + .map((album) => album.id) + .filter( + (albumId): albumId is string => + Boolean(albumId) && + isAlbumIdAvailable( + albumId, + args.allowDuplicates, + args.queueAlbumIdSet, + selectedAlbumIdSet, + ), + ), ); + + for (const albumId of shuffledRandomAlbumIds.slice(0, randomSlotsToFill)) { + selectedAlbumIdSet.add(albumId); + selectedAlbumIds.push(albumId); + } } - const shuffledAlbums = shuffle(candidateAlbumIds); - return shuffledAlbums.slice(0, targetAlbumCount); + return selectedAlbumIds; }; diff --git a/src/renderer/features/player/auto-dj/auto-dj-songs.ts b/src/renderer/features/player/auto-dj/auto-dj-songs.ts index fd9f1565a..9795ddb50 100644 --- a/src/renderer/features/player/auto-dj/auto-dj-songs.ts +++ b/src/renderer/features/player/auto-dj/auto-dj-songs.ts @@ -14,9 +14,11 @@ import { } from '/@/shared/types/domain-types'; export type AutoDjSongCollectArgs = { + allowDuplicates: boolean; currentSong: QueueSong; itemCount: number; musicFolderId: string | string[] | undefined; + onlySimilar: boolean; queryClient: QueryClient; queueSongIdSet: Set; server: null | ServerListItem | undefined; @@ -36,6 +38,19 @@ export const runAutoDjSongs = async (args: AutoDjSongCollectArgs): Promise, + selectedSongIds: Set, +) => { + if (allowDuplicates) { + return true; + } + + return !queueSongIdSet.has(song.id) && !selectedSongIds.has(song.id); +}; + const collectSongsLibraryRandom = async (args: AutoDjSongCollectArgs): Promise => { const randomSongs = await args.queryClient.fetchQuery({ ...songsQueries.random({ @@ -48,13 +63,17 @@ const collectSongsLibraryRandom = async (args: AutoDjSongCollectArgs): Promise !args.queueSongIdSet.has(song.id)); + const pool = randomSongs.items.filter( + (song) => args.allowDuplicates || !args.queueSongIdSet.has(song.id), + ); const shuffled = shuffleInPlace(pool); return shuffled.slice(0, args.itemCount); }; const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise => { - let uniqueSimilarSongs: Song[] = []; + const selected: Song[] = []; + const selectedSongIds = new Set(); + const remainingCount = () => args.itemCount - selected.length; if (args.trySimilarSongs) { const similarSongs = await args.queryClient.fetchQuery({ @@ -68,10 +87,24 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise queryKey: queryKeys.player.fetch({ similarSongs: args.currentSong?.id }), }); - uniqueSimilarSongs = similarSongs.filter((song) => !args.queueSongIdSet.has(song.id)); + const slotsToFill = remainingCount(); + const shuffledSimilarSongs = shuffleInPlace( + similarSongs.filter((song) => + isSongAvailable(song, args.allowDuplicates, args.queueSongIdSet, selectedSongIds), + ), + ); + + for (const song of shuffledSimilarSongs.slice(0, slotsToFill)) { + selectedSongIds.add(song.id); + selected.push(song); + } + + if (args.onlySimilar && selected.length > 0) { + return selected; + } } - if (uniqueSimilarSongs.length < args.itemCount) { + if (remainingCount() > 0) { const genre = args.currentSong?.genres?.[0]; if (genre) { @@ -91,10 +124,6 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise }), }); - const genreSongs = genreSimilarSongs.items.filter( - (song) => !args.queueSongIdSet.has(song.id), - ); - if (!args.trySimilarSongs) { const randomSongCount = Math.max(1, Math.ceil(genreLimit * 0.2)); @@ -105,19 +134,48 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise }), }); - const uniqueRandomSongs = randomSongs.items.filter( - (song) => !args.queueSongIdSet.has(song.id), + const spiceSlotsToFill = Math.min(randomSongCount, remainingCount()); + const shuffledSpiceSongs = shuffleInPlace( + randomSongs.items.filter((song) => + isSongAvailable( + song, + args.allowDuplicates, + args.queueSongIdSet, + selectedSongIds, + ), + ), ); - const randomSongsToAdd = uniqueRandomSongs.slice(0, randomSongCount); - uniqueSimilarSongs.push(...randomSongsToAdd, ...genreSongs); - } else { - uniqueSimilarSongs.push(...genreSongs); + for (const song of shuffledSpiceSongs.slice(0, spiceSlotsToFill)) { + selectedSongIds.add(song.id); + selected.push(song); + } + } + + const genreSlotsToFill = remainingCount(); + const shuffledGenreSongs = shuffleInPlace( + genreSimilarSongs.items.filter((song) => + isSongAvailable( + song, + args.allowDuplicates, + args.queueSongIdSet, + selectedSongIds, + ), + ), + ); + + for (const song of shuffledGenreSongs.slice(0, genreSlotsToFill)) { + selectedSongIds.add(song.id); + selected.push(song); + } + + if (args.onlySimilar && selected.length > 0) { + return selected; } } } - if (uniqueSimilarSongs.length < args.itemCount) { + if (remainingCount() > 0) { const albumArtist = args.currentSong?.albumArtists?.[0]; if (albumArtist) { @@ -138,15 +196,30 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise }), }); - uniqueSimilarSongs.push( - ...albumArtistSimilarSongs.items.filter( - (song) => !args.queueSongIdSet.has(song.id), + const artistSlotsToFill = remainingCount(); + const shuffledArtistSongs = shuffleInPlace( + albumArtistSimilarSongs.items.filter((song) => + isSongAvailable( + song, + args.allowDuplicates, + args.queueSongIdSet, + selectedSongIds, + ), ), ); + + for (const song of shuffledArtistSongs.slice(0, artistSlotsToFill)) { + selectedSongIds.add(song.id); + selected.push(song); + } + + if (args.onlySimilar && selected.length > 0) { + return selected; + } } } - if (uniqueSimilarSongs.length < args.itemCount) { + if (remainingCount() > 0) { const randomSongs = await args.queryClient.fetchQuery({ ...songsQueries.random({ query: { limit: 50, played: Played.All }, @@ -154,11 +227,18 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise }), }); - uniqueSimilarSongs.push( - ...randomSongs.items.filter((song) => !args.queueSongIdSet.has(song.id)), + const randomSlotsToFill = remainingCount(); + const shuffledRandomSongs = shuffleInPlace( + randomSongs.items.filter((song) => + isSongAvailable(song, args.allowDuplicates, args.queueSongIdSet, selectedSongIds), + ), ); + + for (const song of shuffledRandomSongs.slice(0, randomSlotsToFill)) { + selectedSongIds.add(song.id); + selected.push(song); + } } - const shuffledSongs = shuffleInPlace(uniqueSimilarSongs); - return shuffledSongs.slice(0, args.itemCount); + return selected; }; diff --git a/src/renderer/features/player/auto-dj/auto-dj-utils.ts b/src/renderer/features/player/auto-dj/auto-dj-utils.ts index 0213451d3..f170f7947 100644 --- a/src/renderer/features/player/auto-dj/auto-dj-utils.ts +++ b/src/renderer/features/player/auto-dj/auto-dj-utils.ts @@ -2,19 +2,6 @@ import type { Genre } from '/@/shared/types/domain-types'; import { ServerType } from '/@/shared/types/domain-types'; -export const autoDjPushUniqueAlbumIds = ( - accumulator: string[], - seenAlbums: Set, - queueAlbumIdSet: Set, - ...ids: (string | undefined)[] -) => { - for (const id of ids) { - if (!id || queueAlbumIdSet.has(id) || seenAlbums.has(id)) continue; - seenAlbums.add(id); - accumulator.push(id); - } -}; - export const autoDjGenreIdsForSongGenre = (genre: Genre, serverType: ServerType): string[] => { if (serverType === ServerType.JELLYFIN) { return [genre.id]; diff --git a/src/renderer/features/player/components/right-controls.tsx b/src/renderer/features/player/components/right-controls.tsx index b07d5a64b..68155f8c3 100644 --- a/src/renderer/features/player/components/right-controls.tsx +++ b/src/renderer/features/player/components/right-controls.tsx @@ -194,58 +194,111 @@ const AutoDJButton = () => { value={settings.mode} w="100%" /> - { + if (!value) return; + setSettings({ + autoDJ: + settings.mode === AUTO_DJ_MODE.ALBUMS + ? { albumStrategy: value as AutoDJStrategy } + : { songStrategy: value as AutoDJStrategy }, + }); + }} + size="sm" + value={strategyValue} + variant="filled" + w="100%" + /> + + + + setSettings({ + autoDJ: { + itemCount: Number(e), + }, + }) + } + size="sm" + value={Number(settings.itemCount)} + variant="filled" + /> + + + + setSettings({ + autoDJ: { + timing: Number(e), + }, + }) + } + size="sm" + value={Number(settings.timing)} + variant="filled" + /> + + + + + + {t('setting.autoDJ_allowDuplicates')} + + + {t('setting.autoDJ_allowDuplicates_description')} + + + + setSettings({ + autoDJ: { + allowDuplicates: e.currentTarget.checked, + }, + }) + } + /> + + + + + + + {t('setting.autoDJ_onlySimilar')} + + + {t('setting.autoDJ_onlySimilar_description')} + + + + setSettings({ + autoDJ: { + onlySimilar: e.currentTarget.checked, + }, + }) + } + /> + + diff --git a/src/renderer/features/player/hooks/use-auto-dj.ts b/src/renderer/features/player/hooks/use-auto-dj.ts index 6ccb4e361..60b8aa4ae 100644 --- a/src/renderer/features/player/hooks/use-auto-dj.ts +++ b/src/renderer/features/player/hooks/use-auto-dj.ts @@ -79,8 +79,10 @@ export const useAutoDJ = () => { !hasMusicFolder || (hasMusicFolder && hasSimilarSongsMusicFolder); const runnerDepsBase = { + allowDuplicates: settings.allowDuplicates, itemCount: settings.itemCount, musicFolderId, + onlySimilar: settings.onlySimilar, queryClient, server, serverId, @@ -165,8 +167,10 @@ export const useAutoDJ = () => { serverId, settings.enabled, settings.albumStrategy, + settings.allowDuplicates, settings.itemCount, settings.mode, + settings.onlySimilar, settings.songStrategy, settings.timing, ]); diff --git a/src/renderer/features/settings/components/playback/auto-dj-settings.tsx b/src/renderer/features/settings/components/playback/auto-dj-settings.tsx index c126a0113..eb839aa88 100644 --- a/src/renderer/features/settings/components/playback/auto-dj-settings.tsx +++ b/src/renderer/features/settings/components/playback/auto-dj-settings.tsx @@ -15,6 +15,7 @@ import { import { NumberInput } from '/@/shared/components/number-input/number-input'; import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control'; import { Select } from '/@/shared/components/select/select'; +import { Switch } from '/@/shared/components/switch/switch'; export const AutoDJSettings = memo(() => { const { t } = useTranslation(); @@ -145,6 +146,40 @@ export const AutoDJSettings = memo(() => { }), title: t('setting.autoDJ_timing'), }, + { + control: ( + { + setSettings({ + autoDJ: { + allowDuplicates: e.currentTarget.checked, + }, + }); + }} + /> + ), + description: t('setting.autoDJ_allowDuplicates_description'), + title: t('setting.autoDJ_allowDuplicates'), + }, + { + control: ( + { + setSettings({ + autoDJ: { + onlySimilar: e.currentTarget.checked, + }, + }); + }} + /> + ), + description: t('setting.autoDJ_onlySimilar_description'), + title: t('setting.autoDJ_onlySimilar'), + }, ]; return ; diff --git a/src/renderer/global.d.ts b/src/renderer/global.d.ts index 414b9151c..15b2fdd4f 100644 --- a/src/renderer/global.d.ts +++ b/src/renderer/global.d.ts @@ -2,9 +2,11 @@ declare global { interface Window { ANALYTICS_DISABLED?: boolean | string; FS_AUTO_DJ_ALBUM_STRATEGY?: string; + FS_AUTO_DJ_ALLOW_DUPLICATES?: string; FS_AUTO_DJ_ENABLED?: string; FS_AUTO_DJ_ITEM_COUNT?: string; FS_AUTO_DJ_MODE?: string; + FS_AUTO_DJ_ONLY_SIMILAR?: string; FS_AUTO_DJ_SONG_STRATEGY?: string; FS_AUTO_DJ_TIMING?: string; FS_CSS_CONTENT?: string; diff --git a/src/renderer/store/env-settings-overrides.ts b/src/renderer/store/env-settings-overrides.ts index 4887ddb5a..d0b975474 100644 --- a/src/renderer/store/env-settings-overrides.ts +++ b/src/renderer/store/env-settings-overrides.ts @@ -461,9 +461,11 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [ path: ['autoDJ', 'albumStrategy'], type: 'enum', }, + { key: 'FS_AUTO_DJ_ALLOW_DUPLICATES', path: ['autoDJ', 'allowDuplicates'], type: 'bool' }, { key: 'FS_AUTO_DJ_ENABLED', path: ['autoDJ', 'enabled'], type: 'bool' }, { key: 'FS_AUTO_DJ_ITEM_COUNT', path: ['autoDJ', 'itemCount'], type: 'num' }, { enumSet: AUTO_DJ_MODES, key: 'FS_AUTO_DJ_MODE', path: ['autoDJ', 'mode'], type: 'enum' }, + { key: 'FS_AUTO_DJ_ONLY_SIMILAR', path: ['autoDJ', 'onlySimilar'], type: 'bool' }, { enumSet: AUTO_DJ_STRATEGIES, key: 'FS_AUTO_DJ_SONG_STRATEGY', diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index ad0a13282..ff5af1637 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -746,9 +746,11 @@ const autoDjStrategyEnum = z.enum(['similar', 'library_random']); const AutoDJSettingsSchema = z.object({ albumStrategy: autoDjStrategyEnum, + allowDuplicates: z.boolean(), enabled: z.boolean(), itemCount: z.number(), mode: z.enum(['songs', 'albums']), + onlySimilar: z.boolean(), songStrategy: autoDjStrategyEnum, timing: z.number(), }); @@ -1219,9 +1221,11 @@ const platformDefaultWindowBarStyle: Platform = getPlatformDefaultWindowBarStyle const initialState: SettingsState = { autoDJ: { albumStrategy: AUTO_DJ_STRATEGY.SIMILAR, + allowDuplicates: false, enabled: false, itemCount: 5, mode: 'songs', + onlySimilar: false, songStrategy: AUTO_DJ_STRATEGY.SIMILAR, timing: 1, },