import { useCallback, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store'; import { usePlaybackType } 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 playbackType = usePlaybackType(); const server = useCurrentServer(); const { play } = usePlayerControls(); const timeoutIds = useRef> | 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); delete timeoutIds.current[fetchId]; toast.hide(fetchId); } catch (err: any) { if (instanceOfCancellationError(err)) { return null; } clearTimeout(timeoutIds.current[fetchId] as ReturnType); 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 (playbackType === 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, playbackType, queryClient, server, t], ); return handlePlayQueueAdd; };