From 65a7c3440bc456425f03a9da5b2030d35d4c8085 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 7 Dec 2025 15:04:39 -0800 Subject: [PATCH] add player autodj (#7) --- src/i18n/locales/en.json | 6 + .../api/navidrome/navidrome-controller.ts | 40 +--- .../audio-player/hooks/use-player-events.ts | 4 +- .../player/components/audio-players.tsx | 2 + .../player/components/right-controls.tsx | 31 +++ .../features/player/hooks/use-auto-dj.ts | 192 ++++++++++++++++++ .../components/playback/auto-dj-settings.tsx | 72 +++++++ .../components/playback/playback-tab.tsx | 3 + src/renderer/features/songs/api/songs-api.ts | 20 +- src/renderer/store/player.store.ts | 40 ++-- src/renderer/store/settings.store.ts | 14 ++ src/renderer/store/utils.ts | 146 +++++++++++++ src/renderer/utils/logger-message.ts | 2 + src/shared/types/domain-types.ts | 1 - 14 files changed, 514 insertions(+), 59 deletions(-) create mode 100644 src/renderer/features/player/hooks/use-auto-dj.ts create mode 100644 src/renderer/features/settings/components/playback/auto-dj-settings.tsx diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index a4b4d3fa9..64177b483 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -624,6 +624,12 @@ } }, "setting": { + "autoDJ": "auto DJ", + "autoDJ_description": "automatically add similar songs to the queue", + "autoDJ_itemCount": "item count", + "autoDJ_itemCount_description": "the number of items attempted to be added to the queue when auto DJ is enabled", + "autoDJ_timing": "timing", + "autoDJ_timing_description": "the number of songs remaining in the queue before auto DJ is triggered", "accentColor_description": "sets the accent color for the application", "accentColor": "accent color", "albumBackground_description": "adds a background image for album pages containing the album art", diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index a0232745b..45959fa06 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -17,7 +17,6 @@ import { PlaylistSongListArgs, PlaylistSongListResponse, ServerListItemWithCredential, - Song, songListSortMap, sortOrderMap, tagListSortMap, @@ -575,42 +574,15 @@ export const NavidromeController: InternalControllerEndpoint = { }, }); - if (res.status === 200 && res.body.similarSongs?.song) { - const similar = res.body.similarSongs.song.reduce((acc, song) => { - if (song.id !== query.songId) { - acc.push(ssNormalize.song(song, apiClientProps.server)); - } - - return acc; - }, []); - - if (similar.length > 0) { - return similar; - } - } - - const fallback = await ndApiClient(apiClientProps).getSongList({ - query: { - _end: 50, - _order: 'ASC', - _sort: NDSongListSort.RANDOM, - _start: 0, - [getArtistSongKey(apiClientProps.server)]: query.albumArtistIds, - ...excludeMissing(apiClientProps.server), - }, - }); - - if (fallback.status !== 200) { + if (res.status !== 200) { throw new Error('Failed to get similar songs'); } - return fallback.body.data.reduce((acc, song) => { - if (song.id !== query.songId) { - acc.push(ndNormalize.song(song, apiClientProps.server)); - } - - return acc; - }, []); + return ( + (res.body.similarSongs?.song || []) + .filter((song) => song.id !== query.songId) + .map((song) => ssNormalize.song(song, apiClientProps.server)) || [] + ); }, getSongDetail: async (args) => { const { apiClientProps, query } = args; diff --git a/src/renderer/features/player/audio-player/hooks/use-player-events.ts b/src/renderer/features/player/audio-player/hooks/use-player-events.ts index 11179e740..8aebd6a9a 100644 --- a/src/renderer/features/player/audio-player/hooks/use-player-events.ts +++ b/src/renderer/features/player/audio-player/hooks/use-player-events.ts @@ -22,8 +22,8 @@ interface PlayerEvents { interface PlayerEventsCallbacks { onCurrentSongChange?: ( - properties: { index: number; song: QueueSong | undefined }, - prev: { index: number; song: QueueSong | undefined }, + properties: { index: number; remaining: number; song: QueueSong | undefined }, + prev: { index: number; remaining: number; song: QueueSong | undefined }, ) => void; onPlayerMute?: (properties: { muted: boolean }, prev: { muted: boolean }) => void; onPlayerProgress?: (properties: { timestamp: number }, prev: { timestamp: number }) => void; diff --git a/src/renderer/features/player/components/audio-players.tsx b/src/renderer/features/player/components/audio-players.tsx index aa3ef0133..70db0c5f5 100644 --- a/src/renderer/features/player/components/audio-players.tsx +++ b/src/renderer/features/player/components/audio-players.tsx @@ -7,6 +7,7 @@ import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc' import { useMainPlayerListener } from '/@/renderer/features/player/audio-player/hooks/use-main-player-listener'; import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player'; import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player'; +import { useAutoDJ } from '/@/renderer/features/player/hooks/use-auto-dj'; import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session'; import { useMPRIS } from '/@/renderer/features/player/hooks/use-mpris'; import { usePlaybackHotkeys } from '/@/renderer/features/player/hooks/use-playback-hotkeys'; @@ -44,6 +45,7 @@ export const AudioPlayers = () => { useMainPlayerListener(); useMediaSession(); usePlaybackHotkeys(); + useAutoDJ(); useEffect(() => { if (webAudio && 'AudioContext' in window) { diff --git a/src/renderer/features/player/components/right-controls.tsx b/src/renderer/features/player/components/right-controls.tsx index 6f48aa2b0..9d3b26f5a 100644 --- a/src/renderer/features/player/components/right-controls.tsx +++ b/src/renderer/features/player/components/right-controls.tsx @@ -11,6 +11,7 @@ import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete- import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { useAppStoreActions, + useAutoDJSettings, useCurrentServer, useGeneralSettings, useHotkeySettings, @@ -19,9 +20,11 @@ import { usePlayerSong, usePlayerVolume, useSettingsStore, + useSettingsStoreActions, useSidebarRightExpanded, } from '/@/renderer/store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; +import { Button } from '/@/shared/components/button/button'; import { Flex } from '/@/shared/components/flex/flex'; import { Group } from '/@/shared/components/group/group'; import { Rating } from '/@/shared/components/rating/rating'; @@ -58,6 +61,7 @@ export const RightControls = () => { + @@ -70,6 +74,33 @@ export const RightControls = () => { ); }; +const AutoDJButton = () => { + const { t } = useTranslation(); + const settings = useAutoDJSettings(); + const { setSettings } = useSettingsStoreActions(); + + const toggleAutoDJ = () => { + setSettings({ + autoDJ: { + ...settings, + enabled: !settings.enabled, + }, + }); + }; + + return ( + + ); +}; + const QueueButton = () => { const { t } = useTranslation(); const isSidebarRightExpanded = useSidebarRightExpanded(); diff --git a/src/renderer/features/player/hooks/use-auto-dj.ts b/src/renderer/features/player/hooks/use-auto-dj.ts new file mode 100644 index 000000000..914bcd1bb --- /dev/null +++ b/src/renderer/features/player/hooks/use-auto-dj.ts @@ -0,0 +1,192 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useEffect } from 'react'; + +import { queryKeys } from '/@/renderer/api/query-keys'; +import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context'; +import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; +import { + isShuffleEnabled, + mapShuffledToQueueIndex, + useAutoDJSettings, + useCurrentServerId, + usePlayerStore, + usePlayerStoreBase, +} from '/@/renderer/store'; +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 { Play } from '/@/shared/types/types'; + +export const useAutoDJ = () => { + const queryClient = useQueryClient(); + const serverId = useCurrentServerId(); + const player = usePlayer(); + const settings = useAutoDJSettings(); + const isFetching = useIsPlayerFetching(); + + useEffect(() => { + const unsubscribe = usePlayerStoreBase.subscribe( + (state) => { + const queue = state.getQueue(); + let index = state.player.index; + let remaining: number; + + if (isShuffleEnabled(state)) { + remaining = state.queue.shuffled.length - index - 1; + index = mapShuffledToQueueIndex(index, state.queue.shuffled); + } else { + remaining = queue.items.slice(index + 1).length; + } + + return { index, remaining, song: queue.items[index] }; + }, + async (properties) => { + if (!settings.enabled) { + return; + } + + // If the player is fetching, don't autoplay + if (isFetching) { + return; + } + + // If no current song, don't autoplay + if (!properties.song?.id) { + return; + } + + if (properties.remaining >= settings.timing) { + return; + } + + logFn.debug(logMsg[LogCategory.PLAYER].autoPlayTriggered, { + category: LogCategory.PLAYER, + meta: { remaining: properties.remaining, songId: properties.song?.id }, + }); + + 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), + ); + + // 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 genreSimilarSongs = await queryClient.fetchQuery({ + ...songsQueries.random({ + query: { + genre: genre.id, + limit: 50, + played: Played.All, + }, + serverId, + }), + queryKey: queryKeys.player.fetch({ + genre, + similarSongs: properties.song?.id, + }), + }); + + uniqueSimilarSongs.push( + ...genreSimilarSongs.items.filter( + (song) => !queueSongIdSet.has(song.id), + ), + ); + } + } + + // If not enough songs, try to fetch more similar songs based on the album artist of the current song + if (uniqueSimilarSongs.length < settings.itemCount) { + const albumArtist = properties.song?.albumArtists?.[0]; + + if (albumArtist) { + const albumArtistSimilarSongs = await queryClient.fetchQuery({ + ...songsQueries.list({ + query: { + albumArtistIds: [albumArtist.id], + limit: 50, + sortBy: SongListSort.RANDOM, + sortOrder: SortOrder.ASC, + startIndex: 0, + }, + serverId, + }), + queryKey: queryKeys.player.fetch({ + albumArtist, + similarSongs: properties.song?.id, + }), + }); + + uniqueSimilarSongs.push( + ...albumArtistSimilarSongs.items.filter( + (song) => !queueSongIdSet.has(song.id), + ), + ); + } + } + + // If not enough songs, just fetch fully random songs + if (uniqueSimilarSongs.length < settings.itemCount) { + const randomSongs = await queryClient.fetchQuery({ + ...songsQueries.random({ + query: { limit: 50, played: Played.All }, + serverId, + }), + }); + + uniqueSimilarSongs.push( + ...randomSongs.items.filter((song) => !queueSongIdSet.has(song.id)), + ); + } + + // Shuffle the songs and then add to the queue + const shuffledSongs = shuffleInPlace(uniqueSimilarSongs); + + // Splice the first itemCount songs and add to the queue + const songsToAdd = shuffledSongs.slice(0, settings.itemCount); + + // Add to the end of the queue + player.addToQueueByData(songsToAdd, Play.LAST); + } catch (error) { + logFn.error(logMsg[LogCategory.PLAYER].autoPlayFailed, { + category: LogCategory.PLAYER, + meta: { error: (error as Error).message, songId: properties.song?.id }, + }); + } + }, + { + equalityFn: (a, b) => { + return a.song?._uniqueId === b.song?._uniqueId; + }, + }, + ); + + return () => unsubscribe(); + }, [ + isFetching, + player, + queryClient, + serverId, + settings.enabled, + settings.itemCount, + 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 new file mode 100644 index 000000000..66f34d232 --- /dev/null +++ b/src/renderer/features/settings/components/playback/auto-dj-settings.tsx @@ -0,0 +1,72 @@ +import { useTranslation } from 'react-i18next'; + +import { + SettingOption, + SettingsSection, +} from '/@/renderer/features/settings/components/settings-section'; +import { useAutoDJSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; +import { NumberInput } from '/@/shared/components/number-input/number-input'; + +export const AutoDJSettings = () => { + const { t } = useTranslation(); + const settings = useAutoDJSettings(); + const { setSettings } = useSettingsStoreActions(); + + const autoDJOptions: SettingOption[] = [ + { + control: ( + { + setSettings({ + autoDJ: { + ...settings, + itemCount: Number(e), + }, + }); + }} + value={Number(settings.itemCount)} + /> + ), + description: t('setting.autoDJ_itemCount', { + context: 'description', + postProcess: 'sentenceCase', + }), + title: t('setting.autoDJ_itemCount', { postProcess: 'sentenceCase' }), + }, + { + control: ( + { + setSettings({ + autoDJ: { + ...settings, + timing: Number(e), + }, + }); + }} + value={Number(settings.timing)} + /> + ), + description: t('setting.autoDJ_timing', { + context: 'description', + postProcess: 'sentenceCase', + }), + title: t('setting.autoDJ_timing', { postProcess: 'sentenceCase' }), + }, + ]; + + return ( + + ); +}; diff --git a/src/renderer/features/settings/components/playback/playback-tab.tsx b/src/renderer/features/settings/components/playback/playback-tab.tsx index aac18f0c7..107696f5d 100644 --- a/src/renderer/features/settings/components/playback/playback-tab.tsx +++ b/src/renderer/features/settings/components/playback/playback-tab.tsx @@ -2,6 +2,7 @@ import isElectron from 'is-electron'; import { lazy, Suspense, useMemo } from 'react'; import { AudioSettings } from '/@/renderer/features/settings/components/playback/audio-settings'; +import { AutoDJSettings } from '/@/renderer/features/settings/components/playback/auto-dj-settings'; import { PlayerFilterSettings } from '/@/renderer/features/settings/components/playback/player-filter-settings'; import { TranscodeSettings } from '/@/renderer/features/settings/components/playback/transcode-settings'; import { useSettingsStore } from '/@/renderer/store'; @@ -34,6 +35,8 @@ export const PlaybackTab = () => { + + ); }; diff --git a/src/renderer/features/songs/api/songs-api.ts b/src/renderer/features/songs/api/songs-api.ts index ac52bffaa..870d747b2 100644 --- a/src/renderer/features/songs/api/songs-api.ts +++ b/src/renderer/features/songs/api/songs-api.ts @@ -4,7 +4,12 @@ import { api } from '/@/renderer/api'; import { controller } from '/@/renderer/api/controller'; import { queryKeys } from '/@/renderer/api/query-keys'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { ListCountQuery, SimilarSongsQuery, SongListQuery } from '/@/shared/types/domain-types'; +import { + ListCountQuery, + RandomSongListQuery, + SimilarSongsQuery, + SongListQuery, +} from '/@/shared/types/domain-types'; export const songsQueries = { list: (args: QueryHookArgs, imageSize?: number) => { @@ -36,13 +41,24 @@ export const songsQueries = { ...args.options, }); }, + random: (args: QueryHookArgs) => { + return queryOptions({ + queryFn: ({ signal }) => { + return api.controller.getRandomSongList({ + apiClientProps: { serverId: args.serverId, signal }, + query: args.query, + }); + }, + queryKey: queryKeys.songs.randomSongList(args.serverId, args.query), + ...args.options, + }); + }, similar: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { return api.controller.getSimilarSongs({ apiClientProps: { serverId: args.serverId, signal }, query: { - albumArtistIds: args.query.albumArtistIds, count: args.query.count ?? 50, songId: args.query.songId, }, diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 2bbcdc550..6320cd6c5 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -130,6 +130,26 @@ export function calculateNextSong( } } +// Helper function to check if shuffle is enabled and not in priority mode +export function isShuffleEnabled(state: { + player: { queueType: PlayerQueueType; shuffle: PlayerShuffle }; + queue: { shuffled: number[] }; +}): boolean { + return ( + state.player.shuffle === PlayerShuffle.TRACK && + state.queue.shuffled.length > 0 && + state.player.queueType !== PlayerQueueType.PRIORITY + ); +} + +// Helper function to map shuffled position to actual queue position +export function mapShuffledToQueueIndex(shuffledIndex: number, shuffled: number[]): number { + if (shuffledIndex >= 0 && shuffledIndex < shuffled.length) { + return shuffled[shuffledIndex]; + } + return shuffledIndex; +} + // Helper function to add new indexes to shuffled array after current position function addIndexesToShuffled( shuffled: number[], @@ -206,26 +226,6 @@ function getCombinedQueueLength(priority: string[], defaultQueue: string[]): num return priority.length + defaultQueue.length; } -// Helper function to check if shuffle is enabled and not in priority mode -function isShuffleEnabled(state: { - player: { queueType: PlayerQueueType; shuffle: PlayerShuffle }; - queue: { shuffled: number[] }; -}): boolean { - return ( - state.player.shuffle === PlayerShuffle.TRACK && - state.queue.shuffled.length > 0 && - state.player.queueType !== PlayerQueueType.PRIORITY - ); -} - -// Helper function to map shuffled position to actual queue position -function mapShuffledToQueueIndex(shuffledIndex: number, shuffled: number[]): number { - if (shuffledIndex >= 0 && shuffledIndex < shuffled.length) { - return shuffled[shuffledIndex]; - } - return shuffledIndex; -} - // Helper function to regenerate shuffled indexes if shuffle is enabled function regenerateShuffledIndexesIfNeeded(state: { player: { queueType: PlayerQueueType; shuffle: PlayerShuffle }; diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 5b46f6a5b..238df3fa7 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -401,10 +401,17 @@ const QueryBuilderSettingsSchema = z.object({ tag: z.array(QueryBuilderCustomFieldSchema), }); +const AutoDJSettingsSchema = z.object({ + enabled: z.boolean(), + itemCount: z.number(), + timing: z.number(), +}); + /** * This schema is used for validation of the imported settings json */ export const ValidationSettingsStateSchema = z.object({ + autoDJ: AutoDJSettingsSchema, css: CssSettingsSchema, discord: DiscordSettingsSchema, font: FontSettingsSchema, @@ -659,6 +666,11 @@ const getPlatformDefaultWindowBarStyle = (): Platform => { const platformDefaultWindowBarStyle: Platform = getPlatformDefaultWindowBarStyle(); const initialState: SettingsState = { + autoDJ: { + enabled: false, + itemCount: 10, + timing: 3, + }, css: { content: '', enabled: false, @@ -1529,3 +1541,5 @@ export const usePrimaryColor = () => useSettingsStore((store) => store.general.a export const usePlayerbarSlider = () => useSettingsStore((store) => store.general.playerbarSlider); export const useGenreTarget = () => useSettingsStore((store) => store.general.genreTarget); + +export const useAutoDJSettings = () => useSettingsStore((store) => store.autoDJ, shallow); diff --git a/src/renderer/store/utils.ts b/src/renderer/store/utils.ts index 865326934..40aa00426 100644 --- a/src/renderer/store/utils.ts +++ b/src/renderer/store/utils.ts @@ -30,3 +30,149 @@ export const idbStateStorage: StateStorage = { await set(name, value); }, }; + +const settingsKeys = [ + 'store_settings_autoDJ', + 'store_settings_general', + 'store_settings_lists', + 'store_settings_hotkeys', + 'store_settings_playback', + 'store_settings_lyrics', + 'store_settings_window', + 'store_settings_discord', + 'store_settings_font', + 'store_settings_css', + 'store_settings_remote', + 'store_settings_queryBuilder', + 'store_settings_tab', +]; + +export const splitSettingsStorage: StateStorage = { + getItem: (name: string): null | string => { + if (name !== 'store_settings') { + return localStorage.getItem(name); + } + + // Read from all split keys and merge them + const keys = settingsKeys; + + // Check if old single key exists (for migration) + const oldKeyRaw = localStorage.getItem('store_settings'); + if (oldKeyRaw && !localStorage.getItem('store_settings_general')) { + // Only migrate if split keys don't exist yet + try { + const oldData = JSON.parse(oldKeyRaw); + const splitData: Record = {}; + const state = oldData.state || oldData; + + if (state && typeof state === 'object') { + splitData.general = state.general; + splitData.lists = state.lists; + splitData.hotkeys = state.hotkeys; + splitData.playback = state.playback; + splitData.lyrics = state.lyrics; + splitData.window = state.window; + splitData.discord = state.discord; + splitData.font = state.font; + splitData.css = state.css; + splitData.remote = state.remote; + splitData.queryBuilder = state.queryBuilder; + splitData.tab = state.tab; + + // Save to new split keys + keys.forEach((key) => { + const keyName = key.replace('store_settings_', ''); + if (splitData[keyName] !== undefined) { + localStorage.setItem(key, JSON.stringify(splitData[keyName])); + } + }); + + // Store version if it exists + if (oldData.version !== undefined) { + localStorage.setItem('store_settings_version', oldData.version.toString()); + } + } + } catch (e) { + // If parsing fails, continue with reading from split keys + console.warn('Failed to migrate old settings format:', e); + } + } + + // Read from all split keys + const mergedState: Record = {}; + let hasData = false; + + keys.forEach((key) => { + const value = localStorage.getItem(key); + if (value) { + try { + const keyName = key.replace('store_settings_', ''); + mergedState[keyName] = JSON.parse(value); + hasData = true; + } catch (e) { + console.warn(`Failed to parse ${key}:`, e); + } + } + }); + + if (!hasData) { + return null; + } + + const versionKey = localStorage.getItem('store_settings_version'); + const version = versionKey ? parseInt(versionKey, 10) : 14; + + return JSON.stringify({ + state: mergedState, + version, + }); + }, + + removeItem: (name: string): void => { + if (name !== 'store_settings') { + localStorage.removeItem(name); + return; + } + + // Remove all split keys + const keys = settingsKeys; + + keys.forEach((key) => { + localStorage.removeItem(key); + }); + + // Also remove old key if it exists + localStorage.removeItem('store_settings'); + }, + + setItem: (name: string, value: string): void => { + if (name !== 'store_settings') { + localStorage.setItem(name, value); + return; + } + + try { + const data = JSON.parse(value); + const state = data.state || data; + + const keys = settingsKeys.map((key) => ({ + key, + value: state[key as keyof typeof state], + })); + + keys.forEach(({ key, value: keyValue }) => { + if (keyValue !== undefined) { + localStorage.setItem(key, JSON.stringify(keyValue)); + } + }); + + // Store version separately + if (data.version !== undefined) { + localStorage.setItem('store_settings_version', data.version.toString()); + } + } catch (e) { + console.error('Failed to split settings storage:', e); + localStorage.setItem(name, value); + } + }, +}; diff --git a/src/renderer/utils/logger-message.ts b/src/renderer/utils/logger-message.ts index 59d3b3007..39d1067e3 100644 --- a/src/renderer/utils/logger-message.ts +++ b/src/renderer/utils/logger-message.ts @@ -23,6 +23,8 @@ export const logMsg = { addToQueueByFetch: 'Added to queue by fetch', addToQueueByListQuery: 'Added to queue by list query', addToQueueByType: 'Added to queue by type', + autoPlayFailed: 'Auto play failed', + autoPlayTriggered: 'Auto play triggered', cancelledFetch: 'Cancelled fetch', clearQueue: 'Cleared queue', clearSelected: 'Cleared selected', diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts index fa01b5241..d7a695ea3 100644 --- a/src/shared/types/domain-types.ts +++ b/src/shared/types/domain-types.ts @@ -1449,7 +1449,6 @@ export type SimilarSongsArgs = BaseEndpointArgs & { }; export type SimilarSongsQuery = { - albumArtistIds: string[]; count?: number; songId: string; };