Support using alt+click on prev/next to jump to previous/next album in queue (#2220)

This commit is contained in:
Damien Erambert
2026-07-15 22:27:40 -07:00
committed by GitHub
parent 62c1ac04e4
commit 5e79e385d9
14 changed files with 179 additions and 46 deletions
+4
View File
@@ -678,6 +678,7 @@
"mute": "Mute", "mute": "Mute",
"muted": "Muted", "muted": "Muted",
"next": "Next", "next": "Next",
"nextAlbum": "Alt+click for next album",
"play": "Play", "play": "Play",
"playbackFetchCancel": "This is taking a while… close the notification to cancel", "playbackFetchCancel": "This is taking a while… close the notification to cancel",
"playbackFetchInProgress": "Loading songs…", "playbackFetchInProgress": "Loading songs…",
@@ -686,6 +687,7 @@
"playRandom": "Play random", "playRandom": "Play random",
"playSimilarSongs": "Play similar songs", "playSimilarSongs": "Play similar songs",
"previous": "Previous", "previous": "Previous",
"previousAlbum": "Alt+click for previous album",
"queue_clear": "Clear queue", "queue_clear": "Clear queue",
"queue_moveToBottom": "Move selected to bottom", "queue_moveToBottom": "Move selected to bottom",
"queue_moveToTop": "Move selected to top", "queue_moveToTop": "Move selected to top",
@@ -927,10 +929,12 @@
"hotkey_listShowPlayingSong": "Show playing song in list", "hotkey_listShowPlayingSong": "Show playing song in list",
"hotkey_navigateHome": "Navigate to home", "hotkey_navigateHome": "Navigate to home",
"hotkey_playbackNext": "Next track", "hotkey_playbackNext": "Next track",
"hotkey_playbackNextAlbum": "Next album",
"hotkey_playbackPause": "Pause", "hotkey_playbackPause": "Pause",
"hotkey_playbackPlay": "Play", "hotkey_playbackPlay": "Play",
"hotkey_playbackPlayPause": "Play / pause", "hotkey_playbackPlayPause": "Play / pause",
"hotkey_playbackPrevious": "Previous track", "hotkey_playbackPrevious": "Previous track",
"hotkey_playbackPreviousAlbum": "Previous album",
"hotkey_playbackStop": "Stop", "hotkey_playbackStop": "Stop",
"hotkey_rate0": "Rating clear", "hotkey_rate0": "Rating clear",
"hotkey_rate1": "Rating 1 star", "hotkey_rate1": "Rating 1 star",
+6
View File
@@ -875,10 +875,12 @@ enum BindingActions {
LOCAL_SEARCH = 'localSearch', LOCAL_SEARCH = 'localSearch',
MUTE = 'volumeMute', MUTE = 'volumeMute',
NEXT = 'next', NEXT = 'next',
NEXT_ALBUM = 'nextAlbum',
PAUSE = 'pause', PAUSE = 'pause',
PLAY = 'play', PLAY = 'play',
PLAY_PAUSE = 'playPause', PLAY_PAUSE = 'playPause',
PREVIOUS = 'previous', PREVIOUS = 'previous',
PREVIOUS_ALBUM = 'previousAlbum',
SHUFFLE = 'toggleShuffle', SHUFFLE = 'toggleShuffle',
SKIP_BACKWARD = 'skipBackward', SKIP_BACKWARD = 'skipBackward',
SKIP_FORWARD = 'skipForward', SKIP_FORWARD = 'skipForward',
@@ -906,11 +908,15 @@ const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
[BindingActions.LOCAL_SEARCH]: () => {}, [BindingActions.LOCAL_SEARCH]: () => {},
[BindingActions.MUTE]: () => getMainWindow()?.webContents.send('renderer-player-volume-mute'), [BindingActions.MUTE]: () => getMainWindow()?.webContents.send('renderer-player-volume-mute'),
[BindingActions.NEXT]: () => getMainWindow()?.webContents.send('renderer-player-next'), [BindingActions.NEXT]: () => getMainWindow()?.webContents.send('renderer-player-next'),
[BindingActions.NEXT_ALBUM]: () =>
getMainWindow()?.webContents.send('renderer-player-next-album'),
[BindingActions.PAUSE]: () => getMainWindow()?.webContents.send('renderer-player-pause'), [BindingActions.PAUSE]: () => getMainWindow()?.webContents.send('renderer-player-pause'),
[BindingActions.PLAY]: () => getMainWindow()?.webContents.send('renderer-player-play'), [BindingActions.PLAY]: () => getMainWindow()?.webContents.send('renderer-player-play'),
[BindingActions.PLAY_PAUSE]: () => [BindingActions.PLAY_PAUSE]: () =>
getMainWindow()?.webContents.send('renderer-player-play-pause'), getMainWindow()?.webContents.send('renderer-player-play-pause'),
[BindingActions.PREVIOUS]: () => getMainWindow()?.webContents.send('renderer-player-previous'), [BindingActions.PREVIOUS]: () => getMainWindow()?.webContents.send('renderer-player-previous'),
[BindingActions.PREVIOUS_ALBUM]: () =>
getMainWindow()?.webContents.send('renderer-player-previous-album'),
[BindingActions.SHUFFLE]: () => [BindingActions.SHUFFLE]: () =>
getMainWindow()?.webContents.send('renderer-player-toggle-shuffle'), getMainWindow()?.webContents.send('renderer-player-toggle-shuffle'),
[BindingActions.SKIP_BACKWARD]: () => [BindingActions.SKIP_BACKWARD]: () =>
+10
View File
@@ -118,6 +118,10 @@ const rendererNext = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-next', (_, data) => cb(data)); ipcRenderer.on('renderer-player-next', (_, data) => cb(data));
}; };
const rendererNextAlbum = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-next-album', (_, data) => cb(data));
};
const rendererPause = (cb: (data: PlayerData) => void) => { const rendererPause = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-pause', (_, data) => cb(data)); ipcRenderer.on('renderer-player-pause', (_, data) => cb(data));
}; };
@@ -134,6 +138,10 @@ const rendererPrevious = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-previous', (_, data) => cb(data)); ipcRenderer.on('renderer-player-previous', (_, data) => cb(data));
}; };
const rendererPreviousAlbum = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-previous-album', (_, data) => cb(data));
};
const rendererStop = (cb: (data: PlayerData) => void) => { const rendererStop = (cb: (data: PlayerData) => void) => {
ipcRenderer.on('renderer-player-stop', (_, data) => cb(data)); ipcRenderer.on('renderer-player-stop', (_, data) => cb(data));
}; };
@@ -215,11 +223,13 @@ export const mpvPlayerListener = {
rendererError, rendererError,
rendererMpvReconnect, rendererMpvReconnect,
rendererNext, rendererNext,
rendererNextAlbum,
rendererPause, rendererPause,
rendererPlay, rendererPlay,
rendererPlayerFallback, rendererPlayerFallback,
rendererPlayPause, rendererPlayPause,
rendererPrevious, rendererPrevious,
rendererPreviousAlbum,
rendererQuit, rendererQuit,
rendererSkipBackward, rendererSkipBackward,
rendererSkipForward, rendererSkipForward,
@@ -56,13 +56,25 @@ export const useMainPlayerListener = () => {
mpvPlayerListener.rendererNext(() => { mpvPlayerListener.rendererNext(() => {
if (!isRadioActive) { if (!isRadioActive) {
mediaNext(); mediaNext(false);
}
});
mpvPlayerListener.rendererNextAlbum(() => {
if (!isRadioActive) {
mediaNext(true);
} }
}); });
mpvPlayerListener.rendererPrevious(() => { mpvPlayerListener.rendererPrevious(() => {
if (!isRadioActive) { if (!isRadioActive) {
mediaPrevious(); mediaPrevious(false);
}
});
mpvPlayerListener.rendererPreviousAlbum(() => {
if (!isRadioActive) {
mediaPrevious(true);
} }
}); });
@@ -21,6 +21,8 @@ import {
useSkipButtons, useSkipButtons,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; import { PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types';
export const CenterControls = () => { export const CenterControls = () => {
@@ -163,9 +165,18 @@ const PreviousButton = ({ disabled }: { disabled?: boolean }) => {
<PlayerButton <PlayerButton
disabled={disabled} disabled={disabled}
icon={<Icon fill="default" icon="mediaPrevious" size={buttonSize} />} icon={<Icon fill="default" icon="mediaPrevious" size={buttonSize} />}
onClick={mediaPrevious} onClick={(e) => mediaPrevious(e.altKey)}
tooltip={{ tooltip={{
label: t('player.previous'), label: (
<Stack gap="xs" justify="center">
<Text fw={500} ta="center">
{t('player.previous')}
</Text>
<Text fw={500} isMuted size="xs" ta="center">
{t('player.previousAlbum')}
</Text>
</Stack>
),
openDelay: 0, openDelay: 0,
}} }}
variant="secondary" variant="secondary"
@@ -239,9 +250,18 @@ const NextButton = ({ disabled }: { disabled?: boolean }) => {
<PlayerButton <PlayerButton
disabled={disabled} disabled={disabled}
icon={<Icon fill="default" icon="mediaNext" size={buttonSize} />} icon={<Icon fill="default" icon="mediaNext" size={buttonSize} />}
onClick={mediaNext} onClick={(e) => mediaNext(e.altKey)}
tooltip={{ tooltip={{
label: t('player.next'), label: (
<Stack gap="xs" justify="center">
<Text fw={500} ta="center">
{t('player.next')}
</Text>
<Text fw={500} isMuted size="xs" ta="center">
{t('player.nextAlbum')}
</Text>
</Stack>
),
openDelay: 0, openDelay: 0,
}} }}
variant="secondary" variant="secondary"
@@ -31,7 +31,7 @@ export const MobileFullscreenPlayerControls = memo(
<div className={styles.controlsContainer}> <div className={styles.controlsContainer}>
<PlayerButton <PlayerButton
icon={<Icon fill="default" icon="mediaPrevious" size="xl" />} icon={<Icon fill="default" icon="mediaPrevious" size="xl" />}
onClick={mediaPrevious} onClick={(e) => mediaPrevious(e.altKey)}
tooltip={{ tooltip={{
label: t('player.previous'), label: t('player.previous'),
openDelay: 0, openDelay: 0,
@@ -71,7 +71,7 @@ export const MobileFullscreenPlayerControls = memo(
/> />
<PlayerButton <PlayerButton
icon={<Icon fill="default" icon="mediaNext" size="xl" />} icon={<Icon fill="default" icon="mediaNext" size="xl" />}
onClick={mediaNext} onClick={(e) => mediaNext(e.altKey)}
tooltip={{ tooltip={{
label: t('player.next'), label: t('player.next'),
openDelay: 0, openDelay: 0,
@@ -203,7 +203,7 @@ export const MobilePlayerbar = () => {
icon={<Icon fill="default" icon="mediaPrevious" size="md" />} icon={<Icon fill="default" icon="mediaPrevious" size="md" />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
mediaPrevious(); mediaPrevious(e.altKey);
}} }}
tooltip={{ tooltip={{
label: t('player.previous'), label: t('player.previous'),
@@ -223,7 +223,7 @@ export const MobilePlayerbar = () => {
icon={<Icon fill="default" icon="mediaNext" size="md" />} icon={<Icon fill="default" icon="mediaNext" size="md" />}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
mediaNext(); mediaNext(e.altKey);
}} }}
tooltip={{ tooltip={{
label: t('player.next'), label: t('player.next'),
@@ -61,11 +61,11 @@ export interface PlayerContext {
clearSelected: (items: QueueSong[]) => void; clearSelected: (items: QueueSong[]) => void;
decreaseVolume: (amount: number) => void; decreaseVolume: (amount: number) => void;
increaseVolume: (amount: number) => void; increaseVolume: (amount: number) => void;
mediaNext: () => void; mediaNext: (toNextAlbum: boolean) => void;
mediaPause: () => void; mediaPause: () => void;
mediaPlay: (id?: string) => void; mediaPlay: (id?: string) => void;
mediaPlayByIndex: (index: number) => void; mediaPlayByIndex: (index: number) => void;
mediaPrevious: () => void; mediaPrevious: (toPreviousAlbum: boolean) => void;
mediaSeekToTimestamp: (timestamp: number) => void; mediaSeekToTimestamp: (timestamp: number) => void;
mediaSkipBackward: () => void; mediaSkipBackward: () => void;
mediaSkipForward: () => void; mediaSkipForward: () => void;
@@ -579,13 +579,16 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
[storeActions], [storeActions],
); );
const mediaNext = useCallback(() => { const mediaNext = useCallback(
logFn.debug(logMsg[LogCategory.PLAYER].mediaNext, { (toNextAlbum: boolean) => {
category: LogCategory.PLAYER, logFn.debug(logMsg[LogCategory.PLAYER].mediaNext, {
}); category: LogCategory.PLAYER,
});
storeActions.mediaNext(); storeActions.mediaNext(toNextAlbum);
}, [storeActions]); },
[storeActions],
);
const mediaPause = useCallback(() => { const mediaPause = useCallback(() => {
logFn.debug(logMsg[LogCategory.PLAYER].mediaPause, { logFn.debug(logMsg[LogCategory.PLAYER].mediaPause, {
@@ -619,13 +622,16 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
[storeActions], [storeActions],
); );
const mediaPrevious = useCallback(() => { const mediaPrevious = useCallback(
logFn.debug(logMsg[LogCategory.PLAYER].mediaPrevious, { (toPreviousAlbum: boolean) => {
category: LogCategory.PLAYER, logFn.debug(logMsg[LogCategory.PLAYER].mediaPrevious, {
}); category: LogCategory.PLAYER,
});
storeActions.mediaPrevious(); storeActions.mediaPrevious(toPreviousAlbum);
}, [storeActions]); },
[storeActions],
);
const mediaStop = useCallback( const mediaStop = useCallback(
(options?: { reset?: boolean }) => { (options?: { reset?: boolean }) => {
@@ -100,7 +100,7 @@ export const useMediaSession = () => {
return; return;
} }
playerRef.current.mediaNext(); playerRef.current.mediaNext(false);
}); });
mediaSession.setActionHandler('pause', () => { mediaSession.setActionHandler('pause', () => {
@@ -116,7 +116,7 @@ export const useMediaSession = () => {
return; return;
} }
playerRef.current.mediaPrevious(); playerRef.current.mediaPrevious(false);
}); });
mediaSession.setActionHandler('seekto', (e) => { mediaSession.setActionHandler('seekto', (e) => {
@@ -14,11 +14,13 @@ export const usePlaybackHotkeys = () => {
binding: (typeof bindings)[keyof typeof bindings]; binding: (typeof bindings)[keyof typeof bindings];
handler: () => void; handler: () => void;
}> = [ }> = [
{ binding: bindings.next, handler: () => player.mediaNext() }, { binding: bindings.next, handler: () => player.mediaNext(false) },
{ binding: bindings.nextAlbum, handler: () => player.mediaNext(true) },
{ binding: bindings.pause, handler: () => player.mediaPause() }, { binding: bindings.pause, handler: () => player.mediaPause() },
{ binding: bindings.play, handler: () => player.mediaPlay() }, { binding: bindings.play, handler: () => player.mediaPlay() },
{ binding: bindings.playPause, handler: () => player.mediaTogglePlayPause() }, { binding: bindings.playPause, handler: () => player.mediaTogglePlayPause() },
{ binding: bindings.previous, handler: () => player.mediaPrevious() }, { binding: bindings.previous, handler: () => player.mediaPrevious(false) },
{ binding: bindings.previousAlbum, handler: () => player.mediaPrevious(true) },
{ binding: bindings.skipBackward, handler: () => player.mediaSkipBackward() }, { binding: bindings.skipBackward, handler: () => player.mediaSkipBackward() },
{ binding: bindings.skipForward, handler: () => player.mediaSkipForward() }, { binding: bindings.skipForward, handler: () => player.mediaSkipForward() },
{ binding: bindings.stop, handler: () => player.mediaStop() }, { binding: bindings.stop, handler: () => player.mediaStop() },
@@ -70,6 +70,7 @@ const BINDINGS_MAP: Record<BindingActions, string> = {
context: 'navigateHome', context: 'navigateHome',
}), }),
next: i18n.t('setting.hotkey', { context: 'playbackNext' }), next: i18n.t('setting.hotkey', { context: 'playbackNext' }),
nextAlbum: i18n.t('setting.hotkey', { context: 'playbackNextAlbum' }),
pause: i18n.t('setting.hotkey', { context: 'playbackPause' }), pause: i18n.t('setting.hotkey', { context: 'playbackPause' }),
play: i18n.t('setting.hotkey', { context: 'playbackPlay' }), play: i18n.t('setting.hotkey', { context: 'playbackPlay' }),
playPause: i18n.t('setting.hotkey', { playPause: i18n.t('setting.hotkey', {
@@ -78,6 +79,7 @@ const BINDINGS_MAP: Record<BindingActions, string> = {
previous: i18n.t('setting.hotkey', { previous: i18n.t('setting.hotkey', {
context: 'playbackPrevious', context: 'playbackPrevious',
}), }),
previousAlbum: i18n.t('setting.hotkey', { context: 'playbackPreviousAlbum' }),
rate0: i18n.t('setting.hotkey', { context: 'rate0' }), rate0: i18n.t('setting.hotkey', { context: 'rate0' }),
rate1: i18n.t('setting.hotkey', { context: 'rate1' }), rate1: i18n.t('setting.hotkey', { context: 'rate1' }),
rate2: i18n.t('setting.hotkey', { context: 'rate2' }), rate2: i18n.t('setting.hotkey', { context: 'rate2' }),
+74 -9
View File
@@ -52,11 +52,11 @@ interface Actions {
isFirstTrackInQueue: () => boolean; isFirstTrackInQueue: () => boolean;
isLastTrackInQueue: () => boolean; isLastTrackInQueue: () => boolean;
mediaAutoNext: () => PlayerData; mediaAutoNext: () => PlayerData;
mediaNext: () => void; mediaNext: (toNextAlbum: boolean) => void;
mediaPause: () => void; mediaPause: () => void;
mediaPlay: (id?: string) => void; mediaPlay: (id?: string) => void;
mediaPlayByIndex: (index: number) => void; mediaPlayByIndex: (index: number) => void;
mediaPrevious: () => void; mediaPrevious: (toPreviousAlbum: boolean) => void;
mediaSeekToTimestamp: (timestamp: number) => void; mediaSeekToTimestamp: (timestamp: number) => void;
mediaSkipBackward: (offset?: number) => void; mediaSkipBackward: (offset?: number) => void;
mediaSkipForward: (offset?: number) => void; mediaSkipForward: (offset?: number) => void;
@@ -1044,7 +1044,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
status: newStatus, status: newStatus,
}; };
}, },
mediaNext: () => { mediaNext: (toNextAlbum) => {
const state = get(); const state = get();
const currentIndex = state.player.index; const currentIndex = state.player.index;
const player = state.player; const player = state.player;
@@ -1072,11 +1072,30 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
return; return;
} }
const { nextIndex, shouldStop } = calculateNextIndex( const nextIndexProps = calculateNextIndex(currentIndex, playbackLength, repeat);
currentIndex, let { nextIndex } = nextIndexProps;
playbackLength, const { shouldStop } = nextIndexProps;
repeat,
); if (toNextAlbum && !shouldStop) {
const currentItem = queue.items[currentIndex];
const [start, end] = findLastAlbumRange(queue.items);
const isOnLastAlbum = start <= currentIndex && currentIndex <= end;
if (isOnLastAlbum) {
const nextIndexWithNextAlbum = queue.items.findIndex(
(i) => i.albumId !== currentItem.albumId,
);
nextIndex = nextIndexWithNextAlbum;
} else {
const queueStartingFromCurrent = queue.items.slice(currentIndex);
const nextIndexWithNextAlbum = queueStartingFromCurrent.findIndex(
(i) => i.albumId !== currentItem.albumId,
);
nextIndex =
nextIndexWithNextAlbum +
(queue.items.length - queueStartingFromCurrent.length);
}
}
if (shouldStop) { if (shouldStop) {
set((state) => { set((state) => {
@@ -1194,7 +1213,7 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
}); });
} }
}, },
mediaPrevious: () => { mediaPrevious: (toPreviousAlbum) => {
const currentIndex = get().player.index; const currentIndex = get().player.index;
const player = get().player; const player = get().player;
const queue = get().getQueueOrder(); const queue = get().getQueueOrder();
@@ -1217,6 +1236,11 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
} else if (player.repeat === PlayerRepeat.NONE && isFirstTrack) { } else if (player.repeat === PlayerRepeat.NONE && isFirstTrack) {
// Repeat none: stay on first track if already there // Repeat none: stay on first track if already there
previousIndex = currentIndex; previousIndex = currentIndex;
} else if (toPreviousAlbum) {
previousIndex = Math.max(
0,
findIndexWithPreviousAlbum(queue.items, currentIndex),
);
} else { } else {
// Otherwise, go to previous track // Otherwise, go to previous track
previousIndex = Math.max(0, currentIndex - 1); previousIndex = Math.max(0, currentIndex - 1);
@@ -2262,6 +2286,47 @@ function cleanupOrphanedSongs(state: any): boolean {
return hasOrphans; return hasOrphans;
} }
function findIndexWithPreviousAlbum(queueItems: QueueSong[], currentIndex: number) {
const queueBeforeCurrent = queueItems.slice(0, currentIndex);
const currentItem = queueItems[currentIndex];
const previousAlbumIdInQueue = queueBeforeCurrent.findLast(
(i) => i.albumId !== currentItem.albumId,
)?.albumId;
let prevIndex = -1;
if (previousAlbumIdInQueue) {
for (let index = queueBeforeCurrent.length - 1; index > -1; index--) {
const element = queueBeforeCurrent[index];
if (element.albumId === previousAlbumIdInQueue) {
prevIndex = index;
}
if (prevIndex > -1 && element.albumId !== previousAlbumIdInQueue) {
break;
}
}
}
return prevIndex;
}
function findLastAlbumRange(queueItems: QueueSong[]) {
const lastAlbumId = queueItems.at(-1)?.albumId;
const rangeEnd = queueItems.length - 1;
let rangeStart = rangeEnd;
for (let index = rangeEnd; index > -1; index--) {
const element = queueItems[index];
rangeStart = index;
if (element.albumId !== lastAlbumId) {
break;
}
}
return [rangeStart + 1, rangeEnd];
}
function parseUniqueSeekToTimestamp(timestamp: string) { function parseUniqueSeekToTimestamp(timestamp: string) {
return Number(timestamp.split('-')[0]); return Number(timestamp.split('-')[0]);
} }
+6
View File
@@ -145,10 +145,12 @@ const BindingActionsSchema = z.enum([
'volumeMute', 'volumeMute',
'navigateHome', 'navigateHome',
'next', 'next',
'nextAlbum',
'pause', 'pause',
'play', 'play',
'playPause', 'playPause',
'previous', 'previous',
'previousAlbum',
'rate0', 'rate0',
'rate1', 'rate1',
'rate2', 'rate2',
@@ -847,10 +849,12 @@ export enum BindingActions {
MUTE = 'volumeMute', MUTE = 'volumeMute',
NAVIGATE_HOME = 'navigateHome', NAVIGATE_HOME = 'navigateHome',
NEXT = 'next', NEXT = 'next',
NEXT_ALBUM = 'nextAlbum',
PAUSE = 'pause', PAUSE = 'pause',
PLAY = 'play', PLAY = 'play',
PLAY_PAUSE = 'playPause', PLAY_PAUSE = 'playPause',
PREVIOUS = 'previous', PREVIOUS = 'previous',
PREVIOUS_ALBUM = 'previousAlbum',
RATE_0 = 'rate0', RATE_0 = 'rate0',
RATE_1 = 'rate1', RATE_1 = 'rate1',
RATE_2 = 'rate2', RATE_2 = 'rate2',
@@ -1322,10 +1326,12 @@ const initialState: SettingsState = {
localSearch: { allowGlobal: false, hotkey: 'mod+f', isGlobal: false }, localSearch: { allowGlobal: false, hotkey: 'mod+f', isGlobal: false },
navigateHome: { allowGlobal: false, hotkey: '', isGlobal: false }, navigateHome: { allowGlobal: false, hotkey: '', isGlobal: false },
next: { allowGlobal: true, hotkey: '', isGlobal: false }, next: { allowGlobal: true, hotkey: '', isGlobal: false },
nextAlbum: { allowGlobal: true, hotkey: '', isGlobal: false },
pause: { allowGlobal: true, hotkey: '', isGlobal: false }, pause: { allowGlobal: true, hotkey: '', isGlobal: false },
play: { allowGlobal: true, hotkey: '', isGlobal: false }, play: { allowGlobal: true, hotkey: '', isGlobal: false },
playPause: { allowGlobal: true, hotkey: 'space', isGlobal: false }, playPause: { allowGlobal: true, hotkey: 'space', isGlobal: false },
previous: { allowGlobal: true, hotkey: '', isGlobal: false }, previous: { allowGlobal: true, hotkey: '', isGlobal: false },
previousAlbum: { allowGlobal: true, hotkey: '', isGlobal: false },
rate0: { allowGlobal: true, hotkey: '', isGlobal: false }, rate0: { allowGlobal: true, hotkey: '', isGlobal: false },
rate1: { allowGlobal: true, hotkey: '', isGlobal: false }, rate1: { allowGlobal: true, hotkey: '', isGlobal: false },
rate2: { allowGlobal: true, hotkey: '', isGlobal: false }, rate2: { allowGlobal: true, hotkey: '', isGlobal: false },
+9 -9
View File
@@ -342,31 +342,31 @@ export const sortSongsByFetchedOrder = (
// Group songs by the fetched ID they belong to // Group songs by the fetched ID they belong to
const songsByFetchedId = new Map<string, Song[]>(); const songsByFetchedId = new Map<string, Song[]>();
const fetchedIdsSet = new Set(fetchedIds);
for (const song of songs) { for (const song of songs) {
let matchedId: string | undefined; let matchedId: string | undefined;
switch (itemType) { switch (itemType) {
case LibraryItem.ALBUM: case LibraryItem.ALBUM:
matchedId = fetchedIds.find((id) => song.albumId === id); matchedId = fetchedIdsSet.has(song.albumId) ? song.albumId : undefined;
break; break;
case LibraryItem.ALBUM_ARTIST: case LibraryItem.ALBUM_ARTIST:
matchedId = fetchedIds.find((id) => matchedId = song.albumArtists.find((a) => fetchedIdsSet.has(a.id))?.id;
song.albumArtists.some((artist) => artist.id === id),
);
break; break;
case LibraryItem.ARTIST: case LibraryItem.ARTIST:
matchedId = fetchedIds.find((id) => matchedId = song.artists.find((a) => fetchedIdsSet.has(a.id))?.id;
song.artists.some((artist) => artist.id === id),
);
break; break;
case LibraryItem.GENRE: case LibraryItem.GENRE:
matchedId = fetchedIds.find((id) => song.genres.some((genre) => genre.id === id)); matchedId = song.genres.find((a) => fetchedIdsSet.has(a.id))?.id;
break; break;
case LibraryItem.PLAYLIST: case LibraryItem.PLAYLIST:
// For playlists, we might need to track which playlist each song came from // For playlists, we might need to track which playlist each song came from
// This is a simplified approach - you may need to adjust based on your data structure // This is a simplified approach - you may need to adjust based on your data structure
matchedId = fetchedIds.find((id) => song.playlistItemId === id); matchedId =
song.playlistItemId && fetchedIdsSet.has(song.playlistItemId)
? song.playlistItemId
: undefined;
break; break;
default: default:
break; break;