mirror of
https://github.com/jeffvli/feishin.git
synced 2026-08-08 21:32:59 +02:00
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
This commit is contained in:
@@ -128,9 +128,11 @@ Applies to the default lyrics display profile (`lyricsDisplay.default`).
|
|||||||
| Setting path | Default | Env variable | Available values / Description |
|
| Setting path | Default | Env variable | Available values / Description |
|
||||||
|-------------|---------|--------------|--------------------------------|
|
|-------------|---------|--------------|--------------------------------|
|
||||||
| `autoDJ.albumStrategy` | `similar` | `FS_AUTO_DJ_ALBUM_STRATEGY` | `similar` / `library_random`. |
|
| `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.enabled` | `false` | `FS_AUTO_DJ_ENABLED` | `true` / `false`. |
|
||||||
| `autoDJ.itemCount` | `5` | `FS_AUTO_DJ_ITEM_COUNT` | Number of items to add. |
|
| `autoDJ.itemCount` | `5` | `FS_AUTO_DJ_ITEM_COUNT` | Number of items to add. |
|
||||||
| `autoDJ.mode` | `songs` | `FS_AUTO_DJ_MODE` | `songs` / `albums`. |
|
| `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.songStrategy` | `similar` | `FS_AUTO_DJ_SONG_STRATEGY` | `similar` / `library_random`. |
|
||||||
| `autoDJ.timing` | `1` | `FS_AUTO_DJ_TIMING` | Timing value (number). |
|
| `autoDJ.timing` | `1` | `FS_AUTO_DJ_TIMING` | Timing value (number). |
|
||||||
|
|
||||||
|
|||||||
@@ -93,9 +93,11 @@ window.FS_LYRICS_PADDING_LEFT = "${FS_LYRICS_PADDING_LEFT}";
|
|||||||
window.FS_LYRICS_PADDING_RIGHT = "${FS_LYRICS_PADDING_RIGHT}";
|
window.FS_LYRICS_PADDING_RIGHT = "${FS_LYRICS_PADDING_RIGHT}";
|
||||||
|
|
||||||
window.FS_AUTO_DJ_ALBUM_STRATEGY = "${FS_AUTO_DJ_ALBUM_STRATEGY}";
|
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_ENABLED = "${FS_AUTO_DJ_ENABLED}";
|
||||||
window.FS_AUTO_DJ_ITEM_COUNT = "${FS_AUTO_DJ_ITEM_COUNT}";
|
window.FS_AUTO_DJ_ITEM_COUNT = "${FS_AUTO_DJ_ITEM_COUNT}";
|
||||||
window.FS_AUTO_DJ_MODE = "${FS_AUTO_DJ_MODE}";
|
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_SONG_STRATEGY = "${FS_AUTO_DJ_SONG_STRATEGY}";
|
||||||
window.FS_AUTO_DJ_TIMING = "${FS_AUTO_DJ_TIMING}";
|
window.FS_AUTO_DJ_TIMING = "${FS_AUTO_DJ_TIMING}";
|
||||||
|
|
||||||
|
|||||||
@@ -796,6 +796,10 @@
|
|||||||
"autoDJ_songStrategy": "Song selection mode",
|
"autoDJ_songStrategy": "Song selection mode",
|
||||||
"autoDJ_strategy_option_library_random": "Random",
|
"autoDJ_strategy_option_library_random": "Random",
|
||||||
"autoDJ_strategy_option_similar": "Similar",
|
"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": "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.",
|
"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",
|
"autosaveCount": "Automatic play queue save frequency",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { QueryClient } from '@tanstack/react-query';
|
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 { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
@@ -16,9 +16,11 @@ import {
|
|||||||
|
|
||||||
export type AutoDjAlbumCollectArgs = {
|
export type AutoDjAlbumCollectArgs = {
|
||||||
albumStrategy: AutoDJStrategy;
|
albumStrategy: AutoDJStrategy;
|
||||||
|
allowDuplicates: boolean;
|
||||||
currentSong: QueueSong;
|
currentSong: QueueSong;
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
musicFolderId: string | string[] | undefined;
|
musicFolderId: string | string[] | undefined;
|
||||||
|
onlySimilar: boolean;
|
||||||
queryClient: QueryClient;
|
queryClient: QueryClient;
|
||||||
queueAlbumIdSet: Set<string>;
|
queueAlbumIdSet: Set<string>;
|
||||||
server: null | ServerListItem | undefined;
|
server: null | ServerListItem | undefined;
|
||||||
@@ -37,6 +39,19 @@ export const runAutoDjAlbumIds = async (args: AutoDjAlbumCollectArgs): Promise<s
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isAlbumIdAvailable = (
|
||||||
|
albumId: string,
|
||||||
|
allowDuplicates: boolean,
|
||||||
|
queueAlbumIdSet: Set<string>,
|
||||||
|
selectedAlbumIdSet: Set<string>,
|
||||||
|
) => {
|
||||||
|
if (allowDuplicates) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !queueAlbumIdSet.has(albumId) && !selectedAlbumIdSet.has(albumId);
|
||||||
|
};
|
||||||
|
|
||||||
const collectAlbumsLibraryRandom = async (args: AutoDjAlbumCollectArgs): Promise<string[]> => {
|
const collectAlbumsLibraryRandom = async (args: AutoDjAlbumCollectArgs): Promise<string[]> => {
|
||||||
const page = await args.queryClient.fetchQuery({
|
const page = await args.queryClient.fetchQuery({
|
||||||
...albumQueries.list({
|
...albumQueries.list({
|
||||||
@@ -52,14 +67,18 @@ const collectAlbumsLibraryRandom = async (args: AutoDjAlbumCollectArgs): Promise
|
|||||||
queryKey: queryKeys.player.fetch({ autoDjAlbumLibraryRandom: args.currentSong?.id }),
|
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);
|
return shuffle(ids).slice(0, args.itemCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<string[]> => {
|
const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<string[]> => {
|
||||||
const targetAlbumCount = args.itemCount;
|
const selectedAlbumIds: string[] = [];
|
||||||
const candidateAlbumIds: string[] = [];
|
const selectedAlbumIdSet = new Set<string>();
|
||||||
const seenAlbumCandidates = new Set<string>();
|
const remainingCount = () => args.itemCount - selectedAlbumIds.length;
|
||||||
|
|
||||||
if (args.trySimilarSongs && args.currentSong?.id) {
|
if (args.trySimilarSongs && args.currentSong?.id) {
|
||||||
const similarSongsFromSimilarApi = await args.queryClient.fetchQuery({
|
const similarSongsFromSimilarApi = await args.queryClient.fetchQuery({
|
||||||
@@ -75,24 +94,46 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<strin
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
autoDjPushUniqueAlbumIds(
|
const similarSlotsToFill = remainingCount();
|
||||||
candidateAlbumIds,
|
const shuffledSimilarAlbumIds = shuffle(
|
||||||
seenAlbumCandidates,
|
similarSongsFromSimilarApi
|
||||||
args.queueAlbumIdSet,
|
.map((song) => song.albumId)
|
||||||
...similarSongsFromSimilarApi.map((s) => s.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];
|
const genre = args.currentSong.genres?.[0];
|
||||||
|
|
||||||
if (genre) {
|
if (genre) {
|
||||||
const genreIds = autoDjGenreIdsForSongGenre(genre, args.server.type);
|
const genreIds = autoDjGenreIdsForSongGenre(genre, args.server.type);
|
||||||
|
const genreLimit = 50;
|
||||||
|
|
||||||
const genreAlbums = await args.queryClient.fetchQuery({
|
const genreAlbums = await args.queryClient.fetchQuery({
|
||||||
...albumQueries.list({
|
...albumQueries.list({
|
||||||
query: {
|
query: {
|
||||||
genreIds,
|
genreIds,
|
||||||
limit: 50,
|
limit: genreLimit,
|
||||||
musicFolderId: args.musicFolderId,
|
musicFolderId: args.musicFolderId,
|
||||||
sortBy: AlbumListSort.RANDOM,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
@@ -106,15 +147,8 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<strin
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
autoDjPushUniqueAlbumIds(
|
|
||||||
candidateAlbumIds,
|
|
||||||
seenAlbumCandidates,
|
|
||||||
args.queueAlbumIdSet,
|
|
||||||
...genreAlbums.items.map((album) => album.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!args.trySimilarSongs) {
|
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({
|
const randomAlbumsMix = await args.queryClient.fetchQuery({
|
||||||
...albumQueries.list({
|
...albumQueries.list({
|
||||||
query: {
|
query: {
|
||||||
@@ -131,17 +165,56 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<strin
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
autoDjPushUniqueAlbumIds(
|
const spiceSlotsToFill = Math.min(randomAlbumMixCount, remainingCount());
|
||||||
candidateAlbumIds,
|
const shuffledSpiceAlbumIds = shuffle(
|
||||||
seenAlbumCandidates,
|
randomAlbumsMix.items
|
||||||
args.queueAlbumIdSet,
|
.map((album) => album.id)
|
||||||
...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];
|
const albumArtist = args.currentSong.albumArtists?.[0];
|
||||||
|
|
||||||
if (albumArtist) {
|
if (albumArtist) {
|
||||||
@@ -163,16 +236,34 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<strin
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
autoDjPushUniqueAlbumIds(
|
const artistSlotsToFill = remainingCount();
|
||||||
candidateAlbumIds,
|
const shuffledArtistAlbumIds = shuffle(
|
||||||
seenAlbumCandidates,
|
albumsByArtist.items
|
||||||
args.queueAlbumIdSet,
|
.map((album) => album.id)
|
||||||
...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({
|
const randomAlbumsFallback = await args.queryClient.fetchQuery({
|
||||||
...albumQueries.list({
|
...albumQueries.list({
|
||||||
query: {
|
query: {
|
||||||
@@ -189,14 +280,27 @@ const collectAlbumsSimilar = async (args: AutoDjAlbumCollectArgs): Promise<strin
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
autoDjPushUniqueAlbumIds(
|
const randomSlotsToFill = remainingCount();
|
||||||
candidateAlbumIds,
|
const shuffledRandomAlbumIds = shuffle(
|
||||||
seenAlbumCandidates,
|
randomAlbumsFallback.items
|
||||||
args.queueAlbumIdSet,
|
.map((album) => album.id)
|
||||||
...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 selectedAlbumIds;
|
||||||
return shuffledAlbums.slice(0, targetAlbumCount);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export type AutoDjSongCollectArgs = {
|
export type AutoDjSongCollectArgs = {
|
||||||
|
allowDuplicates: boolean;
|
||||||
currentSong: QueueSong;
|
currentSong: QueueSong;
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
musicFolderId: string | string[] | undefined;
|
musicFolderId: string | string[] | undefined;
|
||||||
|
onlySimilar: boolean;
|
||||||
queryClient: QueryClient;
|
queryClient: QueryClient;
|
||||||
queueSongIdSet: Set<string>;
|
queueSongIdSet: Set<string>;
|
||||||
server: null | ServerListItem | undefined;
|
server: null | ServerListItem | undefined;
|
||||||
@@ -36,6 +38,19 @@ export const runAutoDjSongs = async (args: AutoDjSongCollectArgs): Promise<Song[
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSongAvailable = (
|
||||||
|
song: Song,
|
||||||
|
allowDuplicates: boolean,
|
||||||
|
queueSongIdSet: Set<string>,
|
||||||
|
selectedSongIds: Set<string>,
|
||||||
|
) => {
|
||||||
|
if (allowDuplicates) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !queueSongIdSet.has(song.id) && !selectedSongIds.has(song.id);
|
||||||
|
};
|
||||||
|
|
||||||
const collectSongsLibraryRandom = async (args: AutoDjSongCollectArgs): Promise<Song[]> => {
|
const collectSongsLibraryRandom = async (args: AutoDjSongCollectArgs): Promise<Song[]> => {
|
||||||
const randomSongs = await args.queryClient.fetchQuery({
|
const randomSongs = await args.queryClient.fetchQuery({
|
||||||
...songsQueries.random({
|
...songsQueries.random({
|
||||||
@@ -48,13 +63,17 @@ const collectSongsLibraryRandom = async (args: AutoDjSongCollectArgs): Promise<S
|
|||||||
queryKey: queryKeys.player.fetch({ autoDjLibraryRandomSongs: args.currentSong.id }),
|
queryKey: queryKeys.player.fetch({ autoDjLibraryRandomSongs: args.currentSong.id }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const pool = randomSongs.items.filter((song) => !args.queueSongIdSet.has(song.id));
|
const pool = randomSongs.items.filter(
|
||||||
|
(song) => args.allowDuplicates || !args.queueSongIdSet.has(song.id),
|
||||||
|
);
|
||||||
const shuffled = shuffleInPlace(pool);
|
const shuffled = shuffleInPlace(pool);
|
||||||
return shuffled.slice(0, args.itemCount);
|
return shuffled.slice(0, args.itemCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]> => {
|
const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]> => {
|
||||||
let uniqueSimilarSongs: Song[] = [];
|
const selected: Song[] = [];
|
||||||
|
const selectedSongIds = new Set<string>();
|
||||||
|
const remainingCount = () => args.itemCount - selected.length;
|
||||||
|
|
||||||
if (args.trySimilarSongs) {
|
if (args.trySimilarSongs) {
|
||||||
const similarSongs = await args.queryClient.fetchQuery({
|
const similarSongs = await args.queryClient.fetchQuery({
|
||||||
@@ -68,10 +87,24 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]>
|
|||||||
queryKey: queryKeys.player.fetch({ similarSongs: args.currentSong?.id }),
|
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];
|
const genre = args.currentSong?.genres?.[0];
|
||||||
|
|
||||||
if (genre) {
|
if (genre) {
|
||||||
@@ -91,10 +124,6 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]>
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const genreSongs = genreSimilarSongs.items.filter(
|
|
||||||
(song) => !args.queueSongIdSet.has(song.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!args.trySimilarSongs) {
|
if (!args.trySimilarSongs) {
|
||||||
const randomSongCount = Math.max(1, Math.ceil(genreLimit * 0.2));
|
const randomSongCount = Math.max(1, Math.ceil(genreLimit * 0.2));
|
||||||
|
|
||||||
@@ -105,19 +134,48 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]>
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueRandomSongs = randomSongs.items.filter(
|
const spiceSlotsToFill = Math.min(randomSongCount, remainingCount());
|
||||||
(song) => !args.queueSongIdSet.has(song.id),
|
const shuffledSpiceSongs = shuffleInPlace(
|
||||||
|
randomSongs.items.filter((song) =>
|
||||||
|
isSongAvailable(
|
||||||
|
song,
|
||||||
|
args.allowDuplicates,
|
||||||
|
args.queueSongIdSet,
|
||||||
|
selectedSongIds,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const randomSongsToAdd = uniqueRandomSongs.slice(0, randomSongCount);
|
for (const song of shuffledSpiceSongs.slice(0, spiceSlotsToFill)) {
|
||||||
uniqueSimilarSongs.push(...randomSongsToAdd, ...genreSongs);
|
selectedSongIds.add(song.id);
|
||||||
} else {
|
selected.push(song);
|
||||||
uniqueSimilarSongs.push(...genreSongs);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
const albumArtist = args.currentSong?.albumArtists?.[0];
|
||||||
|
|
||||||
if (albumArtist) {
|
if (albumArtist) {
|
||||||
@@ -138,15 +196,30 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]>
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
uniqueSimilarSongs.push(
|
const artistSlotsToFill = remainingCount();
|
||||||
...albumArtistSimilarSongs.items.filter(
|
const shuffledArtistSongs = shuffleInPlace(
|
||||||
(song) => !args.queueSongIdSet.has(song.id),
|
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({
|
const randomSongs = await args.queryClient.fetchQuery({
|
||||||
...songsQueries.random({
|
...songsQueries.random({
|
||||||
query: { limit: 50, played: Played.All },
|
query: { limit: 50, played: Played.All },
|
||||||
@@ -154,11 +227,18 @@ const collectSongsSimilar = async (args: AutoDjSongCollectArgs): Promise<Song[]>
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
uniqueSimilarSongs.push(
|
const randomSlotsToFill = remainingCount();
|
||||||
...randomSongs.items.filter((song) => !args.queueSongIdSet.has(song.id)),
|
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 selected;
|
||||||
return shuffledSongs.slice(0, args.itemCount);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,19 +2,6 @@ import type { Genre } from '/@/shared/types/domain-types';
|
|||||||
|
|
||||||
import { ServerType } from '/@/shared/types/domain-types';
|
import { ServerType } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const autoDjPushUniqueAlbumIds = (
|
|
||||||
accumulator: string[],
|
|
||||||
seenAlbums: Set<string>,
|
|
||||||
queueAlbumIdSet: Set<string>,
|
|
||||||
...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[] => {
|
export const autoDjGenreIdsForSongGenre = (genre: Genre, serverType: ServerType): string[] => {
|
||||||
if (serverType === ServerType.JELLYFIN) {
|
if (serverType === ServerType.JELLYFIN) {
|
||||||
return [genre.id];
|
return [genre.id];
|
||||||
|
|||||||
@@ -194,58 +194,111 @@ const AutoDJButton = () => {
|
|||||||
value={settings.mode}
|
value={settings.mode}
|
||||||
w="100%"
|
w="100%"
|
||||||
/>
|
/>
|
||||||
<Select
|
<Paper p="md" radius="md">
|
||||||
comboboxProps={{ withinPortal: false }}
|
<Select
|
||||||
data={strategySelectData}
|
comboboxProps={{ withinPortal: false }}
|
||||||
description={strategyLabels.description}
|
data={strategySelectData}
|
||||||
label={strategyLabels.title}
|
description={strategyLabels.description}
|
||||||
onChange={(value) => {
|
label={strategyLabels.title}
|
||||||
if (!value) return;
|
onChange={(value) => {
|
||||||
setSettings({
|
if (!value) return;
|
||||||
autoDJ:
|
setSettings({
|
||||||
settings.mode === AUTO_DJ_MODE.ALBUMS
|
autoDJ:
|
||||||
? { albumStrategy: value as AutoDJStrategy }
|
settings.mode === AUTO_DJ_MODE.ALBUMS
|
||||||
: { songStrategy: value as AutoDJStrategy },
|
? { albumStrategy: value as AutoDJStrategy }
|
||||||
});
|
: { songStrategy: value as AutoDJStrategy },
|
||||||
}}
|
});
|
||||||
size="md"
|
}}
|
||||||
value={strategyValue}
|
size="sm"
|
||||||
w="100%"
|
value={strategyValue}
|
||||||
/>
|
variant="filled"
|
||||||
<NumberInput
|
w="100%"
|
||||||
aria-label={itemLabels.title}
|
/>
|
||||||
description={itemLabels.description}
|
</Paper>
|
||||||
hideControls={false}
|
<Paper p="md" radius="md">
|
||||||
label={itemLabels.title}
|
<NumberInput
|
||||||
max={50}
|
aria-label={itemLabels.title}
|
||||||
min={1}
|
description={itemLabels.description}
|
||||||
onChange={(e) =>
|
hideControls={false}
|
||||||
setSettings({
|
label={itemLabels.title}
|
||||||
autoDJ: {
|
max={50}
|
||||||
itemCount: Number(e),
|
min={1}
|
||||||
},
|
onChange={(e) =>
|
||||||
})
|
setSettings({
|
||||||
}
|
autoDJ: {
|
||||||
size="md"
|
itemCount: Number(e),
|
||||||
value={Number(settings.itemCount)}
|
},
|
||||||
/>
|
})
|
||||||
<NumberInput
|
}
|
||||||
aria-label={t('setting.autoDJ_timing')}
|
size="sm"
|
||||||
description={t('setting.autoDJ_timing_description')}
|
value={Number(settings.itemCount)}
|
||||||
hideControls={false}
|
variant="filled"
|
||||||
label={t('setting.autoDJ_timing')}
|
/>
|
||||||
max={5}
|
</Paper>
|
||||||
min={1}
|
<Paper p="md" radius="md">
|
||||||
onChange={(e) =>
|
<NumberInput
|
||||||
setSettings({
|
aria-label={t('setting.autoDJ_timing')}
|
||||||
autoDJ: {
|
description={t('setting.autoDJ_timing_description')}
|
||||||
timing: Number(e),
|
hideControls={false}
|
||||||
},
|
label={t('setting.autoDJ_timing')}
|
||||||
})
|
max={5}
|
||||||
}
|
min={1}
|
||||||
size="md"
|
onChange={(e) =>
|
||||||
value={Number(settings.timing)}
|
setSettings({
|
||||||
/>
|
autoDJ: {
|
||||||
|
timing: Number(e),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
value={Number(settings.timing)}
|
||||||
|
variant="filled"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
<Paper p="md" radius="md">
|
||||||
|
<Group align="center" gap="sm" justify="space-between" wrap="nowrap">
|
||||||
|
<Stack gap="xs" style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<Text fw={600} isNoSelect size="sm">
|
||||||
|
{t('setting.autoDJ_allowDuplicates')}
|
||||||
|
</Text>
|
||||||
|
<Text isMuted isNoSelect size="xs">
|
||||||
|
{t('setting.autoDJ_allowDuplicates_description')}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Switch
|
||||||
|
checked={settings.allowDuplicates}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({
|
||||||
|
autoDJ: {
|
||||||
|
allowDuplicates: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
<Paper p="md" radius="md">
|
||||||
|
<Group align="center" gap="sm" justify="space-between" wrap="nowrap">
|
||||||
|
<Stack gap="xs" style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<Text fw={600} isNoSelect size="sm">
|
||||||
|
{t('setting.autoDJ_onlySimilar')}
|
||||||
|
</Text>
|
||||||
|
<Text isMuted isNoSelect size="xs">
|
||||||
|
{t('setting.autoDJ_onlySimilar_description')}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Switch
|
||||||
|
checked={settings.onlySimilar}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings({
|
||||||
|
autoDJ: {
|
||||||
|
onlySimilar: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -79,8 +79,10 @@ export const useAutoDJ = () => {
|
|||||||
!hasMusicFolder || (hasMusicFolder && hasSimilarSongsMusicFolder);
|
!hasMusicFolder || (hasMusicFolder && hasSimilarSongsMusicFolder);
|
||||||
|
|
||||||
const runnerDepsBase = {
|
const runnerDepsBase = {
|
||||||
|
allowDuplicates: settings.allowDuplicates,
|
||||||
itemCount: settings.itemCount,
|
itemCount: settings.itemCount,
|
||||||
musicFolderId,
|
musicFolderId,
|
||||||
|
onlySimilar: settings.onlySimilar,
|
||||||
queryClient,
|
queryClient,
|
||||||
server,
|
server,
|
||||||
serverId,
|
serverId,
|
||||||
@@ -165,8 +167,10 @@ export const useAutoDJ = () => {
|
|||||||
serverId,
|
serverId,
|
||||||
settings.enabled,
|
settings.enabled,
|
||||||
settings.albumStrategy,
|
settings.albumStrategy,
|
||||||
|
settings.allowDuplicates,
|
||||||
settings.itemCount,
|
settings.itemCount,
|
||||||
settings.mode,
|
settings.mode,
|
||||||
|
settings.onlySimilar,
|
||||||
settings.songStrategy,
|
settings.songStrategy,
|
||||||
settings.timing,
|
settings.timing,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
|
|
||||||
export const AutoDJSettings = memo(() => {
|
export const AutoDJSettings = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -145,6 +146,40 @@ export const AutoDJSettings = memo(() => {
|
|||||||
}),
|
}),
|
||||||
title: t('setting.autoDJ_timing'),
|
title: t('setting.autoDJ_timing'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.autoDJ_allowDuplicates')}
|
||||||
|
checked={settings.allowDuplicates}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
autoDJ: {
|
||||||
|
allowDuplicates: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.autoDJ_allowDuplicates_description'),
|
||||||
|
title: t('setting.autoDJ_allowDuplicates'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.autoDJ_onlySimilar')}
|
||||||
|
checked={settings.onlySimilar}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
autoDJ: {
|
||||||
|
onlySimilar: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.autoDJ_onlySimilar_description'),
|
||||||
|
title: t('setting.autoDJ_onlySimilar'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <SettingsSection options={autoDJOptions} title={t('setting.autoDJ')} />;
|
return <SettingsSection options={autoDJOptions} title={t('setting.autoDJ')} />;
|
||||||
|
|||||||
Vendored
+2
@@ -2,9 +2,11 @@ declare global {
|
|||||||
interface Window {
|
interface Window {
|
||||||
ANALYTICS_DISABLED?: boolean | string;
|
ANALYTICS_DISABLED?: boolean | string;
|
||||||
FS_AUTO_DJ_ALBUM_STRATEGY?: string;
|
FS_AUTO_DJ_ALBUM_STRATEGY?: string;
|
||||||
|
FS_AUTO_DJ_ALLOW_DUPLICATES?: string;
|
||||||
FS_AUTO_DJ_ENABLED?: string;
|
FS_AUTO_DJ_ENABLED?: string;
|
||||||
FS_AUTO_DJ_ITEM_COUNT?: string;
|
FS_AUTO_DJ_ITEM_COUNT?: string;
|
||||||
FS_AUTO_DJ_MODE?: string;
|
FS_AUTO_DJ_MODE?: string;
|
||||||
|
FS_AUTO_DJ_ONLY_SIMILAR?: string;
|
||||||
FS_AUTO_DJ_SONG_STRATEGY?: string;
|
FS_AUTO_DJ_SONG_STRATEGY?: string;
|
||||||
FS_AUTO_DJ_TIMING?: string;
|
FS_AUTO_DJ_TIMING?: string;
|
||||||
FS_CSS_CONTENT?: string;
|
FS_CSS_CONTENT?: string;
|
||||||
|
|||||||
@@ -461,9 +461,11 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
|||||||
path: ['autoDJ', 'albumStrategy'],
|
path: ['autoDJ', 'albumStrategy'],
|
||||||
type: 'enum',
|
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_ENABLED', path: ['autoDJ', 'enabled'], type: 'bool' },
|
||||||
{ key: 'FS_AUTO_DJ_ITEM_COUNT', path: ['autoDJ', 'itemCount'], type: 'num' },
|
{ 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' },
|
{ 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,
|
enumSet: AUTO_DJ_STRATEGIES,
|
||||||
key: 'FS_AUTO_DJ_SONG_STRATEGY',
|
key: 'FS_AUTO_DJ_SONG_STRATEGY',
|
||||||
|
|||||||
@@ -746,9 +746,11 @@ const autoDjStrategyEnum = z.enum(['similar', 'library_random']);
|
|||||||
|
|
||||||
const AutoDJSettingsSchema = z.object({
|
const AutoDJSettingsSchema = z.object({
|
||||||
albumStrategy: autoDjStrategyEnum,
|
albumStrategy: autoDjStrategyEnum,
|
||||||
|
allowDuplicates: z.boolean(),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
itemCount: z.number(),
|
itemCount: z.number(),
|
||||||
mode: z.enum(['songs', 'albums']),
|
mode: z.enum(['songs', 'albums']),
|
||||||
|
onlySimilar: z.boolean(),
|
||||||
songStrategy: autoDjStrategyEnum,
|
songStrategy: autoDjStrategyEnum,
|
||||||
timing: z.number(),
|
timing: z.number(),
|
||||||
});
|
});
|
||||||
@@ -1219,9 +1221,11 @@ const platformDefaultWindowBarStyle: Platform = getPlatformDefaultWindowBarStyle
|
|||||||
const initialState: SettingsState = {
|
const initialState: SettingsState = {
|
||||||
autoDJ: {
|
autoDJ: {
|
||||||
albumStrategy: AUTO_DJ_STRATEGY.SIMILAR,
|
albumStrategy: AUTO_DJ_STRATEGY.SIMILAR,
|
||||||
|
allowDuplicates: false,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
itemCount: 5,
|
itemCount: 5,
|
||||||
mode: 'songs',
|
mode: 'songs',
|
||||||
|
onlySimilar: false,
|
||||||
songStrategy: AUTO_DJ_STRATEGY.SIMILAR,
|
songStrategy: AUTO_DJ_STRATEGY.SIMILAR,
|
||||||
timing: 1,
|
timing: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user