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 {