Allow initialIndex on queue add (#67)
- Clean up play queue handler - Split out functions to utils
This commit is contained in:
parent
02caf896ff
commit
3df2915f5f
4 changed files with 200 additions and 157 deletions
|
@ -1,18 +1,25 @@
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { api } from '/@/renderer/api/index';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
||||||
import { usePlayerType } from '/@/renderer/store/settings.store';
|
import { usePlayerType } from '/@/renderer/store/settings.store';
|
||||||
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
|
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
|
||||||
import { toast } from '/@/renderer/components/toast/index';
|
import { toast } from '/@/renderer/components/toast/index';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { LibraryItem, SongListSort, SortOrder, Song } from '/@/renderer/api/types';
|
import { LibraryItem, QueueSong, Song, SongListResponse } from '/@/renderer/api/types';
|
||||||
|
import {
|
||||||
|
getPlaylistSongsById,
|
||||||
|
getSongById,
|
||||||
|
getAlbumSongsById,
|
||||||
|
getAlbumArtistSongsById,
|
||||||
|
} from '/@/renderer/features/player/utils';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const utils = isElectron() ? window.electron.utils : null;
|
const utils = isElectron() ? window.electron.utils : null;
|
||||||
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
|
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
|
||||||
|
|
||||||
|
const addToQueue = usePlayerStore.getState().actions.addToQueue;
|
||||||
|
|
||||||
export const useHandlePlayQueueAdd = () => {
|
export const useHandlePlayQueueAdd = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const playerType = usePlayerType();
|
const playerType = usePlayerType();
|
||||||
|
@ -22,129 +29,22 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
const handlePlayQueueAdd = useCallback(
|
const handlePlayQueueAdd = useCallback(
|
||||||
async (options: PlayQueueAddOptions) => {
|
async (options: PlayQueueAddOptions) => {
|
||||||
if (!server) return toast.error({ message: 'No server selected', type: 'error' });
|
if (!server) return toast.error({ message: 'No server selected', type: 'error' });
|
||||||
let songs = null;
|
const { initialIndex, playType, byData, byItemType } = options;
|
||||||
|
let songs: QueueSong[] | null = null;
|
||||||
|
|
||||||
// const itemCount = options.byItemType?.id?.length || 0;
|
if (byItemType) {
|
||||||
// const fetchId = itemCount > 1 ? nanoid() : null;
|
let songList: SongListResponse | undefined;
|
||||||
|
const { type: itemType, id } = byItemType;
|
||||||
if (options.byItemType) {
|
|
||||||
let songsList: any;
|
|
||||||
let queryFilter: any;
|
|
||||||
let queryKey: any;
|
|
||||||
|
|
||||||
if (options.byItemType.type === LibraryItem.PLAYLIST) {
|
|
||||||
// if (fetchId) {
|
|
||||||
// toast.success({
|
|
||||||
// autoClose: false,
|
|
||||||
// id: fetchId,
|
|
||||||
// loading: true,
|
|
||||||
// message: `This may take a while...`,
|
|
||||||
// title: `Adding ${itemCount} albums to the queue`,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
queryFilter = {
|
|
||||||
id: options.byItemType?.id || [],
|
|
||||||
sortBy: 'id',
|
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
queryKey = queryKeys.playlists.songList(
|
|
||||||
server?.id,
|
|
||||||
options.byItemType?.id?.[0] || '',
|
|
||||||
queryFilter,
|
|
||||||
);
|
|
||||||
} else if (options.byItemType.type === LibraryItem.ALBUM) {
|
|
||||||
// if (fetchId) {
|
|
||||||
// toast.success({
|
|
||||||
// autoClose: false,
|
|
||||||
// id: fetchId,
|
|
||||||
// loading: true,
|
|
||||||
// message: `This may take a while...`,
|
|
||||||
// title: `Adding ${itemCount} albums to the queue`,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
queryFilter = {
|
|
||||||
albumIds: options.byItemType?.id || [],
|
|
||||||
sortBy: SongListSort.ALBUM,
|
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
|
||||||
} else if (options.byItemType.type === LibraryItem.ALBUM_ARTIST) {
|
|
||||||
// if (fetchId) {
|
|
||||||
// toast.success({
|
|
||||||
// autoClose: false,
|
|
||||||
// id: fetchId,
|
|
||||||
// loading: true,
|
|
||||||
// message: `This may take a while...`,
|
|
||||||
// title: `Adding ${itemCount} album artists to the queue`,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
queryFilter = {
|
|
||||||
artistIds: options.byItemType?.id || [],
|
|
||||||
sortBy: SongListSort.ALBUM_ARTIST,
|
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
|
||||||
} else if (options.byItemType.type === LibraryItem.SONG) {
|
|
||||||
queryFilter = { id: options.byItemType.id };
|
|
||||||
queryKey = queryKeys.songs.detail(server?.id, queryFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (options.byItemType?.type === LibraryItem.PLAYLIST) {
|
if (itemType === LibraryItem.PLAYLIST) {
|
||||||
songsList = await queryClient.fetchQuery(
|
songList = await getPlaylistSongsById({ id, queryClient, server });
|
||||||
queryKey,
|
} else if (itemType === LibraryItem.ALBUM) {
|
||||||
async ({ signal }) =>
|
songList = await getAlbumSongsById({ id, queryClient, server });
|
||||||
api.controller.getPlaylistSongList({
|
} else if (itemType === LibraryItem.ALBUM_ARTIST) {
|
||||||
apiClientProps: {
|
songList = await getAlbumArtistSongsById({ id, queryClient, server });
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query: queryFilter,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (options.byItemType?.type === LibraryItem.SONG) {
|
|
||||||
const song = (await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
|
||||||
api.controller.getSongDetail({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query: queryFilter,
|
|
||||||
}),
|
|
||||||
)) as Song;
|
|
||||||
|
|
||||||
if (song) {
|
|
||||||
songsList = { items: [song], startIndex: 0, totalRecordCount: 1 };
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
songsList = await queryClient.fetchQuery(
|
songList = await getSongById({ id, queryClient, server });
|
||||||
queryKey,
|
|
||||||
async ({ signal }) =>
|
|
||||||
api.controller.getSongList({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query: queryFilter,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
cacheTime: 1000 * 60,
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return toast.error({
|
return toast.error({
|
||||||
|
@ -153,24 +53,25 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!songsList) return toast.warn({ message: 'No songs found' });
|
songs = songList?.items?.map((song: Song) => ({ ...song, uniqueId: nanoid() })) || null;
|
||||||
songs = songsList.items?.map((song: Song) => ({ ...song, uniqueId: nanoid() }));
|
} else if (byData) {
|
||||||
} else if (options.byData) {
|
songs = byData.map((song) => ({ ...song, uniqueId: nanoid() }));
|
||||||
songs = options.byData.map((song) => ({ ...song, uniqueId: nanoid() }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!songs) return toast.warn({ message: 'No songs found' });
|
if (!songs) return toast.warn({ message: 'No songs found' });
|
||||||
|
|
||||||
const playerData = usePlayerStore.getState().actions.addToQueue(songs, options.play);
|
const playerData = addToQueue({ initialIndex: initialIndex || 0, playType, songs });
|
||||||
|
|
||||||
if (playerType === PlaybackType.LOCAL) {
|
if (playerType === PlaybackType.LOCAL) {
|
||||||
if (options.play === Play.NEXT || options.play === Play.LAST) {
|
mpvPlayer?.volume(usePlayerStore.getState().volume);
|
||||||
mpvPlayer.setQueueNext(playerData);
|
|
||||||
|
if (playType === Play.NEXT || playType === Play.LAST) {
|
||||||
|
mpvPlayer?.setQueueNext(playerData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.play === Play.NOW) {
|
if (playType === Play.NOW) {
|
||||||
mpvPlayer.setQueue(playerData);
|
mpvPlayer?.setQueue(playerData);
|
||||||
mpvPlayer.play();
|
mpvPlayer?.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,21 +85,6 @@ export const useHandlePlayQueueAdd = () => {
|
||||||
status: 'Playing',
|
status: 'Playing',
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (fetchId) {
|
|
||||||
// toast.update({
|
|
||||||
// autoClose: 1000,
|
|
||||||
// id: fetchId,
|
|
||||||
// message: '',
|
|
||||||
// title: `Added ${songs.length} items to the queue`,
|
|
||||||
// });
|
|
||||||
// // toast.hide(fetchId);
|
|
||||||
// } else {
|
|
||||||
// toast.success({
|
|
||||||
// // message: 'Success',
|
|
||||||
// title: `Added ${songs.length} items to the queue`,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[play, playerType, queryClient, server],
|
[play, playerType, queryClient, server],
|
||||||
|
|
|
@ -3,3 +3,4 @@ export * from './components/left-controls';
|
||||||
export * from './components/playerbar';
|
export * from './components/playerbar';
|
||||||
export * from './context/play-queue-handler-context';
|
export * from './context/play-queue-handler-context';
|
||||||
export * from './hooks/use-playqueue-add';
|
export * from './hooks/use-playqueue-add';
|
||||||
|
export * from './utils';
|
||||||
|
|
155
src/renderer/features/player/utils.ts
Normal file
155
src/renderer/features/player/utils.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import {
|
||||||
|
PlaylistSongListQuery,
|
||||||
|
SongDetailQuery,
|
||||||
|
SongListQuery,
|
||||||
|
SongListResponse,
|
||||||
|
SongListSort,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/renderer/api/types';
|
||||||
|
import { ServerListItem } from '/@/renderer/types';
|
||||||
|
|
||||||
|
export const getPlaylistSongsById = async (args: {
|
||||||
|
id: string;
|
||||||
|
queryClient: QueryClient;
|
||||||
|
server: ServerListItem;
|
||||||
|
}) => {
|
||||||
|
const { id, queryClient, server } = args;
|
||||||
|
|
||||||
|
const queryFilter: PlaylistSongListQuery = {
|
||||||
|
id,
|
||||||
|
sortBy: SongListSort.ID,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryKey = queryKeys.playlists.songList(server?.id, id, queryFilter);
|
||||||
|
|
||||||
|
const res = await queryClient.fetchQuery(
|
||||||
|
queryKey,
|
||||||
|
async ({ signal }) =>
|
||||||
|
api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
query: queryFilter,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAlbumSongsById = async (args: {
|
||||||
|
id: string[];
|
||||||
|
orderByIds?: boolean;
|
||||||
|
queryClient: QueryClient;
|
||||||
|
server: ServerListItem;
|
||||||
|
}) => {
|
||||||
|
const { id, queryClient, server } = args;
|
||||||
|
|
||||||
|
const queryFilter: SongListQuery = {
|
||||||
|
albumIds: id,
|
||||||
|
sortBy: SongListSort.ALBUM,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
|
const res = await queryClient.fetchQuery(
|
||||||
|
queryKey,
|
||||||
|
async ({ signal }) =>
|
||||||
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
query: queryFilter,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAlbumArtistSongsById = async (args: {
|
||||||
|
id: string[];
|
||||||
|
orderByIds?: boolean;
|
||||||
|
queryClient: QueryClient;
|
||||||
|
server: ServerListItem;
|
||||||
|
}) => {
|
||||||
|
const { id, queryClient, server } = args;
|
||||||
|
|
||||||
|
const queryFilter: SongListQuery = {
|
||||||
|
artistIds: id || [],
|
||||||
|
sortBy: SongListSort.ALBUM_ARTIST,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
|
const res = await queryClient.fetchQuery(
|
||||||
|
queryKey,
|
||||||
|
async ({ signal }) =>
|
||||||
|
api.controller.getSongList({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
query: queryFilter,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSongById = async (args: {
|
||||||
|
id: string;
|
||||||
|
queryClient: QueryClient;
|
||||||
|
server: ServerListItem;
|
||||||
|
}): Promise<SongListResponse> => {
|
||||||
|
const { id, queryClient, server } = args;
|
||||||
|
|
||||||
|
const queryFilter: SongDetailQuery = { id };
|
||||||
|
|
||||||
|
const queryKey = queryKeys.songs.detail(server?.id, queryFilter);
|
||||||
|
|
||||||
|
const res = await queryClient.fetchQuery(
|
||||||
|
queryKey,
|
||||||
|
async ({ signal }) =>
|
||||||
|
api.controller.getSongDetail({
|
||||||
|
apiClientProps: {
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
query: queryFilter,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
cacheTime: 1000 * 60,
|
||||||
|
staleTime: 1000 * 60,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res) throw new Error('Song not found');
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: [res],
|
||||||
|
startIndex: 0,
|
||||||
|
totalRecordCount: 1,
|
||||||
|
};
|
||||||
|
};
|
|
@ -96,7 +96,7 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
actions: {
|
actions: {
|
||||||
addToQueue: (args) => {
|
addToQueue: (args) => {
|
||||||
const { 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 queueSongs = map(songs, (song) => ({
|
||||||
|
@ -107,29 +107,30 @@ export const usePlayerStore = create<PlayerSlice>()(
|
||||||
if (playType === Play.NOW) {
|
if (playType === Play.NOW) {
|
||||||
if (get().shuffle === PlayerShuffle.TRACK) {
|
if (get().shuffle === PlayerShuffle.TRACK) {
|
||||||
const shuffledSongs = shuffle(queueSongs);
|
const shuffledSongs = shuffle(queueSongs);
|
||||||
const foundIndex = queueSongs.findIndex(
|
const index = initialIndex || 0;
|
||||||
(song) => song.uniqueId === shuffledSongs[0].uniqueId,
|
const initialSongUniqueId = queueSongs[index].uniqueId;
|
||||||
|
const initialSongIndex = shuffledSongs.findIndex(
|
||||||
|
(song) => song.uniqueId === initialSongUniqueId,
|
||||||
);
|
);
|
||||||
set((state) => {
|
|
||||||
state.queue.shuffled = shuffledSongs.map((song) => song.uniqueId);
|
|
||||||
});
|
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
state.queue.shuffled = shuffledSongs.map((song) => song.uniqueId);
|
||||||
state.queue.default = queueSongs;
|
state.queue.default = queueSongs;
|
||||||
state.current.time = 0;
|
state.current.time = 0;
|
||||||
state.current.player = 1;
|
state.current.player = 1;
|
||||||
state.current.index = foundIndex;
|
state.current.index = initialSongIndex;
|
||||||
state.current.shuffledIndex = 0;
|
state.current.shuffledIndex = 0;
|
||||||
state.current.song = shuffledSongs[0];
|
state.current.song = shuffledSongs[initialSongIndex];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const index = initialIndex || 0;
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.queue.default = queueSongs;
|
state.queue.default = queueSongs;
|
||||||
state.current.time = 0;
|
state.current.time = 0;
|
||||||
state.current.player = 1;
|
state.current.player = 1;
|
||||||
state.current.index = 0;
|
state.current.index = index;
|
||||||
state.current.shuffledIndex = 0;
|
state.current.shuffledIndex = 0;
|
||||||
state.current.song = queueSongs[0];
|
state.current.song = queueSongs[index];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (playType === Play.LAST) {
|
} else if (playType === Play.LAST) {
|
||||||
|
|
Reference in a new issue