diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 19d3cda8..2354ae69 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -333,6 +333,7 @@ "removeFromPlaylist": "$t(action.removeFromPlaylist)", "removeFromQueue": "$t(action.removeFromQueue)", "setRating": "$t(action.setRating)", + "playShuffled": "$t(player.shuffle)", "shareItem": "share item", "showDetails": "get info" }, @@ -438,7 +439,7 @@ "repeat_off": "repeat disabled", "repeat_one": "repeat one", "repeat_other": "", - "shuffle": "shuffle", + "shuffle": "play shuffled", "shuffle_off": "shuffle disabled", "skip": "skip", "skip_back": "skip backwards", @@ -591,6 +592,7 @@ "playButtonBehavior_optionAddLast": "$t(player.addLast)", "playButtonBehavior_optionAddNext": "$t(player.addNext)", "playButtonBehavior_optionPlay": "$t(player.play)", + "playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)", "playerAlbumArtResolution": "player album art resolution", "playerAlbumArtResolution_description": "the resolution for the large player's album art preview. larger makes it look more crisp, but may slow loading down. defaults to 0, meaning auto", "playerbarOpenDrawer": "playerbar fullscreen toggle", diff --git a/src/renderer/features/context-menu/context-menu-items.tsx b/src/renderer/features/context-menu/context-menu-items.tsx index 3d9f23e5..12eb1673 100644 --- a/src/renderer/features/context-menu/context-menu-items.tsx +++ b/src/renderer/features/context-menu/context-menu-items.tsx @@ -18,6 +18,7 @@ export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, { id: 'playNext' }, + { id: 'playShuffled' }, { divider: true, id: 'playSimilarSongs' }, { divider: true, id: 'addToPlaylist' }, { id: 'addToFavorites' }, @@ -31,7 +32,8 @@ export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ export const SONG_ALBUM_PAGE: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, - { divider: true, id: 'playNext' }, + { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'addToPlaylist' }, ]; @@ -39,6 +41,7 @@ export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, { id: 'playNext' }, + { id: 'playShuffled' }, { divider: true, id: 'playSimilarSongs' }, { id: 'addToPlaylist' }, { divider: true, id: 'removeFromPlaylist' }, @@ -54,6 +57,7 @@ export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'playSimilarSongs' }, { divider: true, id: 'addToPlaylist' }, { id: 'addToFavorites' }, @@ -67,7 +71,8 @@ export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, - { divider: true, id: 'playNext' }, + { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'addToPlaylist' }, { id: 'addToFavorites' }, { id: 'removeFromFavorites' }, @@ -79,14 +84,16 @@ export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ export const GENRE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, - { divider: true, id: 'playNext' }, + { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'addToPlaylist' }, ]; export const ARTIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, - { divider: true, id: 'playNext' }, + { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'addToPlaylist' }, { id: 'addToFavorites' }, { divider: true, id: 'removeFromFavorites' }, @@ -98,7 +105,8 @@ export const ARTIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ export const PLAYLIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [ { id: 'play' }, { id: 'playLast' }, - { divider: true, id: 'playNext' }, + { id: 'playNext' }, + { divider: true, id: 'playShuffled' }, { divider: true, id: 'shareItem' }, { id: 'deletePlaylist' }, ]; diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index 2b8b736c..915617d5 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -31,6 +31,7 @@ import { RiInformationFill, RiRadio2Fill, RiDownload2Line, + RiShuffleFill, } from 'react-icons/ri'; import { AnyLibraryItems, LibraryItem, ServerType, AnyLibraryItem } from '/@/renderer/api/types'; import { @@ -774,6 +775,12 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { leftIcon: , onClick: () => handlePlay(Play.NEXT), }, + playShuffled: { + id: 'playShuffled', + label: t('page.contextMenu.playShuffled', { postProcess: 'sentenceCase' }), + leftIcon: , + onClick: () => handlePlay(Play.SHUFFLE), + }, playSimilarSongs: { id: 'playSimilarSongs', label: t('page.contextMenu.playSimilarSongs', { postProcess: 'sentenceCase' }), diff --git a/src/renderer/features/context-menu/events.ts b/src/renderer/features/context-menu/events.ts index a5b4d153..35884fbb 100644 --- a/src/renderer/features/context-menu/events.ts +++ b/src/renderer/features/context-menu/events.ts @@ -23,6 +23,7 @@ export type ContextMenuItemType = | 'play' | 'playLast' | 'playNext' + | 'playShuffled' | 'addToPlaylist' | 'removeFromPlaylist' | 'addToFavorites' @@ -45,6 +46,7 @@ export const CONFIGURABLE_CONTEXT_MENU_ITEMS: ContextMenuItemType[] = [ 'play', 'playLast', 'playNext', + 'playShuffled', 'playSimilarSongs', 'addToPlaylist', 'removeFromPlaylist', diff --git a/src/renderer/features/settings/components/general/control-settings.tsx b/src/renderer/features/settings/components/general/control-settings.tsx index c6fbed83..c4e4a32b 100644 --- a/src/renderer/features/settings/components/general/control-settings.tsx +++ b/src/renderer/features/settings/components/general/control-settings.tsx @@ -215,6 +215,13 @@ export const ControlSettings = () => { }), value: Play.LAST, }, + { + label: t('setting.playButtonBehavior', { + context: 'optionPlayShuffled', + postProcess: 'titleCase', + }), + value: Play.SHUFFLE, + }, ]} defaultValue={settings.playButtonBehavior} onChange={(e) => diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index e9a89b73..781434bf 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -107,18 +107,53 @@ export const usePlayerStore = create()( actions: { addToQueue: (args) => { const { initialIndex, playType, songs } = args; - const { shuffledIndex } = get().current; - const shuffledQueue = get().queue.shuffled; const songsToAddToQueue = map(songs, (song) => ({ ...song, uniqueId: nanoid(), })); - const queue = get().queue.default; // If the queue is empty, next/last should behave the same as now - if (playType === Play.NOW || queue.length === 0) { + if (playType === Play.SHUFFLE) { + const songs = shuffle(songsToAddToQueue); + const initialSong = songs[0]; + + if (get().shuffle === PlayerShuffle.TRACK) { + const shuffledIds = [ + initialSong.uniqueId, + ...shuffle(songs.slice(1).map((song) => song.uniqueId)), + ]; + + set((state) => { + state.queue.default = songs; + state.queue.shuffled = shuffledIds; + state.current.time = 0; + state.current.player = 1; + state.current.index = 0; + state.current.shuffledIndex = 0; + state.current.song = initialSong; + }); + } else { + set((state) => { + state.queue.default = songs; + state.queue.shuffled = []; + state.current.time = 0; + state.current.player = 1; + state.current.index = 0; + state.current.shuffledIndex = 0; + state.current.song = initialSong; + }); + } + + return get().actions.getPlayerData(); + } + + const shuffledQueue = get().queue.shuffled; + const queue = get().queue.default; + const { shuffledIndex } = get().current; + + if (playType === Play.NOW || queue.length === 0) { + const index = initialIndex || 0; if (get().shuffle === PlayerShuffle.TRACK) { - const index = initialIndex || 0; const initialSong = songsToAddToQueue[index]; const queueCopy = [...songsToAddToQueue]; @@ -145,7 +180,6 @@ export const usePlayerStore = create()( state.current.song = initialSong; }); } else { - const index = initialIndex || 0; set((state) => { state.queue.default = songsToAddToQueue; state.current.time = 0; diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 154cf68a..add64802 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -109,6 +109,7 @@ export enum Play { LAST = 'last', NEXT = 'next', NOW = 'now', + SHUFFLE = 'shuffle', } export enum CrossfadeStyle {