Fix various queue behavior

- Fix add next behavior when shuffle is enabled
- Fix shuffled queue when songs are removed from queue
- Fix queue indices when currently playing song is removed
- Re-shuffle queue after queue is finished when shuffle is enabled
This commit is contained in:
jeffvli 2023-06-13 17:47:40 -07:00
parent 2fac9efc1b
commit a6990fd732
3 changed files with 93 additions and 45 deletions

View file

@ -44,7 +44,12 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useDeletePlaylist } from '/@/renderer/features/playlists'; import { useDeletePlaylist } from '/@/renderer/features/playlists';
import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation'; import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation';
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared'; import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
import { useAuthStore, useCurrentServer, useQueueControls } from '/@/renderer/store'; import {
useAuthStore,
useCurrentServer,
usePlayerStore,
useQueueControls,
} from '/@/renderer/store';
import { usePlayerType } from '/@/renderer/store/settings.store'; import { usePlayerType } from '/@/renderer/store/settings.store';
import { Play, PlaybackType } from '/@/renderer/types'; import { Play, PlaybackType } from '/@/renderer/types';
@ -558,11 +563,17 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId);
if (!uniqueIds?.length) return; if (!uniqueIds?.length) return;
const currentSong = usePlayerStore.getState().current.song;
const playerData = removeFromQueue(uniqueIds); const playerData = removeFromQueue(uniqueIds);
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId);
if (playerType === PlaybackType.LOCAL) { if (playerType === PlaybackType.LOCAL) {
if (isCurrentSongRemoved) {
mpvPlayer.setQueue(playerData);
} else {
mpvPlayer.setQueueNext(playerData); mpvPlayer.setQueueNext(playerData);
} }
}
}, [ctx.dataNodes, playerType, removeFromQueue]); }, [ctx.dataNodes, playerType, removeFromQueue]);
const handleDeselectAll = useCallback(() => { const handleDeselectAll = useCallback(() => {

View file

@ -15,7 +15,7 @@ import { Song } from '/@/renderer/api/types';
import { usePlayerControls, useQueueControls } from '/@/renderer/store'; import { usePlayerControls, useQueueControls } from '/@/renderer/store';
import { PlaybackType, TableType } from '/@/renderer/types'; import { PlaybackType, TableType } from '/@/renderer/types';
import { usePlayerType } from '/@/renderer/store/settings.store'; import { usePlayerType } from '/@/renderer/store/settings.store';
import { useSetCurrentTime } from '../../../store/player.store'; import { usePlayerStore, useSetCurrentTime } from '../../../store/player.store';
import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null; const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
@ -63,11 +63,17 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
const uniqueIds = selectedRows?.map((row) => row.uniqueId); const uniqueIds = selectedRows?.map((row) => row.uniqueId);
if (!uniqueIds?.length) return; if (!uniqueIds?.length) return;
const currentSong = usePlayerStore.getState().current.song;
const playerData = removeFromQueue(uniqueIds); const playerData = removeFromQueue(uniqueIds);
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId);
if (playerType === PlaybackType.LOCAL) { if (playerType === PlaybackType.LOCAL) {
if (isCurrentSongRemoved) {
mpvPlayer.setQueue(playerData);
} else {
mpvPlayer.setQueueNext(playerData); mpvPlayer.setQueueNext(playerData);
} }
}
}; };
const handleClearQueue = () => { const handleClearQueue = () => {

View file

@ -60,7 +60,7 @@ export interface PlayerSlice extends PlayerState {
addToQueue: (args: { initialIndex: number; playType: Play; songs: QueueSong[] }) => PlayerData; addToQueue: (args: { initialIndex: number; playType: Play; songs: QueueSong[] }) => PlayerData;
autoNext: () => PlayerData; autoNext: () => PlayerData;
checkIsFirstTrack: () => boolean; checkIsFirstTrack: () => boolean;
checkIsLastTrack: () => boolean; checkIsLastTrack: (type?: 'next' | 'prev') => boolean;
clearQueue: () => PlayerData; clearQueue: () => PlayerData;
getPlayerData: () => PlayerData; getPlayerData: () => PlayerData;
getQueueData: () => QueueData; getQueueData: () => QueueData;
@ -101,7 +101,7 @@ export const usePlayerStore = create<PlayerSlice>()(
const { initialIndex, playType, songs } = args; const { initialIndex, playType, songs } = args;
const { shuffledIndex } = get().current; const { shuffledIndex } = get().current;
const shuffledQueue = get().queue.shuffled; const shuffledQueue = get().queue.shuffled;
const queueSongs = map(songs, (song) => ({ const songsToAddToQueue = map(songs, (song) => ({
...song, ...song,
uniqueId: nanoid(), uniqueId: nanoid(),
})); }));
@ -109,8 +109,8 @@ export const usePlayerStore = create<PlayerSlice>()(
if (playType === Play.NOW) { if (playType === Play.NOW) {
if (get().shuffle === PlayerShuffle.TRACK) { if (get().shuffle === PlayerShuffle.TRACK) {
const index = initialIndex || 0; const index = initialIndex || 0;
const initialSong = queueSongs[index]; const initialSong = songsToAddToQueue[index];
const queueCopy = [...queueSongs]; const queueCopy = [...songsToAddToQueue];
// Splice the initial song from the queue // Splice the initial song from the queue
queueCopy.splice(index, 1); queueCopy.splice(index, 1);
@ -127,7 +127,7 @@ export const usePlayerStore = create<PlayerSlice>()(
set((state) => { set((state) => {
state.queue.shuffled = shuffledSongIndices; state.queue.shuffled = shuffledSongIndices;
state.queue.default = queueSongs; state.queue.default = songsToAddToQueue;
state.current.time = 0; state.current.time = 0;
state.current.player = 1; state.current.player = 1;
state.current.index = 0; state.current.index = 0;
@ -137,12 +137,12 @@ export const usePlayerStore = create<PlayerSlice>()(
} else { } else {
const index = initialIndex || 0; const index = initialIndex || 0;
set((state) => { set((state) => {
state.queue.default = queueSongs; state.queue.default = songsToAddToQueue;
state.current.time = 0; state.current.time = 0;
state.current.player = 1; state.current.player = 1;
state.current.index = index; state.current.index = index;
state.current.shuffledIndex = 0; state.current.shuffledIndex = 0;
state.current.song = queueSongs[index]; state.current.song = songsToAddToQueue[index];
}); });
} }
} else if (playType === Play.LAST) { } else if (playType === Play.LAST) {
@ -152,40 +152,49 @@ export const usePlayerStore = create<PlayerSlice>()(
? [ ? [
...shuffledQueue.slice(0, shuffledIndex + 1), ...shuffledQueue.slice(0, shuffledIndex + 1),
...shuffle([ ...shuffle([
...queueSongs.map((song) => song.uniqueId), ...songsToAddToQueue.map((song) => song.uniqueId),
...shuffledQueue.slice(shuffledIndex + 1), ...shuffledQueue.slice(shuffledIndex + 1),
]), ]),
] ]
: []; : [];
set((state) => { set((state) => {
state.queue.default = [...get().queue.default, ...queueSongs]; state.queue.default = [...get().queue.default, ...songsToAddToQueue];
state.queue.shuffled = shuffledQueueWithNewSongs; state.queue.shuffled = shuffledQueueWithNewSongs;
}); });
} else if (playType === Play.NEXT) { } else if (playType === Play.NEXT) {
const queue = get().queue.default; const queue = get().queue.default;
const currentIndex = get().current.index; const currentIndex = get().current.index;
if (get().shuffle === PlayerShuffle.TRACK) {
const shuffledIndex = get().current.shuffledIndex;
const shuffledQueue = get().queue.shuffled;
// Shuffle the queue after the current track // Shuffle the queue after the current track
const shuffledQueueWithNewSongs = const shuffledQueueWithNewSongs = [
get().shuffle === PlayerShuffle.TRACK
? [
...shuffledQueue.slice(0, shuffledIndex + 1), ...shuffledQueue.slice(0, shuffledIndex + 1),
...shuffle([ ...shuffle(songsToAddToQueue.map((song) => song.uniqueId)),
...queueSongs.map((song) => song.uniqueId),
...shuffledQueue.slice(shuffledIndex + 1), ...shuffledQueue.slice(shuffledIndex + 1),
]), ];
]
: [];
set((state) => { set((state) => {
state.queue.default = [ state.queue.default = [
...queue.slice(0, currentIndex + 1), ...queue.slice(0, currentIndex + 1),
...queueSongs, ...songsToAddToQueue,
...queue.slice(currentIndex + 1), ...queue.slice(currentIndex + 1),
]; ];
state.queue.shuffled = shuffledQueueWithNewSongs; state.queue.shuffled = shuffledQueueWithNewSongs;
}); });
} else {
set((state) => {
state.queue.default = [
...queue.slice(0, currentIndex + 1),
...songsToAddToQueue,
...queue.slice(currentIndex + 1),
];
state.queue.shuffled = [];
});
}
} }
return get().actions.getPlayerData(); return get().actions.getPlayerData();
@ -223,6 +232,10 @@ export const usePlayerStore = create<PlayerSlice>()(
state.current.player = state.current.player === 1 ? 2 : 1; state.current.player = state.current.player === 1 ? 2 : 1;
state.current.song = nextSong!; state.current.song = nextSong!;
state.queue.previousNode = get().current.song; state.queue.previousNode = get().current.song;
if (isLastTrack) {
state.queue.shuffled = shuffle(get().queue.shuffled);
}
}); });
} else { } else {
const nextIndex = isLastTrack ? 0 : get().current.index + 1; const nextIndex = isLastTrack ? 0 : get().current.index + 1;
@ -246,13 +259,17 @@ export const usePlayerStore = create<PlayerSlice>()(
return currentIndex === 0; return currentIndex === 0;
}, },
checkIsLastTrack: () => { checkIsLastTrack: (type) => {
const currentIndex = const isShuffled = get().shuffle === PlayerShuffle.TRACK;
get().shuffle === PlayerShuffle.TRACK const queueLength = get().queue.default.length - 1;
? get().current.shuffledIndex const modifier = type === 'next' ? 1 : type === 'prev' ? -1 : 0;
: get().current.index;
return currentIndex === get().queue.default.length - 1; if (isShuffled) {
const currentIndex = get().current.shuffledIndex + modifier;
return currentIndex === queueLength;
}
return get().current.index + modifier === queueLength;
}, },
clearQueue: () => { clearQueue: () => {
set((state) => { set((state) => {
@ -496,15 +513,18 @@ export const usePlayerStore = create<PlayerSlice>()(
state.current.song = nextSong!; state.current.song = nextSong!;
state.queue.previousNode = get().current.song; state.queue.previousNode = get().current.song;
}); });
if (isLastTrack) {
get().actions.setShuffle(PlayerShuffle.TRACK);
}
} else { } else {
const nextIndex = let nextIndex = 0;
repeat === PlayerRepeat.ALL
? isLastTrack if (repeat === PlayerRepeat.ALL) {
? 0 nextIndex = isLastTrack ? 0 : get().current.index + 1;
: get().current.index + 1 } else {
: isLastTrack nextIndex = isLastTrack ? get().current.index : get().current.index + 1;
? get().current.index }
: get().current.index + 1;
set((state) => { set((state) => {
state.current.time = 0; state.current.time = 0;
@ -579,11 +599,22 @@ export const usePlayerStore = create<PlayerSlice>()(
}, },
removeFromQueue: (uniqueIds) => { removeFromQueue: (uniqueIds) => {
const queue = get().queue.default; const queue = get().queue.default;
const currentSong = get().current.song;
const newQueue = queue.filter((song) => !uniqueIds.includes(song.uniqueId)); const newQueue = queue.filter((song) => !uniqueIds.includes(song.uniqueId));
const newShuffledQueue = get().queue.shuffled.filter(
(uniqueId) => !uniqueIds.includes(uniqueId),
);
const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId);
set((state) => { set((state) => {
state.queue.default = newQueue; state.queue.default = newQueue;
state.queue.shuffled = newShuffledQueue;
if (isCurrentSongRemoved) {
state.current.song = newQueue[0];
state.current.index = 0;
}
}); });
return get().actions.getPlayerData(); return get().actions.getPlayerData();