205 lines
7.8 KiB
TypeScript
205 lines
7.8 KiB
TypeScript
import { useCallback, useRef } from 'react';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
|
import { usePlayerType } from '/@/renderer/store/settings.store';
|
|
import {
|
|
PlayQueueAddOptions,
|
|
Play,
|
|
PlaybackType,
|
|
PlayerStatus,
|
|
PlayerShuffle,
|
|
} from '/@/renderer/types';
|
|
import { toast } from '/@/renderer/components/toast/index';
|
|
import isElectron from 'is-electron';
|
|
import { nanoid } from 'nanoid/non-secure';
|
|
import {
|
|
LibraryItem,
|
|
QueueSong,
|
|
Song,
|
|
SongListResponse,
|
|
instanceOfCancellationError,
|
|
} from '/@/renderer/api/types';
|
|
import {
|
|
getPlaylistSongsById,
|
|
getSongById,
|
|
getAlbumSongsById,
|
|
getAlbumArtistSongsById,
|
|
getSongsByQuery,
|
|
getGenreSongsById,
|
|
} from '/@/renderer/features/player/utils';
|
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
|
|
let queryKey;
|
|
|
|
switch (itemType) {
|
|
case LibraryItem.ALBUM:
|
|
queryKey = queryKeys.songs.list(serverId);
|
|
break;
|
|
case LibraryItem.ALBUM_ARTIST:
|
|
queryKey = queryKeys.songs.list(serverId);
|
|
break;
|
|
case LibraryItem.GENRE:
|
|
queryKey = queryKeys.songs.list(serverId);
|
|
break;
|
|
case LibraryItem.PLAYLIST:
|
|
queryKey = queryKeys.playlists.songList(serverId);
|
|
break;
|
|
case LibraryItem.SONG:
|
|
queryKey = queryKeys.songs.list(serverId);
|
|
break;
|
|
default:
|
|
queryKey = queryKeys.songs.list(serverId);
|
|
break;
|
|
}
|
|
|
|
return queryKey;
|
|
};
|
|
|
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
|
const remote = isElectron() ? window.electron.remote : null;
|
|
|
|
const addToQueue = usePlayerStore.getState().actions.addToQueue;
|
|
|
|
export const useHandlePlayQueueAdd = () => {
|
|
const { t } = useTranslation();
|
|
const queryClient = useQueryClient();
|
|
const playerType = usePlayerType();
|
|
const server = useCurrentServer();
|
|
const { play } = usePlayerControls();
|
|
const timeoutIds = useRef<Record<string, ReturnType<typeof setTimeout>> | null>({});
|
|
|
|
const handlePlayQueueAdd = useCallback(
|
|
async (options: PlayQueueAddOptions) => {
|
|
if (!server) return toast.error({ message: 'No server selected', type: 'error' });
|
|
const { initialIndex, initialSongId, playType, byData, byItemType, query } = options;
|
|
let songs: QueueSong[] | null = null;
|
|
let initialSongIndex = 0;
|
|
|
|
if (byItemType) {
|
|
let songList: SongListResponse | undefined;
|
|
const { type: itemType, id } = byItemType;
|
|
|
|
const fetchId = nanoid();
|
|
timeoutIds.current = {
|
|
...timeoutIds.current,
|
|
[fetchId]: setTimeout(() => {
|
|
toast.info({
|
|
autoClose: false,
|
|
id: fetchId,
|
|
message: t('player.playbackFetchCancel', {
|
|
postProcess: 'sentenceCase',
|
|
}),
|
|
onClose: () => {
|
|
queryClient.cancelQueries({
|
|
exact: false,
|
|
queryKey: getRootQueryKey(itemType, server?.id),
|
|
});
|
|
},
|
|
title: t('player.playbackFetchInProgress', {
|
|
postProcess: 'sentenceCase',
|
|
}),
|
|
});
|
|
}, 2000),
|
|
};
|
|
|
|
try {
|
|
if (itemType === LibraryItem.PLAYLIST) {
|
|
songList = await getPlaylistSongsById({
|
|
id: id?.[0],
|
|
query,
|
|
queryClient,
|
|
server,
|
|
});
|
|
} else if (itemType === LibraryItem.ALBUM) {
|
|
songList = await getAlbumSongsById({ id, query, queryClient, server });
|
|
} else if (itemType === LibraryItem.ALBUM_ARTIST) {
|
|
songList = await getAlbumArtistSongsById({
|
|
id,
|
|
query,
|
|
queryClient,
|
|
server,
|
|
});
|
|
} else if (itemType === LibraryItem.GENRE) {
|
|
songList = await getGenreSongsById({ id, query, queryClient, server });
|
|
} else if (itemType === LibraryItem.SONG) {
|
|
if (id?.length === 1) {
|
|
songList = await getSongById({ id: id?.[0], queryClient, server });
|
|
} else {
|
|
songList = await getSongsByQuery({ query, queryClient, server });
|
|
}
|
|
}
|
|
|
|
clearTimeout(timeoutIds.current[fetchId] as ReturnType<typeof setTimeout>);
|
|
delete timeoutIds.current[fetchId];
|
|
toast.hide(fetchId);
|
|
} catch (err: any) {
|
|
if (instanceOfCancellationError(err)) {
|
|
return null;
|
|
}
|
|
|
|
clearTimeout(timeoutIds.current[fetchId] as ReturnType<typeof setTimeout>);
|
|
delete timeoutIds.current[fetchId];
|
|
toast.hide(fetchId);
|
|
|
|
return toast.error({
|
|
message: err.message,
|
|
title: t('error.genericError', { postProcess: 'sentenceCase' }) as string,
|
|
});
|
|
}
|
|
|
|
songs =
|
|
songList?.items?.map((song: Song) => ({ ...song, uniqueId: nanoid() })) || null;
|
|
} else if (byData) {
|
|
songs = byData.map((song) => ({ ...song, uniqueId: nanoid() })) || null;
|
|
}
|
|
|
|
if (!songs || songs?.length === 0)
|
|
return toast.warn({
|
|
message: t('common.noResultsFromQuery', { postProcess: 'sentenceCase' }),
|
|
title: t('player.playbackFetchNoResults'),
|
|
});
|
|
|
|
if (initialIndex) {
|
|
initialSongIndex = initialIndex;
|
|
} else if (initialSongId) {
|
|
initialSongIndex = songs.findIndex((song) => song.id === initialSongId);
|
|
}
|
|
|
|
const hadSong = usePlayerStore.getState().queue.default.length > 0;
|
|
const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs });
|
|
|
|
if (playerType === PlaybackType.LOCAL) {
|
|
mpvPlayer!.volume(usePlayerStore.getState().volume);
|
|
|
|
if (playType === Play.NOW || !hadSong) {
|
|
mpvPlayer!.pause();
|
|
mpvPlayer!.setQueue(playerData);
|
|
mpvPlayer!.play();
|
|
} else {
|
|
mpvPlayer!.setQueueNext(playerData);
|
|
}
|
|
}
|
|
|
|
// We should only play if the queue was empty, or we are doing play NOW
|
|
// (override the queue).
|
|
if (playType === Play.NOW || !hadSong) {
|
|
play();
|
|
}
|
|
|
|
remote?.updateSong({
|
|
currentTime: usePlayerStore.getState().current.time,
|
|
repeat: usePlayerStore.getState().repeat,
|
|
shuffle: usePlayerStore.getState().shuffle !== PlayerShuffle.NONE,
|
|
song: playerData.current.song,
|
|
status: PlayerStatus.PLAYING,
|
|
});
|
|
|
|
return null;
|
|
},
|
|
[play, playerType, queryClient, server, t],
|
|
);
|
|
|
|
return handlePlayQueueAdd;
|
|
};
|