From 550ba4f768ccab63671988bdae819eb90e9fbd27 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 2 Nov 2025 01:40:20 -0800 Subject: [PATCH] temporarily remove old player implementations to prevent broken app --- src/main/features/core/player/media-keys.ts | 6 +- src/main/index.ts | 6 +- src/renderer/app.tsx | 94 +- .../components/audio-player/index.tsx | 19 +- .../hooks/use-current-song-row-styles.ts | 4 +- .../components/album-detail-content.tsx | 6 +- .../context-menu/context-menu-provider.tsx | 2096 ++++++++--------- .../features/lyrics/lyrics-actions.tsx | 4 +- src/renderer/features/lyrics/lyrics.tsx | 4 +- .../features/lyrics/synchronized-lyrics.tsx | 41 +- .../components/play-queue-list-controls.tsx | 98 +- .../now-playing/components/play-queue.tsx | 89 +- .../player/components/center-controls.tsx | 138 +- .../components/full-screen-player-queue.tsx | 6 +- .../player/components/full-screen-player.tsx | 4 +- .../components/full-screen-similar-songs.tsx | 4 +- .../player/components/left-controls.tsx | 4 +- .../features/player/components/playerbar.tsx | 48 +- .../player/components/right-controls.tsx | 43 +- .../player/hooks/use-center-controls.ts | 1542 ++++++------ .../player/hooks/use-handle-playqueue-add.ts | 400 ++-- .../player/hooks/use-media-session.ts | 8 +- .../player/hooks/use-power-save-blocker.ts | 4 +- .../player/hooks/use-right-controls.ts | 53 +- .../player/mutations/scrobble-mutation.ts | 5 +- .../playlist-detail-song-list-content.tsx | 8 +- .../hotkeys/window-hotkey-settings.tsx | 6 +- .../components/playback/audio-settings.tsx | 46 +- .../playback/media-session-settings.tsx | 4 +- .../components/playback/mpv-settings.tsx | 46 +- .../components/playback/playback-tab.tsx | 4 +- .../mutations/create-favorite-mutation.ts | 6 +- .../mutations/delete-favorite-mutation.ts | 6 +- .../shared/mutations/set-rating-mutation.ts | 8 +- .../features/sidebar/components/sidebar.tsx | 4 +- .../components/similar-songs-list.tsx | 13 +- .../songs/components/song-list-table-view.tsx | 8 +- src/renderer/layouts/default-layout.tsx | 47 +- src/renderer/router/app-outlet.tsx | 28 +- src/renderer/store/player.store.ts | 171 +- src/renderer/store/settings.store.ts | 16 +- src/shared/types/types.ts | 22 +- 42 files changed, 2571 insertions(+), 2598 deletions(-) diff --git a/src/main/features/core/player/media-keys.ts b/src/main/features/core/player/media-keys.ts index 2af856d2a..865d2a131 100644 --- a/src/main/features/core/player/media-keys.ts +++ b/src/main/features/core/player/media-keys.ts @@ -3,7 +3,7 @@ import { BrowserWindow, globalShortcut, systemPreferences } from 'electron'; import { isMacOS, isWindows } from '../../../utils'; import { store } from '../settings'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; export const enableMediaKeys = (window: BrowserWindow | null) => { if (isMacOS()) { @@ -26,9 +26,9 @@ export const enableMediaKeys = (window: BrowserWindow | null) => { } const enableWindowsMediaSession = store.get('mediaSession', false) as boolean; - const playbackType = store.get('playbackType', PlaybackType.WEB) as PlaybackType; + const playbackType = store.get('playbackType', PlayerType.WEB) as PlayerType; - if (!enableWindowsMediaSession || !isWindows() || playbackType !== PlaybackType.WEB) { + if (!enableWindowsMediaSession || !isWindows() || playbackType !== PlayerType.WEB) { globalShortcut.register('MediaStop', () => { window?.webContents.send('renderer-player-stop'); }); diff --git a/src/main/index.ts b/src/main/index.ts index 1e7d75461..aab596b54 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -38,7 +38,7 @@ import { } from './utils'; import './features'; -import { PlaybackType, TitleTheme } from '/@/shared/types/types'; +import { PlayerType, TitleTheme } from '/@/shared/types/types'; export default class AppUpdater { constructor() { @@ -549,9 +549,9 @@ async function createWindow(first = true): Promise { } const enableWindowsMediaSession = store.get('mediaSession', false) as boolean; -const playbackType = store.get('playbackType', PlaybackType.WEB) as PlaybackType; +const playbackType = store.get('playbackType', PlayerType.WEB) as PlayerType; const shouldDisableMediaFeatures = - !isWindows() || !enableWindowsMediaSession || playbackType !== PlaybackType.WEB; + !isWindows() || !enableWindowsMediaSession || playbackType !== PlayerType.WEB; if (shouldDisableMediaFeatures) { app.commandLine.appendSwitch( 'disable-features', diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index 3c8806c18..4968efc02 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -2,44 +2,37 @@ import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-mod import { ModuleRegistry } from '@ag-grid-community/core'; import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model'; import { MantineProvider } from '@mantine/core'; +import '@mantine/core/styles.css'; +import '@mantine/dates/styles.css'; import { Notifications } from '@mantine/notifications'; +import '@mantine/notifications/styles.css'; import isElectron from 'is-electron'; import { useEffect, useMemo, useRef, useState } from 'react'; -import '@mantine/core/styles.css'; -import '@mantine/notifications/styles.css'; -import '@mantine/dates/styles.css'; import '/@/shared/styles/global.css'; import '@ag-grid-community/styles/ag-grid.css'; import 'overlayscrollbars/overlayscrollbars.css'; -import '/styles/overlayscrollbars.css'; import i18n from '/@/i18n/i18n'; import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc'; -import { PlayQueueHandlerContext } from '/@/renderer/features/player/context/play-queue-handler-context'; import { WebAudioContext } from '/@/renderer/features/player/context/webaudio-context'; -import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add'; -import { updateSong } from '/@/renderer/features/player/update-remote-song'; import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings'; import { useServerVersion } from '/@/renderer/hooks/use-server-version'; import { IsUpdatedDialog } from '/@/renderer/is-updated-dialog'; import { AppRouter } from '/@/renderer/router/app-router'; import { - PlayerState, useCssSettings, useHotkeySettings, usePlaybackSettings, - usePlayerStore, - useQueueControls, useRemoteSettings, useSettingsStore, } from '/@/renderer/store'; import { useAppTheme } from '/@/renderer/themes/use-app-theme'; import { sanitizeCss } from '/@/renderer/utils/sanitize'; -import { setQueue } from '/@/renderer/utils/set-transcoded-queue-data'; import { toast } from '/@/shared/components/toast/toast'; -import { PlaybackType, PlayerStatus, WebAudio } from '/@/shared/types/types'; +import { PlayerType, WebAudio } from '/@/shared/types/types'; +import '/styles/overlayscrollbars.css'; ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]); @@ -55,8 +48,8 @@ export const App = () => { const { content, enabled } = useCssSettings(); const { type: playbackType } = usePlaybackSettings(); const { bindings } = useHotkeySettings(); - const handlePlayQueueAdd = useHandlePlayQueueAdd(); - const { clearQueue, restoreQueue } = useQueueControls(); + // const handlePlayQueueAdd = useHandlePlayQueueAdd(); + // const { clearQueue, restoreQueue } = useQueueControls(); const remoteSettings = useRemoteSettings(); const cssRef = useRef(null); useDiscordRpc(); @@ -84,9 +77,9 @@ export const App = () => { return () => {}; }, [content, enabled]); - const providerValue = useMemo(() => { - return { handlePlayQueueAdd }; - }, [handlePlayQueueAdd]); + // const providerValue = useMemo(() => { + // return { handlePlayQueueAdd }; + // }, [handlePlayQueueAdd]); const webAudioProvider = useMemo(() => { return { setWebAudio, webAudio }; @@ -95,7 +88,7 @@ export const App = () => { // Start the mpv instance on startup useEffect(() => { const initializeMpv = async () => { - if (playbackType === PlaybackType.LOCAL) { + if (playbackType === PlayerType.LOCAL) { const isRunning: boolean | undefined = await mpvPlayer?.isRunning(); mpvPlayer?.stop(); @@ -103,7 +96,7 @@ export const App = () => { if (!isRunning) { const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; const properties: Record = { - speed: usePlayerStore.getState().speed, + // speed: usePlayerStore.getState().speed, ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), }; @@ -124,11 +117,10 @@ export const App = () => { } return () => { - clearQueue(); mpvPlayer?.stop(); mpvPlayer?.cleanup(); }; - }, [clearQueue, playbackType]); + }, [playbackType]); useEffect(() => { if (isElectron()) { @@ -136,34 +128,34 @@ export const App = () => { } }, [bindings]); - useEffect(() => { - if (utils) { - utils.onSaveQueue(() => { - const { current, queue } = usePlayerStore.getState(); - const stateToSave: Partial> = { - current: { - ...current, - status: PlayerStatus.PAUSED, - }, - queue, - }; - utils.saveQueue(stateToSave); - }); + // useEffect(() => { + // if (utils) { + // utils.onSaveQueue(() => { + // const { current, queue } = usePlayerStore.getState(); + // const stateToSave: Partial> = { + // current: { + // ...current, + // status: PlayerStatus.PAUSED, + // }, + // queue, + // }; + // utils.saveQueue(stateToSave); + // }); - utils.onRestoreQueue((_event: any, data) => { - const playerData = restoreQueue(data); - if (playbackType === PlaybackType.LOCAL) { - setQueue(playerData, true); - } - updateSong(playerData.current.song); - }); - } + // utils.onRestoreQueue((_event: any, data) => { + // const playerData = restoreQueue(data); + // if (playbackType === PlaybackType.LOCAL) { + // setQueue(playerData, true); + // } + // updateSong(playerData.current.song); + // }); + // } - return () => { - ipc?.removeAllListeners('renderer-restore-queue'); - ipc?.removeAllListeners('renderer-save-queue'); - }; - }, [playbackType, restoreQueue]); + // return () => { + // ipc?.removeAllListeners('renderer-restore-queue'); + // ipc?.removeAllListeners('renderer-save-queue'); + // }; + // }, [playbackType, restoreQueue]); useEffect(() => { if (remote) { @@ -200,11 +192,9 @@ export const App = () => { }} zIndex={50000} /> - - - - - + + + ); diff --git a/src/renderer/components/audio-player/index.tsx b/src/renderer/components/audio-player/index.tsx index b288b6c84..6fddb0a14 100644 --- a/src/renderer/components/audio-player/index.tsx +++ b/src/renderer/components/audio-player/index.tsx @@ -20,10 +20,15 @@ import { gaplessHandler, } from '/@/renderer/components/audio-player/utils/list-handlers'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; -import { TranscodingConfig, usePlaybackSettings, useSpeed } from '/@/renderer/store'; -import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store'; +import { + TranscodingConfig, + usePlaybackSettings, + usePlayerSpeed, + useSettingsStore, + useSettingsStoreActions, +} from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; -import { PlaybackStyle, PlayerStatus } from '/@/shared/types/types'; +import { PlayerStatus, PlayerStyle } from '/@/shared/types/types'; export type AudioPlayerProgress = { loaded: number; @@ -38,7 +43,7 @@ interface AudioPlayerProps extends ReactPlayerProps { crossfadeStyle: CrossfadeStyle; currentPlayer: 1 | 2; muted: boolean; - playbackStyle: PlaybackStyle; + playbackStyle: PlayerStyle; player1?: Song; player2?: Song; status: PlayerStatus; @@ -122,7 +127,7 @@ export const AudioPlayer = forwardRef((props, const shouldUseWebAudio = useSettingsStore((state) => state.playback.webAudio); const preservesPitch = useSettingsStore((state) => state.playback.preservePitch); const { resetSampleRate } = useSettingsStoreActions(); - const playbackSpeed = useSpeed(); + const playbackSpeed = usePlayerSpeed(); const { transcode } = usePlaybackSettings(); const stream1 = useSongUrl(transcode, currentPlayer === 1, player1); @@ -448,7 +453,7 @@ export const AudioPlayer = forwardRef((props, onEnded={stream1 ? handleOnEnded : undefined} onError={handleOnError(player1Ref)} onProgress={ - playbackStyle === PlaybackStyle.GAPLESS ? handleGapless1 : handleCrossfade1 + playbackStyle === PlayerStyle.GAPLESS ? handleGapless1 : handleCrossfade1 } onReady={handlePlayer1Start} playbackRate={playbackSpeed} @@ -468,7 +473,7 @@ export const AudioPlayer = forwardRef((props, onEnded={stream2 ? handleOnEnded : undefined} onError={handleOnError(player2Ref)} onProgress={ - playbackStyle === PlaybackStyle.GAPLESS ? handleGapless2 : handleCrossfade2 + playbackStyle === PlayerStyle.GAPLESS ? handleGapless2 : handleCrossfade2 } onReady={handlePlayer2Start} playbackRate={playbackSpeed} diff --git a/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts b/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts index b2782b572..42f2f5a15 100644 --- a/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts +++ b/src/renderer/components/virtual-table/hooks/use-current-song-row-styles.ts @@ -4,7 +4,7 @@ import { RowClassRules, RowNode } from '@ag-grid-community/core'; import { MutableRefObject, useEffect, useMemo, useRef } from 'react'; import { useAppFocus } from '/@/renderer/hooks'; -import { useCurrentSong, usePlayerStore } from '/@/renderer/store'; +import { usePlayerSong, usePlayerStore } from '/@/renderer/store'; import { Song } from '/@/shared/types/domain-types'; import { PlayerStatus } from '/@/shared/types/types'; @@ -13,7 +13,7 @@ interface UseCurrentSongRowStylesProps { } export const useCurrentSongRowStyles = ({ tableRef }: UseCurrentSongRowStylesProps) => { - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const isFocused = useAppFocus(); const isFocusedRef = useRef(isFocused); diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index a78d74216..3d4340d1e 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -36,7 +36,7 @@ import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete- import { useAppFocus, useContainerQuery } from '/@/renderer/hooks'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useCurrentSong, useCurrentStatus } from '/@/renderer/store'; +import { useCurrentServer, usePlayerSong, usePlayerStatus } from '/@/renderer/store'; import { PersistedTableColumn, useGeneralSettings, @@ -85,9 +85,9 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP const handlePlayQueueAdd = usePlayQueueAdd(); const tableConfig = useTableSettings('albumDetail'); const { setTable } = useSettingsStoreActions(); - const status = useCurrentStatus(); + const status = usePlayerStatus(); const isFocused = useAppFocus(); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const { externalLinks, lastFM, musicBrainz } = useGeneralSettings(); const genreRoute = useGenreRoute(); diff --git a/src/renderer/features/context-menu/context-menu-provider.tsx b/src/renderer/features/context-menu/context-menu-provider.tsx index e2eade4ce..07a44c29f 100644 --- a/src/renderer/features/context-menu/context-menu-provider.tsx +++ b/src/renderer/features/context-menu/context-menu-provider.tsx @@ -1,1048 +1,1048 @@ -import { RowNode } from '@ag-grid-community/core'; -import { - useClickOutside, - useMergedRef, - useResizeObserver, - useSetState, - useViewportSize, -} from '@mantine/hooks'; -import { closeAllModals, openContextModal, openModal } from '@mantine/modals'; -import isElectron from 'is-electron'; -import { AnimatePresence } from 'motion/react'; -import { - createContext, - Fragment, - ReactNode, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { generatePath, useNavigate } from 'react-router-dom'; - -import { api } from '/@/renderer/api'; -import { controller } from '/@/renderer/api/controller'; -import { ContextMenu, ContextMenuButton } from '/@/renderer/components/context-menu/context-menu'; -import { - ContextMenuItemType, - OpenContextMenuProps, - useContextMenuEvents, -} from '/@/renderer/features/context-menu/events'; -import { ItemDetailsModal } from '/@/renderer/features/item-details/components/item-details-modal'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { updateSong } from '/@/renderer/features/player/update-remote-song'; -import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; -import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation'; -import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; -import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; -import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; -import { AppRoute } from '/@/renderer/router/routes'; -import { - useAuthStore, - useCurrentServer, - usePlayerStore, - useQueueControls, - useSettingsStore, -} from '/@/renderer/store'; -import { usePlaybackType } from '/@/renderer/store/settings.store'; -import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; -import { hasFeature } from '/@/shared/api/utils'; -import { Divider } from '/@/shared/components/divider/divider'; -import { Group } from '/@/shared/components/group/group'; -import { HoverCard } from '/@/shared/components/hover-card/hover-card'; -import { Icon } from '/@/shared/components/icon/icon'; -import { ConfirmModal } from '/@/shared/components/modal/modal'; -import { Portal } from '/@/shared/components/portal/portal'; -import { Rating } from '/@/shared/components/rating/rating'; -import { Stack } from '/@/shared/components/stack/stack'; -import { Text } from '/@/shared/components/text/text'; -import { toast } from '/@/shared/components/toast/toast'; -import { - AnyLibraryItem, - AnyLibraryItems, - LibraryItem, - ServerType, -} from '/@/shared/types/domain-types'; -import { ServerFeature } from '/@/shared/types/features-types'; -import { Play, PlaybackType } from '/@/shared/types/types'; - -type ContextMenuContextProps = { - closeContextMenu: () => void; - openContextMenu: (args: OpenContextMenuProps) => void; -}; - -type ContextMenuItem = { - children?: ContextMenuItem[]; - disabled?: boolean; - id: string; - label: ReactNode | string; - leftIcon?: ReactNode; - onClick?: (...args: any) => any; - rightIcon?: ReactNode; -}; - -const ContextMenuContext = createContext({ - closeContextMenu: () => {}, - openContextMenu: (args: OpenContextMenuProps) => { - return args; - }, -}); - -const JELLYFIN_IGNORED_MENU_ITEMS: ContextMenuItemType[] = ['setRating', 'shareItem']; -// const NAVIDROME_IGNORED_MENU_ITEMS: ContextMenuItemType[] = []; -// const SUBSONIC_IGNORED_MENU_ITEMS: ContextMenuItemType[] = []; - -const utils = isElectron() ? window.api.utils : null; - -export interface ContextMenuProviderProps { - children: ReactNode; -} - -export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { - const disabledItems = useSettingsStore((state) => state.general.disabledContextMenu); - const { t } = useTranslation(); - const [opened, setOpened] = useState(false); - - const clickOutsideRef = useClickOutside(() => setOpened(false), ['mousedown', 'touchstart']); - - const viewport = useViewportSize(); - const server = useCurrentServer(); - const serverType = server?.type; - const [ref, menuRect] = useResizeObserver(); - const [ctx, setCtx] = useSetState({ - data: [], - dataNodes: [], - menuItems: [], - resetGridCache: undefined, - tableApi: undefined, - type: LibraryItem.SONG, - xPos: 0, - yPos: 0, - }); - - const [rating, setRating] = useState(0); - - useEffect(() => { - if (opened && ctx.data.length > 0) { - if (ctx.data.length === 1) { - setRating(ctx.data[0].userRating ?? 0); - } else { - const firstRating = ctx.data[0].userRating ?? 0; - const allSameRating = ctx.data.every( - (item) => (item.userRating ?? 0) === firstRating, - ); - setRating(allSameRating ? firstRating : 0); - } - } else { - setRating(0); - } - }, [ctx.data, opened]); - - const handlePlayQueueAdd = usePlayQueueAdd(); - const navigate = useNavigate(); - - const openContextMenu = useCallback( - (args: OpenContextMenuProps) => { - const { - context, - data, - dataNodes, - menuItems, - resetGridCache, - tableApi, - type, - xPos, - yPos, - } = args; - - const serverType = data[0]?.serverType || useAuthStore.getState().currentServer?.type; - let validMenuItems = menuItems.filter((item) => !disabledItems[item.id]); - - if (serverType === ServerType.JELLYFIN) { - validMenuItems = menuItems.filter( - (item) => !JELLYFIN_IGNORED_MENU_ITEMS.includes(item.id), - ); - } - - // If the context menu dimension can't be automatically calculated, calculate it manually - // This is a hacky way since resize observer may not automatically recalculate when not rendered - const menuHeight = menuRect.height || (validMenuItems.length + 1) * 40; - const menuWidth = menuRect.width || 220; - - const shouldReverseY = yPos + menuHeight > viewport.height; - const shouldReverseX = xPos + menuWidth > viewport.width; - - const calculatedXPos = shouldReverseX ? xPos - menuWidth : xPos; - const calculatedYPos = shouldReverseY ? yPos - menuHeight : yPos; - - setCtx({ - context, - data, - dataNodes, - menuItems: validMenuItems, - resetGridCache, - tableApi, - type, - xPos: calculatedXPos, - yPos: calculatedYPos, - }); - setOpened(true); - }, - [disabledItems, menuRect.height, menuRect.width, setCtx, viewport.height, viewport.width], - ); - - const closeContextMenu = useCallback(() => { - setOpened(false); - setCtx({ - data: [], - dataNodes: [], - menuItems: [], - tableApi: undefined, - type: LibraryItem.SONG, - xPos: 0, - yPos: 0, - }); - }, [setCtx]); - - useContextMenuEvents({ - closeContextMenu, - openContextMenu, - }); - - const handlePlay = useCallback( - (playType: Play) => { - switch (ctx.type) { - case LibraryItem.ALBUM: - handlePlayQueueAdd?.({ - byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, - playType, - }); - break; - case LibraryItem.ALBUM_ARTIST: - handlePlayQueueAdd?.({ - byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, - playType, - }); - break; - case LibraryItem.ARTIST: - handlePlayQueueAdd?.({ - byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, - playType, - }); - break; - case LibraryItem.GENRE: - handlePlayQueueAdd?.({ - byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, - playType, - }); - break; - case LibraryItem.PLAYLIST: - for (const item of ctx.data) { - handlePlayQueueAdd?.({ - byItemType: { id: [item.id], type: ctx.type }, - playType, - }); - } - - break; - case LibraryItem.SONG: - handlePlayQueueAdd?.({ byData: ctx.data, playType }); - break; - } - }, - [ctx.data, ctx.type, handlePlayQueueAdd], - ); - - const deletePlaylistMutation = useDeletePlaylist({}); - - const handleDeletePlaylist = useCallback(() => { - for (const item of ctx.data) { - deletePlaylistMutation?.mutate( - { apiClientProps: { serverId: item.serverId }, query: { id: item.id } }, - { - onError: (err) => { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - }, - onSuccess: () => { - toast.success({ - message: `Playlist has been deleted`, - }); - - ctx.tableApi?.refreshInfiniteCache(); - ctx.resetGridCache?.(); - }, - }, - ); - } - - closeAllModals(); - }, [ctx, deletePlaylistMutation, t]); - - const openDeletePlaylistModal = useCallback(() => { - openModal({ - children: ( - - - {t('common.areYouSure', { postProcess: 'sentenceCase' })} -
    - {ctx.data.map((item) => ( -
  • - - —{item.name} - -
  • - ))} -
-
-
- ), - title: t('page.contextMenu.deletePlaylist', { postProcess: 'titleCase' }), - }); - }, [ctx.data, handleDeletePlaylist, t]); - - const createFavoriteMutation = useCreateFavorite({}); - const deleteFavoriteMutation = useDeleteFavorite({}); - const handleAddToFavorites = useCallback(() => { - if (!ctx.dataNodes && !ctx.data) return; - - if (ctx.dataNodes) { - const nodesToFavorite = ctx.dataNodes.filter((item) => !item.data.userFavorite); - - const nodesByServerId = nodesToFavorite.reduce( - (acc, node) => { - if (!acc[node.data.serverId]) { - acc[node.data.serverId] = []; - } - acc[node.data.serverId].push(node); - return acc; - }, - {} as Record[]>, - ); - - for (const serverId of Object.keys(nodesByServerId)) { - const nodes = nodesByServerId[serverId]; - const items = nodes.map((node) => node.data); - - createFavoriteMutation.mutate( - { - apiClientProps: { serverId }, - query: { - id: items.map((item) => item.id), - type: ctx.type, - }, - }, - { - onError: (err) => { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - }, - onSuccess: () => { - for (const node of nodes) { - node.setData({ ...node.data, userFavorite: true }); - } - }, - }, - ); - } - } else { - const itemsToFavorite = ctx.data.filter((item) => !item.userFavorite); - const itemsByServerId = (itemsToFavorite as any[]).reduce( - (acc, item) => { - if (!acc[item.serverId]) { - acc[item.serverId] = []; - } - acc[item.serverId].push(item); - return acc; - }, - {} as Record, - ); - - for (const serverId of Object.keys(itemsByServerId)) { - const items = itemsByServerId[serverId]; - - createFavoriteMutation.mutate( - { - apiClientProps: { serverId }, - query: { - id: items.map((item: AnyLibraryItem) => item.id), - type: ctx.type, - }, - }, - { - onError: (err) => { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - }, - }, - ); - } - } - }, [createFavoriteMutation, ctx.data, ctx.dataNodes, ctx.type, t]); - - const handleRemoveFromFavorites = useCallback(() => { - if (!ctx.dataNodes && !ctx.data) return; - - if (ctx.dataNodes) { - const nodesToUnfavorite = ctx.dataNodes.filter((item) => item.data.userFavorite); - const nodesByServerId = nodesToUnfavorite.reduce( - (acc, node) => { - if (!acc[node.data.serverId]) { - acc[node.data.serverId] = []; - } - acc[node.data.serverId].push(node); - return acc; - }, - {} as Record[]>, - ); - - for (const serverId of Object.keys(nodesByServerId)) { - const idsToUnfavorite = nodesByServerId[serverId].map((node) => node.data.id); - deleteFavoriteMutation.mutate( - { - apiClientProps: { serverId }, - query: { - id: idsToUnfavorite, - type: ctx.type, - }, - }, - { - onSuccess: () => { - for (const node of nodesToUnfavorite) { - node.setData({ ...node.data, userFavorite: false }); - } - }, - }, - ); - } - } else { - const itemsToUnfavorite = ctx.data.filter((item) => item.userFavorite); - const itemsByServerId = (itemsToUnfavorite as any[]).reduce( - (acc, item) => { - if (!acc[item.serverId]) { - acc[item.serverId] = []; - } - acc[item.serverId].push(item); - return acc; - }, - {} as Record, - ); - - for (const serverId of Object.keys(itemsByServerId)) { - const idsToUnfavorite = itemsByServerId[serverId].map( - (item: AnyLibraryItem) => item.id, - ); - deleteFavoriteMutation.mutate({ - apiClientProps: { serverId }, - query: { - id: idsToUnfavorite, - type: ctx.type, - }, - }); - } - } - }, [ctx.data, ctx.dataNodes, ctx.type, deleteFavoriteMutation]); - - const handleAddToPlaylist = useCallback(() => { - if (!ctx.dataNodes && !ctx.data) return; - - const albumId: string[] = []; - const artistId: string[] = []; - const songId: string[] = []; - const genreId: string[] = []; - - if (ctx.dataNodes) { - for (const node of ctx.dataNodes) { - switch (node.data.itemType) { - case LibraryItem.ALBUM: - albumId.push(node.data.id); - break; - case LibraryItem.ARTIST: - artistId.push(node.data.id); - break; - case LibraryItem.GENRE: - genreId.push(node.data.id); - break; - case LibraryItem.SONG: - songId.push(node.data.id); - break; - } - } - } else { - for (const item of ctx.data) { - switch (item.itemType) { - case LibraryItem.ALBUM: - albumId.push(item.id); - break; - case LibraryItem.ALBUM_ARTIST: - artistId.push(item.id); - break; - case LibraryItem.ARTIST: - artistId.push(item.id); - break; - case LibraryItem.GENRE: - genreId.push(item.id); - break; - case LibraryItem.SONG: - songId.push(item.id); - break; - } - } - } - - openContextModal({ - innerProps: { - albumId: albumId.length > 0 ? albumId : undefined, - artistId: artistId.length > 0 ? artistId : undefined, - genreId: genreId.length > 0 ? genreId : undefined, - songId: songId.length > 0 ? songId : undefined, - }, - modal: 'addToPlaylist', - size: 'lg', - title: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }), - }); - }, [ctx.data, ctx.dataNodes, t]); - - const removeFromPlaylistMutation = useRemoveFromPlaylist(); - - const handleRemoveFromPlaylist = useCallback(() => { - let songId: string[] | undefined; - - switch (serverType) { - case ServerType.JELLYFIN: - case ServerType.NAVIDROME: - songId = ctx.dataNodes?.map((node) => node.data.playlistItemId); - break; - case ServerType.SUBSONIC: - songId = ctx.dataNodes?.map((node) => node.rowIndex!.toString()); - break; - } - - const confirm = () => { - removeFromPlaylistMutation.mutate( - { - apiClientProps: { serverId: ctx.data?.[0]?.serverId }, - query: { - id: ctx.context.playlistId, - songId: songId || [], - }, - }, - { - onError: (err) => { - toast.error({ - message: err.message, - title: t('error.genericError', { postProcess: 'sentenceCase' }), - }); - }, - onSuccess: () => { - closeAllModals(); - }, - }, - ); - }; - - openModal({ - children: ( - - {t('common.areYouSure', { postProcess: 'sentenceCase' })} - - ), - title: t('page.contextMenu.removeFromPlaylist', { postProcess: 'sentenceCase' }), - }); - }, [ - ctx.context?.playlistId, - ctx.data, - ctx.dataNodes, - removeFromPlaylistMutation, - serverType, - t, - ]); - - const updateRatingMutation = useSetRating({}); - - const handleUpdateRating = useCallback( - (newRating: number) => { - if (!ctx.dataNodes && !ctx.data) return; - - let uniqueServerIds: string[] = []; - let items: AnyLibraryItems = []; - - if (ctx.dataNodes) { - uniqueServerIds = ctx.dataNodes.reduce((acc, node) => { - if (!acc.includes(node.data.serverId)) { - acc.push(node.data.serverId); - } - return acc; - }, [] as string[]); - } else { - uniqueServerIds = ctx.data.reduce((acc, item) => { - if (!acc.includes(item.serverId)) { - acc.push(item.serverId); - } - return acc; - }, [] as string[]); - } - - const ratingToSet = newRating === rating ? 0 : newRating; - - for (const serverId of uniqueServerIds) { - if (ctx.dataNodes) { - items = ctx.dataNodes - .filter((node) => node.data.serverId === serverId) - .map((node) => node.data); - } else { - items = ctx.data.filter((item) => item.serverId === serverId); - } - - updateRatingMutation.mutate( - { - apiClientProps: { serverId }, - query: { - item: items, - rating: ratingToSet, - }, - }, - { - onSuccess: () => { - if (ctx.dataNodes) { - for (const node of ctx.dataNodes) { - node.setData({ ...node.data, userRating: ratingToSet }); - } - } - }, - }, - ); - } - }, - [ctx.data, ctx.dataNodes, updateRatingMutation, rating], - ); - - const playbackType = usePlaybackType(); - const { moveToBottomOfQueue, moveToNextOfQueue, moveToTopOfQueue, removeFromQueue } = - useQueueControls(); - - const handleMoveToNext = useCallback(() => { - const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); - if (!uniqueIds?.length) return; - - const playerData = moveToNextOfQueue(uniqueIds); - - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } - }, [ctx.dataNodes, moveToNextOfQueue, playbackType]); - - const handleMoveToBottom = useCallback(() => { - const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); - if (!uniqueIds?.length) return; - - const playerData = moveToBottomOfQueue(uniqueIds); - - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } - }, [ctx.dataNodes, moveToBottomOfQueue, playbackType]); - - const handleMoveToTop = useCallback(() => { - const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); - if (!uniqueIds?.length) return; - - const playerData = moveToTopOfQueue(uniqueIds); - - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } - }, [ctx.dataNodes, moveToTopOfQueue, playbackType]); - - const handleShareItem = useCallback(() => { - if (!ctx.dataNodes && !ctx.data) return; - - const uniqueIds = ctx.data.map((node) => node.id); - - openContextModal({ - innerProps: { - itemIds: uniqueIds, - resourceType: ctx.data[0].itemType, - }, - modal: 'shareItem', - size: 'md', - title: t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' }), - }); - }, [ctx.data, ctx.dataNodes, t]); - - const handleRemoveSelected = useCallback(() => { - const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); - if (!uniqueIds?.length) return; - - const currentSong = usePlayerStore.getState().current.song; - const playerData = removeFromQueue(uniqueIds); - const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId); - - if (playbackType === PlaybackType.LOCAL) { - if (isCurrentSongRemoved) { - setQueue(playerData); - } else { - setQueueNext(playerData); - } - } - - ctx.tableApi?.redrawRows(); - - if (isCurrentSongRemoved) { - updateSong(playerData.current.song); - } - }, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]); - - const handleDeselectAll = useCallback(() => { - ctx.tableApi?.deselectAll(); - }, [ctx.tableApi]); - - const handleOpenItemDetails = useCallback(() => { - const item = ctx.data[0]; - - openModal({ - children: , - size: 'xl', - title: t('page.contextMenu.showDetails', { postProcess: 'titleCase' }), - }); - }, [ctx.data, t]); - - const handleSimilar = useCallback(async () => { - const item = ctx.data[0]; - const songs = await controller.getSimilarSongs({ - apiClientProps: { - serverId: item.serverId, - signal: undefined, - }, - query: { albumArtistIds: item.albumArtistIds, songId: item.id }, - }); - if (songs) { - handlePlayQueueAdd?.({ byData: [ctx.data[0], ...songs], playType: Play.NOW }); - } - }, [ctx, handlePlayQueueAdd]); - - const handleDownload = useCallback(() => { - const item = ctx.data[0]; - const url = api.controller.getDownloadUrl({ - apiClientProps: { serverId: item.serverId }, - query: { id: item.id }, - }); - - if (utils) { - utils.download(url!); - } else { - window.open(url, '_blank'); - } - }, [ctx.data]); - - const handleGoToAlbum = useCallback(() => { - const item = ctx.data[0]; - if (item.albumId) { - navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: item.albumId })); - } - }, [ctx.data, navigate]); - - const handleGoToAlbumArtist = useCallback(() => { - const item = ctx.data[0]; - if (item.albumArtists && item.albumArtists.length > 0) { - navigate( - generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { - albumArtistId: item.albumArtists[0].id, - }), - ); - } - }, [ctx.data, navigate]); - - const contextMenuItems: Record = useMemo(() => { - return { - addToFavorites: { - id: 'addToFavorites', - label: t('page.contextMenu.addToFavorites', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleAddToFavorites, - }, - addToPlaylist: { - id: 'addToPlaylist', - label: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleAddToPlaylist, - }, - createPlaylist: { - id: 'createPlaylist', - label: t('page.contextMenu.createPlaylist', { postProcess: 'sentenceCase' }), - onClick: () => {}, - }, - deletePlaylist: { - id: 'deletePlaylist', - label: t('page.contextMenu.deletePlaylist', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: openDeletePlaylistModal, - }, - deselectAll: { - id: 'deselectAll', - label: t('page.contextMenu.deselectAll', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleDeselectAll, - }, - download: { - disabled: ctx.data?.length !== 1, - id: 'download', - label: t('page.contextMenu.download', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleDownload, - }, - goToAlbum: { - disabled: ctx.data?.length !== 1 || !ctx.data[0]?.albumId, - id: 'goToAlbum', - label: t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleGoToAlbum, - }, - goToAlbumArtist: { - disabled: - ctx.data?.length !== 1 || - !ctx.data[0]?.albumArtists || - ctx.data[0]?.albumArtists?.length === 0, - id: 'goToAlbumArtist', - label: t('page.contextMenu.goToAlbumArtist', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleGoToAlbumArtist, - }, - moveToBottomOfQueue: { - id: 'moveToBottomOfQueue', - label: t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleMoveToBottom, - }, - moveToNextOfQueue: { - id: 'moveToNext', - label: t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleMoveToNext, - }, - moveToTopOfQueue: { - id: 'moveToTopOfQueue', - label: t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleMoveToTop, - }, - play: { - id: 'play', - label: t('page.contextMenu.play', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: () => handlePlay(Play.NOW), - }, - playLast: { - id: 'playLast', - label: t('page.contextMenu.addLast', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: () => handlePlay(Play.LAST), - }, - playNext: { - id: 'playNext', - label: t('page.contextMenu.addNext', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: () => handlePlay(Play.NEXT), - }, - playShuffled: { - id: 'playShuffled', - label: t('page.contextMenu.playShuffled', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: () => handlePlay(Play.SHUFFLE), - }, - playSimilarSongs: { - id: 'playSimilarSongs', - label: t('page.contextMenu.playSimilarSongs', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleSimilar, - }, - removeFromFavorites: { - id: 'removeFromFavorites', - label: t('page.contextMenu.removeFromFavorites', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleRemoveFromFavorites, - }, - removeFromPlaylist: { - id: 'removeFromPlaylist', - label: t('page.contextMenu.removeFromPlaylist', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleRemoveFromPlaylist, - }, - removeFromQueue: { - id: 'removeSongs', - label: t('page.contextMenu.removeFromQueue', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleRemoveSelected, - }, - setRating: { - id: 'setRating', - label: t('action.setRating', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: () => {}, - rightIcon: ( - - { - handleUpdateRating(e); - setRating(e); - }} - size="xs" - value={rating} - /> - - ), - }, - shareItem: { - disabled: !hasFeature(server, ServerFeature.SHARING_ALBUM_SONG), - id: 'shareItem', - label: t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleShareItem, - }, - showDetails: { - disabled: ctx.data?.length !== 1 || !ctx.data[0].itemType, - id: 'showDetails', - label: t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }), - leftIcon: , - onClick: handleOpenItemDetails, - }, - }; - }, [ - t, - handleAddToFavorites, - handleAddToPlaylist, - openDeletePlaylistModal, - handleDeselectAll, - ctx.data, - handleDownload, - handleMoveToNext, - handleMoveToBottom, - handleMoveToTop, - handleSimilar, - handleRemoveFromFavorites, - handleRemoveFromPlaylist, - handleRemoveSelected, - server, - handleShareItem, - handleGoToAlbum, - handleGoToAlbumArtist, - handleOpenItemDetails, - handlePlay, - handleUpdateRating, - rating, - ]); - - const mergedRef = useMergedRef(ref, clickOutsideRef); - - const providerValue = useMemo( - () => ({ - closeContextMenu, - openContextMenu, - }), - [closeContextMenu, openContextMenu], - ); - - return ( - - - - {opened && ( - - - - {ctx.menuItems?.map((item) => { - return ( - !contextMenuItems[item.id].disabled && ( - - {item.children ? ( - - - - { - contextMenuItems[item.id] - .label - } - - - - - {contextMenuItems[ - item.id - ].children?.map((child) => ( - - {child.label} - - ))} - - - - ) : ( - - {contextMenuItems[item.id].label} - - )} - - {item.divider && ( - - )} - - ) - ); - })} - - - {t('page.contextMenu.numberSelected', { - count: ctx.data?.length || 0, - postProcess: 'lowerCase', - })} - - - - )} - - {children} - - - ); -}; +// import { RowNode } from '@ag-grid-community/core'; +// import { +// useClickOutside, +// useMergedRef, +// useResizeObserver, +// useSetState, +// useViewportSize, +// } from '@mantine/hooks'; +// import { closeAllModals, openContextModal, openModal } from '@mantine/modals'; +// import isElectron from 'is-electron'; +// import { AnimatePresence } from 'motion/react'; +// import { +// createContext, +// Fragment, +// ReactNode, +// useCallback, +// useEffect, +// useMemo, +// useState, +// } from 'react'; +// import { useTranslation } from 'react-i18next'; +// import { generatePath, useNavigate } from 'react-router-dom'; + +// import { api } from '/@/renderer/api'; +// import { controller } from '/@/renderer/api/controller'; +// import { ContextMenu, ContextMenuButton } from '/@/renderer/components/context-menu/context-menu'; +// import { +// ContextMenuItemType, +// OpenContextMenuProps, +// useContextMenuEvents, +// } from '/@/renderer/features/context-menu/events'; +// import { ItemDetailsModal } from '/@/renderer/features/item-details/components/item-details-modal'; +// import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; +// import { updateSong } from '/@/renderer/features/player/update-remote-song'; +// import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation'; +// import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/remove-from-playlist-mutation'; +// import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; +// import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; +// import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; +// import { AppRoute } from '/@/renderer/router/routes'; +// import { +// getServerById, +// useAuthStore, +// useCurrentServer, +// usePlayerStore, +// useSettingsStore, +// } from '/@/renderer/store'; +// import { usePlaybackType } from '/@/renderer/store/settings.store'; +// import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; +// import { hasFeature } from '/@/shared/api/utils'; +// import { Divider } from '/@/shared/components/divider/divider'; +// import { Group } from '/@/shared/components/group/group'; +// import { HoverCard } from '/@/shared/components/hover-card/hover-card'; +// import { Icon } from '/@/shared/components/icon/icon'; +// import { ConfirmModal } from '/@/shared/components/modal/modal'; +// import { Portal } from '/@/shared/components/portal/portal'; +// import { Rating } from '/@/shared/components/rating/rating'; +// import { Stack } from '/@/shared/components/stack/stack'; +// import { Text } from '/@/shared/components/text/text'; +// import { toast } from '/@/shared/components/toast/toast'; +// import { +// AnyLibraryItem, +// AnyLibraryItems, +// LibraryItem, +// ServerType, +// } from '/@/shared/types/domain-types'; +// import { ServerFeature } from '/@/shared/types/features-types'; +// import { Play, PlaybackType } from '/@/shared/types/types'; + +// type ContextMenuContextProps = { +// closeContextMenu: () => void; +// openContextMenu: (args: OpenContextMenuProps) => void; +// }; + +// type ContextMenuItem = { +// children?: ContextMenuItem[]; +// disabled?: boolean; +// id: string; +// label: ReactNode | string; +// leftIcon?: ReactNode; +// onClick?: (...args: any) => any; +// rightIcon?: ReactNode; +// }; + +// const ContextMenuContext = createContext({ +// closeContextMenu: () => {}, +// openContextMenu: (args: OpenContextMenuProps) => { +// return args; +// }, +// }); + +// const JELLYFIN_IGNORED_MENU_ITEMS: ContextMenuItemType[] = ['setRating', 'shareItem']; +// // const NAVIDROME_IGNORED_MENU_ITEMS: ContextMenuItemType[] = []; +// // const SUBSONIC_IGNORED_MENU_ITEMS: ContextMenuItemType[] = []; + +// const utils = isElectron() ? window.api.utils : null; + +// export interface ContextMenuProviderProps { +// children: ReactNode; +// } + +// export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => { +// const disabledItems = useSettingsStore((state) => state.general.disabledContextMenu); +// const { t } = useTranslation(); +// const [opened, setOpened] = useState(false); + +// const clickOutsideRef = useClickOutside(() => setOpened(false), ['mousedown', 'touchstart']); + +// const viewport = useViewportSize(); +// const server = useCurrentServer(); +// const serverType = server?.type; +// const [ref, menuRect] = useResizeObserver(); +// const [ctx, setCtx] = useSetState({ +// data: [], +// dataNodes: [], +// menuItems: [], +// resetGridCache: undefined, +// tableApi: undefined, +// type: LibraryItem.SONG, +// xPos: 0, +// yPos: 0, +// }); + +// const [rating, setRating] = useState(0); + +// useEffect(() => { +// if (opened && ctx.data.length > 0) { +// if (ctx.data.length === 1) { +// setRating(ctx.data[0].userRating ?? 0); +// } else { +// const firstRating = ctx.data[0].userRating ?? 0; +// const allSameRating = ctx.data.every( +// (item) => (item.userRating ?? 0) === firstRating, +// ); +// setRating(allSameRating ? firstRating : 0); +// } +// } else { +// setRating(0); +// } +// }, [ctx.data, opened]); + +// const handlePlayQueueAdd = usePlayQueueAdd(); +// const navigate = useNavigate(); + +// const openContextMenu = useCallback( +// (args: OpenContextMenuProps) => { +// const { +// context, +// data, +// dataNodes, +// menuItems, +// resetGridCache, +// tableApi, +// type, +// xPos, +// yPos, +// } = args; + +// const serverType = data[0]?.serverType || useAuthStore.getState().currentServer?.type; +// let validMenuItems = menuItems.filter((item) => !disabledItems[item.id]); + +// if (serverType === ServerType.JELLYFIN) { +// validMenuItems = menuItems.filter( +// (item) => !JELLYFIN_IGNORED_MENU_ITEMS.includes(item.id), +// ); +// } + +// // If the context menu dimension can't be automatically calculated, calculate it manually +// // This is a hacky way since resize observer may not automatically recalculate when not rendered +// const menuHeight = menuRect.height || (validMenuItems.length + 1) * 40; +// const menuWidth = menuRect.width || 220; + +// const shouldReverseY = yPos + menuHeight > viewport.height; +// const shouldReverseX = xPos + menuWidth > viewport.width; + +// const calculatedXPos = shouldReverseX ? xPos - menuWidth : xPos; +// const calculatedYPos = shouldReverseY ? yPos - menuHeight : yPos; + +// setCtx({ +// context, +// data, +// dataNodes, +// menuItems: validMenuItems, +// resetGridCache, +// tableApi, +// type, +// xPos: calculatedXPos, +// yPos: calculatedYPos, +// }); +// setOpened(true); +// }, +// [disabledItems, menuRect.height, menuRect.width, setCtx, viewport.height, viewport.width], +// ); + +// const closeContextMenu = useCallback(() => { +// setOpened(false); +// setCtx({ +// data: [], +// dataNodes: [], +// menuItems: [], +// tableApi: undefined, +// type: LibraryItem.SONG, +// xPos: 0, +// yPos: 0, +// }); +// }, [setCtx]); + +// useContextMenuEvents({ +// closeContextMenu, +// openContextMenu, +// }); + +// const handlePlay = useCallback( +// (playType: Play) => { +// switch (ctx.type) { +// case LibraryItem.ALBUM: +// handlePlayQueueAdd?.({ +// byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, +// playType, +// }); +// break; +// case LibraryItem.ALBUM_ARTIST: +// handlePlayQueueAdd?.({ +// byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, +// playType, +// }); +// break; +// case LibraryItem.ARTIST: +// handlePlayQueueAdd?.({ +// byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, +// playType, +// }); +// break; +// case LibraryItem.GENRE: +// handlePlayQueueAdd?.({ +// byItemType: { id: ctx.data.map((item) => item.id), type: ctx.type }, +// playType, +// }); +// break; +// case LibraryItem.PLAYLIST: +// for (const item of ctx.data) { +// handlePlayQueueAdd?.({ +// byItemType: { id: [item.id], type: ctx.type }, +// playType, +// }); +// } + +// break; +// case LibraryItem.SONG: +// handlePlayQueueAdd?.({ byData: ctx.data, playType }); +// break; +// } +// }, +// [ctx.data, ctx.type, handlePlayQueueAdd], +// ); + +// const deletePlaylistMutation = useDeletePlaylist({}); + +// const handleDeletePlaylist = useCallback(() => { +// for (const item of ctx.data) { +// deletePlaylistMutation?.mutate( +// { query: { id: item.id }, serverId: item.serverId }, +// { +// onError: (err) => { +// toast.error({ +// message: err.message, +// title: t('error.genericError', { postProcess: 'sentenceCase' }), +// }); +// }, +// onSuccess: () => { +// toast.success({ +// message: `Playlist has been deleted`, +// }); + +// ctx.tableApi?.refreshInfiniteCache(); +// ctx.resetGridCache?.(); +// }, +// }, +// ); +// } + +// closeAllModals(); +// }, [ctx, deletePlaylistMutation, t]); + +// const openDeletePlaylistModal = useCallback(() => { +// openModal({ +// children: ( +// +// +// {t('common.areYouSure', { postProcess: 'sentenceCase' })} +//
    +// {ctx.data.map((item) => ( +//
  • +// +// —{item.name} +// +//
  • +// ))} +//
+//
+//
+// ), +// title: t('page.contextMenu.deletePlaylist', { postProcess: 'titleCase' }), +// }); +// }, [ctx.data, handleDeletePlaylist, t]); + +// const createFavoriteMutation = useCreateFavorite({}); +// const deleteFavoriteMutation = useDeleteFavorite({}); +// const handleAddToFavorites = useCallback(() => { +// if (!ctx.dataNodes && !ctx.data) return; + +// if (ctx.dataNodes) { +// const nodesToFavorite = ctx.dataNodes.filter((item) => !item.data.userFavorite); + +// const nodesByServerId = nodesToFavorite.reduce( +// (acc, node) => { +// if (!acc[node.data.serverId]) { +// acc[node.data.serverId] = []; +// } +// acc[node.data.serverId].push(node); +// return acc; +// }, +// {} as Record[]>, +// ); + +// for (const serverId of Object.keys(nodesByServerId)) { +// const nodes = nodesByServerId[serverId]; +// const items = nodes.map((node) => node.data); + +// createFavoriteMutation.mutate( +// { +// query: { +// id: items.map((item) => item.id), +// type: ctx.type, +// }, +// serverId, +// }, +// { +// onError: (err) => { +// toast.error({ +// message: err.message, +// title: t('error.genericError', { postProcess: 'sentenceCase' }), +// }); +// }, +// onSuccess: () => { +// for (const node of nodes) { +// node.setData({ ...node.data, userFavorite: true }); +// } +// }, +// }, +// ); +// } +// } else { +// const itemsToFavorite = ctx.data.filter((item) => !item.userFavorite); +// const itemsByServerId = (itemsToFavorite as any[]).reduce( +// (acc, item) => { +// if (!acc[item.serverId]) { +// acc[item.serverId] = []; +// } +// acc[item.serverId].push(item); +// return acc; +// }, +// {} as Record, +// ); + +// for (const serverId of Object.keys(itemsByServerId)) { +// const items = itemsByServerId[serverId]; + +// createFavoriteMutation.mutate( +// { +// query: { +// id: items.map((item: AnyLibraryItem) => item.id), +// type: ctx.type, +// }, +// serverId, +// }, +// { +// onError: (err) => { +// toast.error({ +// message: err.message, +// title: t('error.genericError', { postProcess: 'sentenceCase' }), +// }); +// }, +// }, +// ); +// } +// } +// }, [createFavoriteMutation, ctx.data, ctx.dataNodes, ctx.type, t]); + +// const handleRemoveFromFavorites = useCallback(() => { +// if (!ctx.dataNodes && !ctx.data) return; + +// if (ctx.dataNodes) { +// const nodesToUnfavorite = ctx.dataNodes.filter((item) => item.data.userFavorite); +// const nodesByServerId = nodesToUnfavorite.reduce( +// (acc, node) => { +// if (!acc[node.data.serverId]) { +// acc[node.data.serverId] = []; +// } +// acc[node.data.serverId].push(node); +// return acc; +// }, +// {} as Record[]>, +// ); + +// for (const serverId of Object.keys(nodesByServerId)) { +// const idsToUnfavorite = nodesByServerId[serverId].map((node) => node.data.id); +// deleteFavoriteMutation.mutate( +// { +// query: { +// id: idsToUnfavorite, +// type: ctx.type, +// }, +// serverId, +// }, +// { +// onSuccess: () => { +// for (const node of nodesToUnfavorite) { +// node.setData({ ...node.data, userFavorite: false }); +// } +// }, +// }, +// ); +// } +// } else { +// const itemsToUnfavorite = ctx.data.filter((item) => item.userFavorite); +// const itemsByServerId = (itemsToUnfavorite as any[]).reduce( +// (acc, item) => { +// if (!acc[item.serverId]) { +// acc[item.serverId] = []; +// } +// acc[item.serverId].push(item); +// return acc; +// }, +// {} as Record, +// ); + +// for (const serverId of Object.keys(itemsByServerId)) { +// const idsToUnfavorite = itemsByServerId[serverId].map( +// (item: AnyLibraryItem) => item.id, +// ); +// deleteFavoriteMutation.mutate({ +// query: { +// id: idsToUnfavorite, +// type: ctx.type, +// }, +// serverId, +// }); +// } +// } +// }, [ctx.data, ctx.dataNodes, ctx.type, deleteFavoriteMutation]); + +// const handleAddToPlaylist = useCallback(() => { +// if (!ctx.dataNodes && !ctx.data) return; + +// const albumId: string[] = []; +// const artistId: string[] = []; +// const songId: string[] = []; +// const genreId: string[] = []; + +// if (ctx.dataNodes) { +// for (const node of ctx.dataNodes) { +// switch (node.data.itemType) { +// case LibraryItem.ALBUM: +// albumId.push(node.data.id); +// break; +// case LibraryItem.ARTIST: +// artistId.push(node.data.id); +// break; +// case LibraryItem.GENRE: +// genreId.push(node.data.id); +// break; +// case LibraryItem.SONG: +// songId.push(node.data.id); +// break; +// } +// } +// } else { +// for (const item of ctx.data) { +// switch (item.itemType) { +// case LibraryItem.ALBUM: +// albumId.push(item.id); +// break; +// case LibraryItem.ALBUM_ARTIST: +// artistId.push(item.id); +// break; +// case LibraryItem.ARTIST: +// artistId.push(item.id); +// break; +// case LibraryItem.GENRE: +// genreId.push(item.id); +// break; +// case LibraryItem.SONG: +// songId.push(item.id); +// break; +// } +// } +// } + +// openContextModal({ +// innerProps: { +// albumId: albumId.length > 0 ? albumId : undefined, +// artistId: artistId.length > 0 ? artistId : undefined, +// genreId: genreId.length > 0 ? genreId : undefined, +// songId: songId.length > 0 ? songId : undefined, +// }, +// modal: 'addToPlaylist', +// size: 'lg', +// title: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }), +// }); +// }, [ctx.data, ctx.dataNodes, t]); + +// const removeFromPlaylistMutation = useRemoveFromPlaylist(); + +// const handleRemoveFromPlaylist = useCallback(() => { +// let songId: string[] | undefined; + +// switch (serverType) { +// case ServerType.JELLYFIN: +// case ServerType.NAVIDROME: +// songId = ctx.dataNodes?.map((node) => node.data.playlistItemId); +// break; +// case ServerType.SUBSONIC: +// songId = ctx.dataNodes?.map((node) => node.rowIndex!.toString()); +// break; +// } + +// const confirm = () => { +// removeFromPlaylistMutation.mutate( +// { +// query: { +// id: ctx.context.playlistId, +// songId: songId || [], +// }, +// serverId: ctx.data?.[0]?.serverId, +// }, +// { +// onError: (err) => { +// toast.error({ +// message: err.message, +// title: t('error.genericError', { postProcess: 'sentenceCase' }), +// }); +// }, +// onSuccess: () => { +// closeAllModals(); +// }, +// }, +// ); +// }; + +// openModal({ +// children: ( +// +// {t('common.areYouSure', { postProcess: 'sentenceCase' })} +// +// ), +// title: t('page.contextMenu.removeFromPlaylist', { postProcess: 'sentenceCase' }), +// }); +// }, [ +// ctx.context?.playlistId, +// ctx.data, +// ctx.dataNodes, +// removeFromPlaylistMutation, +// serverType, +// t, +// ]); + +// const updateRatingMutation = useSetRating({}); + +// const handleUpdateRating = useCallback( +// (newRating: number) => { +// if (!ctx.dataNodes && !ctx.data) return; + +// let uniqueServerIds: string[] = []; +// let items: AnyLibraryItems = []; + +// if (ctx.dataNodes) { +// uniqueServerIds = ctx.dataNodes.reduce((acc, node) => { +// if (!acc.includes(node.data.serverId)) { +// acc.push(node.data.serverId); +// } +// return acc; +// }, [] as string[]); +// } else { +// uniqueServerIds = ctx.data.reduce((acc, item) => { +// if (!acc.includes(item.serverId)) { +// acc.push(item.serverId); +// } +// return acc; +// }, [] as string[]); +// } + +// const ratingToSet = newRating === rating ? 0 : newRating; + +// for (const serverId of uniqueServerIds) { +// if (ctx.dataNodes) { +// items = ctx.dataNodes +// .filter((node) => node.data.serverId === serverId) +// .map((node) => node.data); +// } else { +// items = ctx.data.filter((item) => item.serverId === serverId); +// } + +// updateRatingMutation.mutate( +// { +// query: { +// item: items, +// rating: ratingToSet, +// }, +// serverId, +// }, +// { +// onSuccess: () => { +// if (ctx.dataNodes) { +// for (const node of ctx.dataNodes) { +// node.setData({ ...node.data, userRating: ratingToSet }); +// } +// } +// }, +// }, +// ); +// } +// }, +// [ctx.data, ctx.dataNodes, updateRatingMutation, rating], +// ); + +// const playbackType = usePlaybackType(); +// // const { moveToBottomOfQueue, moveToNextOfQueue, moveToTopOfQueue, removeFromQueue } = +// // useQueueControls(); + +// const handleMoveToNext = useCallback(() => { +// const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); +// if (!uniqueIds?.length) return; + +// const playerData = moveToNextOfQueue(uniqueIds); + +// if (playbackType === PlaybackType.LOCAL) { +// setQueueNext(playerData); +// } +// }, [ctx.dataNodes, moveToNextOfQueue, playbackType]); + +// const handleMoveToBottom = useCallback(() => { +// const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); +// if (!uniqueIds?.length) return; + +// const playerData = moveToBottomOfQueue(uniqueIds); + +// if (playbackType === PlaybackType.LOCAL) { +// setQueueNext(playerData); +// } +// }, [ctx.dataNodes, moveToBottomOfQueue, playbackType]); + +// const handleMoveToTop = useCallback(() => { +// const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); +// if (!uniqueIds?.length) return; + +// const playerData = moveToTopOfQueue(uniqueIds); + +// if (playbackType === PlaybackType.LOCAL) { +// setQueueNext(playerData); +// } +// }, [ctx.dataNodes, moveToTopOfQueue, playbackType]); + +// const handleShareItem = useCallback(() => { +// if (!ctx.dataNodes && !ctx.data) return; + +// const uniqueIds = ctx.data.map((node) => node.id); + +// openContextModal({ +// innerProps: { +// itemIds: uniqueIds, +// resourceType: ctx.data[0].itemType, +// }, +// modal: 'shareItem', +// size: 'md', +// title: t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' }), +// }); +// }, [ctx.data, ctx.dataNodes, t]); + +// const handleRemoveSelected = useCallback(() => { +// const uniqueIds = ctx.dataNodes?.map((row) => row.data.uniqueId); +// if (!uniqueIds?.length) return; + +// const currentSong = usePlayerStore.getState().current.song; +// const playerData = removeFromQueue(uniqueIds); +// const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong?.uniqueId); + +// if (playbackType === PlaybackType.LOCAL) { +// if (isCurrentSongRemoved) { +// setQueue(playerData); +// } else { +// setQueueNext(playerData); +// } +// } + +// ctx.tableApi?.redrawRows(); + +// if (isCurrentSongRemoved) { +// updateSong(playerData.current.song); +// } +// }, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]); + +// const handleDeselectAll = useCallback(() => { +// ctx.tableApi?.deselectAll(); +// }, [ctx.tableApi]); + +// const handleOpenItemDetails = useCallback(() => { +// const item = ctx.data[0]; + +// openModal({ +// children: , +// size: 'xl', +// title: t('page.contextMenu.showDetails', { postProcess: 'titleCase' }), +// }); +// }, [ctx.data, t]); + +// const handleSimilar = useCallback(async () => { +// const item = ctx.data[0]; +// const songs = await controller.getSimilarSongs({ +// apiClientProps: { +// server: getServerById(item.serverId), +// signal: undefined, +// }, +// query: { albumArtistIds: item.albumArtistIds, songId: item.id }, +// }); +// if (songs) { +// handlePlayQueueAdd?.({ byData: [ctx.data[0], ...songs], playType: Play.NOW }); +// } +// }, [ctx, handlePlayQueueAdd]); + +// const handleDownload = useCallback(() => { +// const item = ctx.data[0]; +// const url = api.controller.getDownloadUrl({ +// apiClientProps: { server }, +// query: { id: item.id }, +// }); + +// if (utils) { +// utils.download(url!); +// } else { +// window.open(url, '_blank'); +// } +// }, [ctx.data, server]); + +// const handleGoToAlbum = useCallback(() => { +// const item = ctx.data[0]; +// if (item.albumId) { +// navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: item.albumId })); +// } +// }, [ctx.data, navigate]); + +// const handleGoToAlbumArtist = useCallback(() => { +// const item = ctx.data[0]; +// if (item.albumArtists && item.albumArtists.length > 0) { +// navigate( +// generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { +// albumArtistId: item.albumArtists[0].id, +// }), +// ); +// } +// }, [ctx.data, navigate]); + +// const contextMenuItems: Record = useMemo(() => { +// return { +// addToFavorites: { +// id: 'addToFavorites', +// label: t('page.contextMenu.addToFavorites', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleAddToFavorites, +// }, +// addToPlaylist: { +// id: 'addToPlaylist', +// label: t('page.contextMenu.addToPlaylist', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleAddToPlaylist, +// }, +// createPlaylist: { +// id: 'createPlaylist', +// label: t('page.contextMenu.createPlaylist', { postProcess: 'sentenceCase' }), +// onClick: () => {}, +// }, +// deletePlaylist: { +// id: 'deletePlaylist', +// label: t('page.contextMenu.deletePlaylist', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: openDeletePlaylistModal, +// }, +// deselectAll: { +// id: 'deselectAll', +// label: t('page.contextMenu.deselectAll', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleDeselectAll, +// }, +// download: { +// disabled: ctx.data?.length !== 1, +// id: 'download', +// label: t('page.contextMenu.download', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleDownload, +// }, +// goToAlbum: { +// disabled: ctx.data?.length !== 1 || !ctx.data[0]?.albumId, +// id: 'goToAlbum', +// label: t('page.contextMenu.goToAlbum', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleGoToAlbum, +// }, +// goToAlbumArtist: { +// disabled: +// ctx.data?.length !== 1 || +// !ctx.data[0]?.albumArtists || +// ctx.data[0]?.albumArtists?.length === 0, +// id: 'goToAlbumArtist', +// label: t('page.contextMenu.goToAlbumArtist', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleGoToAlbumArtist, +// }, +// moveToBottomOfQueue: { +// id: 'moveToBottomOfQueue', +// label: t('page.contextMenu.moveToBottom', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleMoveToBottom, +// }, +// moveToNextOfQueue: { +// id: 'moveToNext', +// label: t('page.contextMenu.moveToNext', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleMoveToNext, +// }, +// moveToTopOfQueue: { +// id: 'moveToTopOfQueue', +// label: t('page.contextMenu.moveToTop', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleMoveToTop, +// }, +// play: { +// id: 'play', +// label: t('page.contextMenu.play', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: () => handlePlay(Play.NOW), +// }, +// playLast: { +// id: 'playLast', +// label: t('page.contextMenu.addLast', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: () => handlePlay(Play.LAST), +// }, +// playNext: { +// id: 'playNext', +// label: t('page.contextMenu.addNext', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: () => handlePlay(Play.NEXT), +// }, +// playShuffled: { +// id: 'playShuffled', +// label: t('page.contextMenu.playShuffled', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: () => handlePlay(Play.SHUFFLE), +// }, +// playSimilarSongs: { +// id: 'playSimilarSongs', +// label: t('page.contextMenu.playSimilarSongs', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleSimilar, +// }, +// removeFromFavorites: { +// id: 'removeFromFavorites', +// label: t('page.contextMenu.removeFromFavorites', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleRemoveFromFavorites, +// }, +// removeFromPlaylist: { +// id: 'removeFromPlaylist', +// label: t('page.contextMenu.removeFromPlaylist', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleRemoveFromPlaylist, +// }, +// removeFromQueue: { +// id: 'removeSongs', +// label: t('page.contextMenu.removeFromQueue', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleRemoveSelected, +// }, +// setRating: { +// id: 'setRating', +// label: t('action.setRating', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: () => {}, +// rightIcon: ( +// +// { +// handleUpdateRating(e); +// setRating(e); +// }} +// size="xs" +// value={rating} +// /> +// +// ), +// }, +// shareItem: { +// disabled: !hasFeature(server, ServerFeature.SHARING_ALBUM_SONG), +// id: 'shareItem', +// label: t('page.contextMenu.shareItem', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleShareItem, +// }, +// showDetails: { +// disabled: ctx.data?.length !== 1 || !ctx.data[0].itemType, +// id: 'showDetails', +// label: t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }), +// leftIcon: , +// onClick: handleOpenItemDetails, +// }, +// }; +// }, [ +// t, +// handleAddToFavorites, +// handleAddToPlaylist, +// openDeletePlaylistModal, +// handleDeselectAll, +// ctx.data, +// handleDownload, +// handleMoveToNext, +// handleMoveToBottom, +// handleMoveToTop, +// handleSimilar, +// handleRemoveFromFavorites, +// handleRemoveFromPlaylist, +// handleRemoveSelected, +// server, +// handleShareItem, +// handleGoToAlbum, +// handleGoToAlbumArtist, +// handleOpenItemDetails, +// handlePlay, +// handleUpdateRating, +// rating, +// ]); + +// const mergedRef = useMergedRef(ref, clickOutsideRef); + +// const providerValue = useMemo( +// () => ({ +// closeContextMenu, +// openContextMenu, +// }), +// [closeContextMenu, openContextMenu], +// ); + +// return ( +// +// +// +// {opened && ( +// +// +// +// {ctx.menuItems?.map((item) => { +// return ( +// !contextMenuItems[item.id].disabled && ( +// +// {item.children ? ( +// +// +// +// { +// contextMenuItems[item.id] +// .label +// } +// +// +// +// +// {contextMenuItems[ +// item.id +// ].children?.map((child) => ( +// +// {child.label} +// +// ))} +// +// +// +// ) : ( +// +// {contextMenuItems[item.id].label} +// +// )} + +// {item.divider && ( +// +// )} +// +// ) +// ); +// })} +// +// +// {t('page.contextMenu.numberSelected', { +// count: ctx.data?.length || 0, +// postProcess: 'lowerCase', +// })} +// +// +// +// )} +// +// {children} +// +// +// ); +// }; diff --git a/src/renderer/features/lyrics/lyrics-actions.tsx b/src/renderer/features/lyrics/lyrics-actions.tsx index 3fa3fbc08..077c11732 100644 --- a/src/renderer/features/lyrics/lyrics-actions.tsx +++ b/src/renderer/features/lyrics/lyrics-actions.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { openLyricSearchModal } from '/@/renderer/features/lyrics/components/lyrics-search-form'; import { - useCurrentSong, + usePlayerSong, useLyricsSettings, useSettingsStore, useSettingsStoreActions, @@ -38,7 +38,7 @@ export const LyricsActions = ({ setIndex, }: LyricsActionsProps) => { const { t } = useTranslation(); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const { setSettings } = useSettingsStoreActions(); const { delayMs, sources } = useLyricsSettings(); diff --git a/src/renderer/features/lyrics/lyrics.tsx b/src/renderer/features/lyrics/lyrics.tsx index 730f8a37d..eec617230 100644 --- a/src/renderer/features/lyrics/lyrics.tsx +++ b/src/renderer/features/lyrics/lyrics.tsx @@ -20,7 +20,7 @@ import { UnsynchronizedLyricsProps, } from '/@/renderer/features/lyrics/unsynchronized-lyrics'; import { queryClient } from '/@/renderer/lib/react-query'; -import { useCurrentSong, useLyricsSettings, usePlayerStore } from '/@/renderer/store'; +import { usePlayerSong, useLyricsSettings, usePlayerStore } from '/@/renderer/store'; import { Center } from '/@/shared/components/center/center'; import { Group } from '/@/shared/components/group/group'; import { Icon } from '/@/shared/components/icon/icon'; @@ -29,7 +29,7 @@ import { Text } from '/@/shared/components/text/text'; import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/shared/types/domain-types'; export const Lyrics = () => { - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const { enableAutoTranslation, translationApiKey, diff --git a/src/renderer/features/lyrics/synchronized-lyrics.tsx b/src/renderer/features/lyrics/synchronized-lyrics.tsx index 6f952bc7e..b86d8204a 100644 --- a/src/renderer/features/lyrics/synchronized-lyrics.tsx +++ b/src/renderer/features/lyrics/synchronized-lyrics.tsx @@ -8,17 +8,16 @@ import { LyricLine } from '/@/renderer/features/lyrics/lyric-line'; import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; import { PlayersRef } from '/@/renderer/features/player/ref/players-ref'; import { - useCurrentPlayer, - useCurrentStatus, - useCurrentTime, useLyricsSettings, usePlaybackType, + usePlayerActions, usePlayerData, - useSeeked, - useSetCurrentTime, + usePlayerNum, + usePlayerStatus, + usePlayerTimestamp, } from '/@/renderer/store'; import { FullLyricsMetadata, SynchronizedLyricsArray } from '/@/shared/types/domain-types'; -import { PlaybackType, PlayerStatus } from '/@/shared/types/types'; +import { PlayerStatus, PlayerType } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; const utils = isElectron() ? window.api.utils : null; @@ -38,33 +37,33 @@ export const SynchronizedLyrics = ({ translatedLyrics, }: SynchronizedLyricsProps) => { const playersRef = PlayersRef; - const status = useCurrentStatus(); + const status = usePlayerStatus(); const playbackType = usePlaybackType(); const playerData = usePlayerData(); - const now = useCurrentTime(); + const now = usePlayerTimestamp(); const settings = useLyricsSettings(); - const currentPlayer = useCurrentPlayer(); + const currentPlayer = usePlayerNum(); const currentPlayerRef = currentPlayer === 1 ? playersRef.current?.player1 : playersRef.current?.player2; - const setCurrentTime = useSetCurrentTime(); + const { handleScrobbleFromSeek } = useScrobble(); const handleSeek = useCallback( (time: number) => { - if (playbackType === PlaybackType.LOCAL && mpvPlayer) { + if (playbackType === PlayerType.LOCAL && mpvPlayer) { mpvPlayer.seekTo(time); - setCurrentTime(time, true); + // setCurrentTime(time, true); } else { - setCurrentTime(time, true); + // setCurrentTime(time, true); handleScrobbleFromSeek(time); mpris?.updateSeek(time); currentPlayerRef?.seekTo(time); } }, - [currentPlayerRef, handleScrobbleFromSeek, playbackType, setCurrentTime], + [currentPlayerRef, handleScrobbleFromSeek, playbackType], ); - const seeked = useSeeked(); + // const seeked = useSeeked(); // A reference to the timeout handler const lyricTimer = useRef>(null); @@ -97,7 +96,7 @@ export const SynchronizedLyrics = ({ }; const getCurrentTime = useCallback(async () => { - if (isElectron() && playbackType !== PlaybackType.WEB) { + if (isElectron() && playbackType !== PlayerType.WEB) { if (mpvPlayer) { return mpvPlayer.getCurrentTime(); } @@ -109,7 +108,7 @@ export const SynchronizedLyrics = ({ } const player = - playerData.current.player === 1 + playerData.player.playerNum === 1 ? playersRef.current.player1 : playersRef.current.player2; const underlying = player?.getInternalPlayer(); @@ -282,16 +281,16 @@ export const SynchronizedLyrics = ({ // we may be playing the same track (repeat one). In this case, we also // need to restart playback const restarted = status === PlayerStatus.PLAYING && now === 0; - if (!seeked && !restarted) { - return; - } + // if (!seeked && !restarted) { + // return; + // } if (lyricTimer.current) { clearTimeout(lyricTimer.current); } setCurrentLyric(now * 1000 - delayMsRef.current); - }, [now, seeked, setCurrentLyric, status]); + }, [now, setCurrentLyric, status]); useEffect(() => { // Guaranteed cleanup; stop the timer, and just in case also increment diff --git a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx index f06052607..f7bfdbaf0 100644 --- a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx +++ b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx @@ -7,15 +7,12 @@ import { useTranslation } from 'react-i18next'; import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; import { updateSong } from '/@/renderer/features/player/update-remote-song'; import { SearchInput } from '/@/renderer/features/shared/components/search-input'; -import { usePlayerControls, useQueueControls } from '/@/renderer/store'; -import { usePlayerStore, useSetCurrentTime } from '/@/renderer/store/player.store'; import { usePlaybackType } from '/@/renderer/store/settings.store'; -import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Group } from '/@/shared/components/group/group'; import { Popover } from '/@/shared/components/popover/popover'; import { Song } from '/@/shared/types/domain-types'; -import { PlaybackType, TableType } from '/@/shared/types/types'; +import { TableType } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -33,30 +30,30 @@ export const PlayQueueListControls = ({ type, }: PlayQueueListOptionsProps) => { const { t } = useTranslation(); - const { - clearQueue, - moveToBottomOfQueue, - moveToNextOfQueue, - moveToTopOfQueue, - removeFromQueue, - shuffleQueue, - } = useQueueControls(); + // const { + // clearQueue, + // moveToBottomOfQueue, + // moveToNextOfQueue, + // moveToTopOfQueue, + // removeFromQueue, + // shuffleQueue, + // } = useQueueControls(); - const { pause } = usePlayerControls(); + // const { pause } = usePlayerControls(); const playbackType = usePlaybackType(); - const setCurrentTime = useSetCurrentTime(); + // const setCurrentTime = useSetCurrentTime(); const handleMoveToNext = () => { const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); const uniqueIds = selectedRows?.map((row) => row.uniqueId); if (!uniqueIds?.length) return; - const playerData = moveToNextOfQueue(uniqueIds); + // const playerData = moveToNextOfQueue(uniqueIds); - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } + // if (playbackType === PlaybackType.LOCAL) { + // setQueueNext(playerData); + // } }; const handleMoveToBottom = () => { @@ -64,11 +61,11 @@ export const PlayQueueListControls = ({ const uniqueIds = selectedRows?.map((row) => row.uniqueId); if (!uniqueIds?.length) return; - const playerData = moveToBottomOfQueue(uniqueIds); + // const playerData = moveToBottomOfQueue(uniqueIds); - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } + // if (playbackType === PlaybackType.LOCAL) { + // setQueueNext(playerData); + // } }; const handleMoveToTop = () => { @@ -76,11 +73,11 @@ export const PlayQueueListControls = ({ const uniqueIds = selectedRows?.map((row) => row.uniqueId); if (!uniqueIds?.length) return; - const playerData = moveToTopOfQueue(uniqueIds); + // const playerData = moveToTopOfQueue(uniqueIds); - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } + // if (playbackType === PlaybackType.LOCAL) { + // setQueueNext(playerData); + // } }; const handleRemoveSelected = () => { @@ -88,43 +85,42 @@ export const PlayQueueListControls = ({ const uniqueIds = selectedRows?.map((row) => row.uniqueId); if (!uniqueIds?.length) return; - const currentSong = usePlayerStore.getState().current.song; - const playerData = removeFromQueue(uniqueIds); - const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId); + // const currentSong = usePlayerStore.getState().current.song; + // const playerData = removeFromQueue(uniqueIds); + // const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId); - if (playbackType === PlaybackType.LOCAL) { - if (isCurrentSongRemoved) { - setQueue(playerData); - } else { - setQueueNext(playerData); - } - } + // if (playbackType === PlaybackType.LOCAL) { + // if (isCurrentSongRemoved) { + // setQueue(playerData); + // } else { + // setQueueNext(playerData); + // } + // } - if (isCurrentSongRemoved) { - updateSong(playerData.current.song); - } + // if (isCurrentSongRemoved) { + // updateSong(playerData.current.song); + // } }; const handleClearQueue = () => { - const playerData = clearQueue(); + // const playerData = clearQueue(); - if (playbackType === PlaybackType.LOCAL) { - setQueue(playerData); - mpvPlayer!.pause(); - } + // if (playbackType === PlaybackType.LOCAL) { + // setQueue(playerData); + // mpvPlayer!.pause(); + // } updateSong(undefined); - setCurrentTime(0); - pause(); + // setCurrentTime(0); + // pause(); }; const handleShuffleQueue = () => { - const playerData = shuffleQueue(); - - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } + // const playerData = shuffleQueue(); + // if (playbackType === PlaybackType.LOCAL) { + // setQueueNext(playerData); + // } }; const handleSearchTerm = useCallback( diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 31387cf86..8f63455b4 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -24,13 +24,10 @@ import { updateSong } from '/@/renderer/features/player/update-remote-song'; import { useAppFocus } from '/@/renderer/hooks'; import { useAppStoreActions, - useCurrentSong, - useCurrentStatus, - useDefaultQueue, - usePlayerControls, - usePreviousSong, - useQueueControls, - useVolume, + usePlayerQueue, + usePlayerSong, + usePlayerStatus, + usePlayerVolume, } from '/@/renderer/store'; import { PersistedTableColumn, @@ -42,7 +39,7 @@ import { import { searchSongs } from '/@/renderer/utils/search-songs'; import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; -import { PlaybackType, TableType } from '/@/shared/types/types'; +import { PlayerType, TableType } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -54,18 +51,18 @@ type QueueProps = { export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref) => { const tableRef = useRef(null); const mergedRef = useMergedRef(ref, tableRef); - const queue = useDefaultQueue(); - const { reorderQueue, setCurrentTrack } = useQueueControls(); - const currentSong = useCurrentSong(); - const previousSong = usePreviousSong(); - const status = useCurrentStatus(); + const queue = usePlayerQueue(); + // const { reorderQueue, setCurrentTrack } = useQueueControls(); + const currentSong = usePlayerSong(); + // const previousSong = usePreviousSong(); + const status = usePlayerStatus(); const { setSettings } = useSettingsStoreActions(); const { setAppStore } = useAppStoreActions(); const tableConfig = useTableSettings(type); const [gridApi, setGridApi] = useState(); const playbackType = usePlaybackType(); - const { play } = usePlayerControls(); - const volume = useVolume(); + // const { play } = usePlayerControls(); + const volume = usePlayerVolume(); const isFocused = useAppFocus(); const isFocusedRef = useRef(isFocused); @@ -94,26 +91,26 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< [tableConfig.columns], ); - const handleDoubleClick = (e: CellDoubleClickedEvent) => { - const playerData = setCurrentTrack(e.data.uniqueId); - updateSong(playerData.current.song); + // const handleDoubleClick = (e: CellDoubleClickedEvent) => { + // const playerData = setCurrentTrack(e.data.uniqueId); + // updateSong(playerData.current.song); - if (playbackType === PlaybackType.LOCAL) { - mpvPlayer!.volume(volume); - setQueue(playerData, false); - } else { - const player = - playerData.current.player === 1 - ? PlayersRef.current?.player1 - : PlayersRef.current?.player2; - const underlying = player?.getInternalPlayer(); - if (underlying) { - underlying.currentTime = 0; - } - } + // if (playbackType === PlaybackType.LOCAL) { + // mpvPlayer!.volume(volume); + // setQueue(playerData, false); + // } else { + // const player = + // playerData.current.player === 1 + // ? PlayersRef.current?.player1 + // : PlayersRef.current?.player2; + // const underlying = player?.getInternalPlayer(); + // if (underlying) { + // underlying.currentTime = 0; + // } + // } - play(); - }; + // play(); + // }; const handleDragStart = () => { if (type === 'sideDrawerQueue') { @@ -128,11 +125,11 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< .map((node) => node.data?.uniqueId) .filter((e) => e !== undefined); - const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId); + // const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId); - if (playbackType === PlaybackType.LOCAL) { - setQueueNext(playerData); - } + // if (playbackType === PlaybackType.LOCAL) { + // setQueueNext(playerData); + // } if (type === 'sideDrawerQueue') { setAppStore({ isReorderingQueue: false }); @@ -204,6 +201,14 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< }; }, [currentSong?.uniqueId]); + const previousSongRef = useRef(undefined); + + useEffect(() => { + if (currentSong) { + previousSongRef.current = currentSong; + } + }, [currentSong]); + // Redraw the current song row when the previous song changes useEffect(() => { if (tableRef?.current) { @@ -215,8 +220,8 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< const currentNode = currentSong?.uniqueId ? api.getRowNode(currentSong.uniqueId) : undefined; - const previousNode = previousSong?.uniqueId - ? api.getRowNode(previousSong?.uniqueId) + const previousNode = previousSongRef.current?.uniqueId + ? api.getRowNode(previousSongRef.current?.uniqueId) : undefined; const rowNodes = [currentNode, previousNode].filter( @@ -231,7 +236,7 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< } } } - }, [currentSong, previousSong, tableConfig.followCurrentSong, status]); + }, [currentSong, previousSongRef, tableConfig.followCurrentSong, status]); // As a separate rule, update the current row when focus changes. This is // to prevent queue scrolling when the application loses and then gains focus. @@ -266,7 +271,7 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< columnDefs={columnDefs} context={{ currentSong, - handleDoubleClick, + // handleDoubleClick, isFocused, isQueue: true, itemType: LibraryItem.SONG, @@ -276,7 +281,7 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< deselectOnClickOutside={type === 'fullScreen'} getRowId={(data) => data.data.uniqueId} onCellContextMenu={onCellContextMenu} - onCellDoubleClicked={handleDoubleClick} + // onCellDoubleClicked={handleDoubleClick} onColumnMoved={handleColumnChange} onColumnResized={debouncedColumnChange} onDragStarted={handleDragStart} diff --git a/src/renderer/features/player/components/center-controls.tsx b/src/renderer/features/player/components/center-controls.tsx index ca84ab948..9cdd889ca 100644 --- a/src/renderer/features/player/components/center-controls.tsx +++ b/src/renderer/features/player/components/center-controls.tsx @@ -1,4 +1,3 @@ -import { useHotkeys } from '@mantine/hooks'; import { useQueryClient } from '@tanstack/react-query'; import formatDuration from 'format-duration'; import isElectron from 'is-electron'; @@ -10,27 +9,24 @@ import styles from './center-controls.module.css'; import { PlayButton, PlayerButton } from '/@/renderer/features/player/components/player-button'; import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider'; import { openShuffleAllModal } from '/@/renderer/features/player/components/shuffle-all-modal'; -import { useCenterControls } from '/@/renderer/features/player/hooks/use-center-controls'; -import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session'; import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; import { useAppStore, useAppStoreActions, - useCurrentPlayer, - useCurrentSong, - useCurrentStatus, - useCurrentTime, useHotkeySettings, usePlaybackType, - useRepeatStatus, - useSetCurrentTime, + usePlayerNum, + usePlayerRepeat, + usePlayerShuffle, + usePlayerSong, + usePlayerStatus, + usePlayerTimestamp, useSettingsStore, - useShuffleStatus, } from '/@/renderer/store'; import { Icon } from '/@/shared/components/icon/icon'; import { Text } from '/@/shared/components/text/text'; import { PlaybackSelectors } from '/@/shared/constants/playback-selectors'; -import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; +import { PlayerRepeat, PlayerShuffle, PlayerStatus, PlayerType } from '/@/shared/types/types'; interface CenterControlsProps { playersRef: any; @@ -40,38 +36,38 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { const { t } = useTranslation(); const queryClient = useQueryClient(); const [isSeeking, setIsSeeking] = useState(false); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const skip = useSettingsStore((state) => state.general.skipButtons); const buttonSize = useSettingsStore((state) => state.general.buttonSize); const playbackType = usePlaybackType(); const player1 = playersRef?.current?.player1; const player2 = playersRef?.current?.player2; - const status = useCurrentStatus(); - const player = useCurrentPlayer(); - const setCurrentTime = useSetCurrentTime(); - const repeat = useRepeatStatus(); - const shuffle = useShuffleStatus(); + const status = usePlayerStatus(); + const player = usePlayerNum(); + // const setCurrentTime = useSetCurrentTime(); + const repeat = usePlayerRepeat(); + const shuffle = usePlayerShuffle(); const { bindings } = useHotkeySettings(); const { showTimeRemaining } = useAppStore(); const { setShowTimeRemaining } = useAppStoreActions(); - const { - handleNextTrack, - handlePause, - handlePlay, - handlePlayPause, - handlePrevTrack, - handleSeekSlider, - handleSkipBackward, - handleSkipForward, - handleStop, - handleToggleRepeat, - handleToggleShuffle, - } = useCenterControls({ playersRef }); + // const { + // handleNextTrack, + // handlePause, + // handlePlay, + // handlePlayPause, + // handlePrevTrack, + // handleSeekSlider, + // handleSkipBackward, + // handleSkipForward, + // handleStop, + // handleToggleRepeat, + // handleToggleShuffle, + // } = useCenterControls({ playersRef }); const handlePlayQueueAdd = usePlayQueueAdd(); const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0; - const currentTime = useCurrentTime(); + const currentTime = usePlayerTimestamp(); const currentPlayerRef = player === 1 ? player1 : player2; const formattedDuration = formatDuration(songDuration * 1000 || 0); const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0); @@ -81,50 +77,50 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { let interval: ReturnType; if (status === PlayerStatus.PLAYING && !isSeeking) { - if (!isElectron() || playbackType === PlaybackType.WEB) { + if (!isElectron() || playbackType === PlayerType.WEB) { // Update twice a second for slightly better performance interval = setInterval(() => { if (currentPlayerRef) { - setCurrentTime(currentPlayerRef.getCurrentTime()); + // setCurrentTime(currentPlayerRef.getCurrentTime()); } }, 500); } } return () => clearInterval(interval); - }, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]); + }, [currentPlayerRef, isSeeking, playbackType, status]); const [seekValue, setSeekValue] = useState(0); - useHotkeys([ - [bindings.playPause.isGlobal ? '' : bindings.playPause.hotkey, handlePlayPause], - [bindings.play.isGlobal ? '' : bindings.play.hotkey, handlePlay], - [bindings.pause.isGlobal ? '' : bindings.pause.hotkey, handlePause], - [bindings.stop.isGlobal ? '' : bindings.stop.hotkey, handleStop], - [bindings.next.isGlobal ? '' : bindings.next.hotkey, handleNextTrack], - [bindings.previous.isGlobal ? '' : bindings.previous.hotkey, handlePrevTrack], - [bindings.toggleRepeat.isGlobal ? '' : bindings.toggleRepeat.hotkey, handleToggleRepeat], - [bindings.toggleShuffle.isGlobal ? '' : bindings.toggleShuffle.hotkey, handleToggleShuffle], - [ - bindings.skipBackward.isGlobal ? '' : bindings.skipBackward.hotkey, - () => handleSkipBackward(skip?.skipBackwardSeconds || 5), - ], - [ - bindings.skipForward.isGlobal ? '' : bindings.skipForward.hotkey, - () => handleSkipForward(skip?.skipForwardSeconds || 5), - ], - ]); + // useHotkeys([ + // [bindings.playPause.isGlobal ? '' : bindings.playPause.hotkey, handlePlayPause], + // [bindings.play.isGlobal ? '' : bindings.play.hotkey, handlePlay], + // [bindings.pause.isGlobal ? '' : bindings.pause.hotkey, handlePause], + // [bindings.stop.isGlobal ? '' : bindings.stop.hotkey, handleStop], + // [bindings.next.isGlobal ? '' : bindings.next.hotkey, handleNextTrack], + // [bindings.previous.isGlobal ? '' : bindings.previous.hotkey, handlePrevTrack], + // [bindings.toggleRepeat.isGlobal ? '' : bindings.toggleRepeat.hotkey, handleToggleRepeat], + // [bindings.toggleShuffle.isGlobal ? '' : bindings.toggleShuffle.hotkey, handleToggleShuffle], + // [ + // bindings.skipBackward.isGlobal ? '' : bindings.skipBackward.hotkey, + // () => handleSkipBackward(skip?.skipBackwardSeconds || 5), + // ], + // [ + // bindings.skipForward.isGlobal ? '' : bindings.skipForward.hotkey, + // () => handleSkipForward(skip?.skipForwardSeconds || 5), + // ], + // ]); - useMediaSession({ - handleNextTrack, - handlePause, - handlePlay, - handlePrevTrack, - handleSeekSlider, - handleSkipBackward, - handleSkipForward, - handleStop, - }); + // useMediaSession({ + // handleNextTrack, + // handlePause, + // handlePlay, + // handlePrevTrack, + // handleSeekSlider, + // handleSkipBackward, + // handleSkipForward, + // handleStop, + // }); return ( <> @@ -132,7 +128,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
} - onClick={handleStop} + // onClick={handleStop} tooltip={{ label: t('player.stop', { postProcess: 'sentenceCase' }), openDelay: 0, @@ -148,7 +144,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { /> } isActive={shuffle !== PlayerShuffle.NONE} - onClick={handleToggleShuffle} + // onClick={handleToggleShuffle} tooltip={{ label: shuffle === PlayerShuffle.NONE @@ -163,7 +159,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { /> } - onClick={handlePrevTrack} + // onClick={handlePrevTrack} tooltip={{ label: t('player.previous', { postProcess: 'sentenceCase' }), openDelay: 0, @@ -175,7 +171,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { icon={ } - onClick={() => handleSkipBackward(skip?.skipBackwardSeconds)} + // onClick={() => handleSkipBackward(skip?.skipBackwardSeconds)} tooltip={{ label: t('player.skip', { context: 'back', @@ -190,12 +186,12 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { {skip?.enabled && ( } - onClick={() => handleSkipForward(skip?.skipForwardSeconds)} + // onClick={() => handleSkipForward(skip?.skipForwardSeconds)} tooltip={{ label: t('player.skip', { context: 'forward', @@ -209,7 +205,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { )} } - onClick={handleNextTrack} + // onClick={handleNextTrack} tooltip={{ label: t('player.next', { postProcess: 'sentenceCase' }), openDelay: 0, @@ -229,7 +225,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { ) } isActive={repeat !== PlayerRepeat.NONE} - onClick={handleToggleRepeat} + // onClick={handleToggleRepeat} tooltip={{ label: `${ repeat === PlayerRepeat.NONE @@ -294,7 +290,7 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { // event fires before onChange. Add a small delay to force // onChangeEnd to happen after onCHange setTimeout(() => { - handleSeekSlider(e); + // handleSeekSlider(e); setIsSeeking(false); }, 50); }} diff --git a/src/renderer/features/player/components/full-screen-player-queue.tsx b/src/renderer/features/player/components/full-screen-player-queue.tsx index 6ace8547a..f06e24340 100644 --- a/src/renderer/features/player/components/full-screen-player-queue.tsx +++ b/src/renderer/features/player/components/full-screen-player-queue.tsx @@ -15,7 +15,7 @@ import { } from '/@/renderer/store/full-screen-player.store'; import { Button } from '/@/shared/components/button/button'; import { Group } from '/@/shared/components/group/group'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; const Visualizer = lazy(() => import('/@/renderer/features/player/components/visualizer').then((module) => ({ @@ -48,7 +48,7 @@ export const FullScreenPlayerQueue = () => { }, ]; - if (type === PlaybackType.WEB && webAudio) { + if (type === PlayerType.WEB && webAudio) { items.push({ active: activeTab === 'visualizer', label: t('page.fullscreenPlayer.visualizer', { postProcess: 'titleCase' }), @@ -107,7 +107,7 @@ export const FullScreenPlayerQueue = () => {
) : activeTab === 'lyrics' ? ( - ) : activeTab === 'visualizer' && type === PlaybackType.WEB && webAudio ? ( + ) : activeTab === 'visualizer' && type === PlayerType.WEB && webAudio ? ( }> diff --git a/src/renderer/features/player/components/full-screen-player.tsx b/src/renderer/features/player/components/full-screen-player.tsx index 1092c8604..b350c2ea7 100644 --- a/src/renderer/features/player/components/full-screen-player.tsx +++ b/src/renderer/features/player/components/full-screen-player.tsx @@ -11,7 +11,7 @@ import { FullScreenPlayerImage } from '/@/renderer/features/player/components/fu import { FullScreenPlayerQueue } from '/@/renderer/features/player/components/full-screen-player-queue'; import { useFastAverageColor } from '/@/renderer/hooks'; import { - useCurrentSong, + usePlayerSong, useFullScreenPlayerStore, useFullScreenPlayerStoreActions, useLyricsSettings, @@ -421,7 +421,7 @@ export const FullScreenPlayer = () => { isOpenedRef.current = true; }, [location, setStore]); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const { background } = useFastAverageColor({ algorithm: 'dominant', src: currentSong?.imageUrl, diff --git a/src/renderer/features/player/components/full-screen-similar-songs.tsx b/src/renderer/features/player/components/full-screen-similar-songs.tsx index 12aea8558..1074e5ffc 100644 --- a/src/renderer/features/player/components/full-screen-similar-songs.tsx +++ b/src/renderer/features/player/components/full-screen-similar-songs.tsx @@ -1,8 +1,8 @@ import { SimilarSongsList } from '/@/renderer/features/similar-songs/components/similar-songs-list'; -import { useCurrentSong } from '/@/renderer/store'; +import { usePlayerSong } from '/@/renderer/store'; export const FullScreenSimilarSongs = () => { - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); return currentSong?.id ? : null; }; diff --git a/src/renderer/features/player/components/left-controls.tsx b/src/renderer/features/player/components/left-controls.tsx index 72b19b2dc..eebffc85a 100644 --- a/src/renderer/features/player/components/left-controls.tsx +++ b/src/renderer/features/player/components/left-controls.tsx @@ -12,7 +12,7 @@ import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu/h import { AppRoute } from '/@/renderer/router/routes'; import { useAppStoreActions, - useCurrentSong, + usePlayerSong, useFullScreenPlayerStore, useHotkeySettings, useSetFullScreenPlayerStore, @@ -34,7 +34,7 @@ export const LeftControls = () => { const setFullScreenPlayerStore = useSetFullScreenPlayerStore(); const { collapsed, image } = useSidebarStore(); const hideImage = image && !collapsed; - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const title = currentSong?.name; const artists = currentSong?.artists; const { bindings } = useHotkeySettings(); diff --git a/src/renderer/features/player/components/playerbar.tsx b/src/renderer/features/player/components/playerbar.tsx index 1e275fd65..baa879c9b 100644 --- a/src/renderer/features/player/components/playerbar.tsx +++ b/src/renderer/features/player/components/playerbar.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { MouseEvent, useCallback } from 'react'; +import { MouseEvent } from 'react'; import styles from './playerbar.module.css'; @@ -9,17 +9,17 @@ import { LeftControls } from '/@/renderer/features/player/components/left-contro import { RightControls } from '/@/renderer/features/player/components/right-controls'; import { usePowerSaveBlocker } from '/@/renderer/features/player/hooks/use-power-save-blocker'; import { PlayersRef } from '/@/renderer/features/player/ref/players-ref'; -import { updateSong } from '/@/renderer/features/player/update-remote-song'; import { - useCurrentPlayer, - useCurrentStatus, useFullScreenPlayerStore, - useMuted, - usePlayer1Data, - usePlayer2Data, - usePlayerControls, + usePlayerData, + // usePlayer1Data, + // usePlayer2Data, + // usePlayerControls, + usePlayerMuted, + usePlayerNum, + usePlayerStatus, + usePlayerVolume, useSetFullScreenPlayerStore, - useVolume, } from '/@/renderer/store'; import { useGeneralSettings, @@ -27,20 +27,20 @@ import { useSettingsStore, } from '/@/renderer/store/settings.store'; import { PlaybackSelectors } from '/@/shared/constants/playback-selectors'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; export const Playerbar = () => { const playersRef = PlayersRef; const settings = useSettingsStore((state) => state.playback); const { playerbarOpenDrawer } = useGeneralSettings(); const playbackType = usePlaybackType(); - const volume = useVolume(); - const player1 = usePlayer1Data(); - const player2 = usePlayer2Data(); - const status = useCurrentStatus(); - const player = useCurrentPlayer(); - const muted = useMuted(); - const { autoNext } = usePlayerControls(); + const volume = usePlayerVolume(); + // const player1 = usePlayer1Data(); + // const player2 = usePlayer2Data(); + const status = usePlayerStatus(); + const player = usePlayerNum(); + const muted = usePlayerMuted(); + // const { autoNext } = usePlayerControls(); const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore(); const setFullScreenPlayerStore = useSetFullScreenPlayerStore(); @@ -51,10 +51,12 @@ export const Playerbar = () => { setFullScreenPlayerStore({ expanded: !isFullScreenPlayerExpanded }); }; - const autoNextFn = useCallback(() => { - const playerData = autoNext(); - updateSong(playerData.current.song); - }, [autoNext]); + const { player1, player2 } = usePlayerData(); + + // const autoNextFn = useCallback(() => { + // const playerData = autoNext(); + // updateSong(playerData.current.song); + // }, [autoNext]); return (
{
- {playbackType === PlaybackType.WEB && ( + {playbackType === PlayerType.WEB && ( { const { t } = useTranslation(); const isMinWidth = useMediaQuery('(max-width: 480px)'); - const volume = useVolume(); - const muted = useMuted(); + const volume = usePlayerVolume(); + const muted = usePlayerMuted(); const server = useCurrentServer(); - const currentSong = useCurrentSong(); - const previousSong = usePreviousSong(); + const currentSong = usePlayerSong(); + // const previousSong = usePreviousSong(); const { setSideBar } = useAppStoreActions(); const { rightExpanded: isQueueExpanded } = useSidebarStore(); const { bindings } = useHotkeySettings(); @@ -60,7 +59,7 @@ export const RightControls = () => { const playbackSettings = usePlaybackSettings(); const playbackType = usePlaybackType(); - const speed = useSpeed(); + const speed = usePlayerSpeed(); const volumeWidth = useSettingsStore((state) => state.general.volumeWidth); const speedPreservePitch = useSettingsStore((state) => state.playback.preservePitch); @@ -148,18 +147,18 @@ export const RightControls = () => { bindings.favoriteCurrentToggle.isGlobal ? '' : bindings.favoriteCurrentToggle.hotkey, () => handleToggleFavorite(currentSong), ], - [ - bindings.favoritePreviousAdd.isGlobal ? '' : bindings.favoritePreviousAdd.hotkey, - () => handleAddToFavorites(previousSong), - ], - [ - bindings.favoritePreviousRemove.isGlobal ? '' : bindings.favoritePreviousRemove.hotkey, - () => handleRemoveFromFavorites(previousSong), - ], - [ - bindings.favoritePreviousToggle.isGlobal ? '' : bindings.favoritePreviousToggle.hotkey, - () => handleToggleFavorite(previousSong), - ], + // [ + // bindings.favoritePreviousAdd.isGlobal ? '' : bindings.favoritePreviousAdd.hotkey, + // () => handleAddToFavorites(previousSong), + // ], + // [ + // bindings.favoritePreviousRemove.isGlobal ? '' : bindings.favoritePreviousRemove.hotkey, + // () => handleRemoveFromFavorites(previousSong), + // ], + // [ + // bindings.favoritePreviousToggle.isGlobal ? '' : bindings.favoritePreviousToggle.hotkey, + // () => handleToggleFavorite(previousSong), + // ], [bindings.rate0.isGlobal ? '' : bindings.rate0.hotkey, () => handleUpdateRating(0)], [bindings.rate1.isGlobal ? '' : bindings.rate1.hotkey, () => handleUpdateRating(1)], [bindings.rate2.isGlobal ? '' : bindings.rate2.hotkey, () => handleUpdateRating(2)], diff --git a/src/renderer/features/player/hooks/use-center-controls.ts b/src/renderer/features/player/hooks/use-center-controls.ts index 77642a456..1623c853a 100644 --- a/src/renderer/features/player/hooks/use-center-controls.ts +++ b/src/renderer/features/player/hooks/use-center-controls.ts @@ -1,771 +1,771 @@ -import isElectron from 'is-electron'; -import debounce from 'lodash/debounce'; -import { useCallback, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; -import { updateSong } from '/@/renderer/features/player/update-remote-song'; -import { - useCurrentPlayer, - useCurrentStatus, - useDefaultQueue, - usePlayerControls, - usePlayerStore, - useRepeatStatus, - useSetCurrentTime, - useShuffleStatus, -} from '/@/renderer/store'; -import { usePlaybackType } from '/@/renderer/store/settings.store'; -import { setAutoNext, setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; -import { toast } from '/@/shared/components/toast/toast'; -import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; - -const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; -const mpvPlayerListener = isElectron() ? window.api.mpvPlayerListener : null; -const ipc = isElectron() ? window.api.ipc : null; -const utils = isElectron() ? window.api.utils : null; -const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null; -const remote = isElectron() ? window.api.remote : null; -const mediaSession = navigator.mediaSession; - -export const useCenterControls = (args: { playersRef: any }) => { - const { t } = useTranslation(); - const { playersRef } = args; - - const currentPlayer = useCurrentPlayer(); - const { autoNext, next, pause, play, previous, setCurrentIndex, setRepeat, setShuffle } = - usePlayerControls(); - const setCurrentTime = useSetCurrentTime(); - const queue = useDefaultQueue(); - const playerStatus = useCurrentStatus(); - const repeatStatus = useRepeatStatus(); - const shuffleStatus = useShuffleStatus(); - const playbackType = usePlaybackType(); - const player1Ref = playersRef?.current?.player1; - const player2Ref = playersRef?.current?.player2; - const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref; - const nextPlayerRef = currentPlayer === 1 ? player2Ref : player1Ref; - - const { handleScrobbleFromSeek, handleScrobbleFromSongRestart } = useScrobble(); - - useEffect(() => { - if (mediaSession) { - mediaSession.playbackState = - playerStatus === PlayerStatus.PLAYING ? 'playing' : 'paused'; - } - - remote?.updatePlayback(playerStatus); - }, [playerStatus]); - - useEffect(() => { - remote?.updateRepeat(repeatStatus); - }, [repeatStatus]); - - useEffect(() => { - remote?.updateShuffle(shuffleStatus !== PlayerShuffle.NONE); - }, [shuffleStatus]); - - const resetPlayers = useCallback(() => { - if (player1Ref.getInternalPlayer()) { - player1Ref.getInternalPlayer().currentTime = 0; - player1Ref.getInternalPlayer().pause(); - } - - if (player2Ref.getInternalPlayer()) { - player2Ref.getInternalPlayer().currentTime = 0; - player2Ref.getInternalPlayer().pause(); - } - }, [player1Ref, player2Ref]); - - const resetNextPlayer = useCallback(() => { - currentPlayerRef.getInternalPlayer().volume = 0.1; - - const nextPlayer = nextPlayerRef.getInternalPlayer(); - if (nextPlayer) { - nextPlayer.currentTime = 0; - nextPlayer.pause(); - } - }, [currentPlayerRef, nextPlayerRef]); - - const stopPlayback = useCallback(() => { - player1Ref.getInternalPlayer().pause(); - player2Ref.getInternalPlayer().pause(); - resetPlayers(); - }, [player1Ref, player2Ref, resetPlayers]); - - const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL; - - const handlePlay = useCallback(() => { - if (isMpvPlayer) { - mpvPlayer?.volume(usePlayerStore.getState().volume); - mpvPlayer!.play(); - } else { - currentPlayerRef - .getInternalPlayer() - ?.play() - .catch(() => {}); - } - - play(); - }, [currentPlayerRef, isMpvPlayer, play]); - - const handlePause = useCallback(() => { - if (isMpvPlayer) { - mpvPlayer!.pause(); - } - - pause(); - }, [isMpvPlayer, pause]); - - const handleStop = useCallback(() => { - if (isMpvPlayer) { - mpvPlayer!.stop(); - } else { - stopPlayback(); - } - - setCurrentTime(0); - pause(); - }, [isMpvPlayer, pause, setCurrentTime, stopPlayback]); - - const handleToggleShuffle = useCallback(() => { - if (shuffleStatus === PlayerShuffle.NONE) { - const playerData = setShuffle(PlayerShuffle.TRACK); - remote?.updateShuffle(true); - return setQueueNext(playerData); - } - - const playerData = setShuffle(PlayerShuffle.NONE); - remote?.updateShuffle(false); - return setQueueNext(playerData); - }, [setShuffle, shuffleStatus]); - - const handleToggleRepeat = useCallback(() => { - if (repeatStatus === PlayerRepeat.NONE) { - const playerData = setRepeat(PlayerRepeat.ALL); - remote?.updateRepeat(PlayerRepeat.ALL); - return setQueueNext(playerData); - } - - if (repeatStatus === PlayerRepeat.ALL) { - const playerData = setRepeat(PlayerRepeat.ONE); - remote?.updateRepeat(PlayerRepeat.ONE); - return setQueueNext(playerData); - } - - const playerData = setRepeat(PlayerRepeat.NONE); - remote?.updateRepeat(PlayerRepeat.NONE); - return setQueueNext(playerData); - }, [repeatStatus, setRepeat]); - - const checkIsLastTrack = useCallback(() => { - return usePlayerStore.getState().actions.checkIsLastTrack(); - }, []); - - const checkIsFirstTrack = useCallback(() => { - return usePlayerStore.getState().actions.checkIsFirstTrack(); - }, []); - - const handleAutoNext = useCallback(() => { - const isLastTrack = checkIsLastTrack(); - - const handleRepeatAll = { - local: () => { - const playerData = autoNext(); - updateSong(playerData.current.song); - setAutoNext(playerData); - play(); - }, - web: () => { - const playerData = autoNext(); - updateSong(playerData.current.song); - }, - }; - - const handleRepeatNone = { - local: () => { - if (isLastTrack) { - const playerData = setCurrentIndex(0); - updateSong(playerData.current.song); - setQueue(playerData, true); - pause(); - } else { - const playerData = autoNext(); - updateSong(playerData.current.song); - setAutoNext(playerData); - play(); - } - }, - web: () => { - if (isLastTrack) { - resetPlayers(); - pause(); - } else { - const playerData = autoNext(); - updateSong(playerData.current.song); - resetPlayers(); - } - }, - }; - - const handleRepeatOne = { - local: () => { - const playerData = autoNext(); - updateSong(playerData.current.song); - setAutoNext(playerData); - play(); - }, - web: () => { - if (isLastTrack) { - resetPlayers(); - } else { - autoNext(); - resetPlayers(); - } - }, - }; - - switch (repeatStatus) { - case PlayerRepeat.ALL: - handleRepeatAll[playbackType](); - break; - case PlayerRepeat.NONE: - handleRepeatNone[playbackType](); - break; - case PlayerRepeat.ONE: - handleRepeatOne[playbackType](); - break; - - default: - break; - } - }, [ - autoNext, - checkIsLastTrack, - pause, - play, - playbackType, - repeatStatus, - resetPlayers, - setCurrentIndex, - ]); - - const handleNextTrack = useCallback(() => { - const isLastTrack = checkIsLastTrack(); - setCurrentTime(0); - - const handleRepeatAll = { - local: () => { - const playerData = next(); - updateSong(playerData.current.song); - setQueue(playerData); - }, - web: () => { - const playerData = next(); - updateSong(playerData.current.song); - }, - }; - - const handleRepeatNone = { - local: () => { - if (isLastTrack) { - const playerData = setCurrentIndex(0); - updateSong(playerData.current.song); - setQueue(playerData, true); - pause(); - } else { - const playerData = next(); - updateSong(playerData.current.song); - setQueue(playerData); - } - }, - web: () => { - if (isLastTrack) { - const playerData = setCurrentIndex(0); - updateSong(playerData.current.song); - resetPlayers(); - pause(); - } else { - const playerData = next(); - updateSong(playerData.current.song); - resetPlayers(); - } - }, - }; - - const handleRepeatOne = { - local: () => { - if (!isLastTrack) { - const playerData = next(); - updateSong(playerData.current.song); - setQueue(playerData); - } - }, - web: () => { - if (!isLastTrack) { - const playerData = next(); - updateSong(playerData.current.song); - } - }, - }; - - switch (repeatStatus) { - case PlayerRepeat.ALL: - handleRepeatAll[playbackType](); - break; - case PlayerRepeat.NONE: - handleRepeatNone[playbackType](); - break; - case PlayerRepeat.ONE: - handleRepeatOne[playbackType](); - break; - - default: - break; - } - - setCurrentTime(0); - }, [ - checkIsLastTrack, - next, - pause, - playbackType, - repeatStatus, - resetPlayers, - setCurrentIndex, - setCurrentTime, - ]); - - const handlePrevTrack = useCallback(() => { - const currentTime = isMpvPlayer - ? usePlayerStore.getState().current.time - : currentPlayerRef.getCurrentTime(); - - // Reset the current track more than 10 seconds have elapsed - if (currentTime >= 10) { - setCurrentTime(0, true); - handleScrobbleFromSongRestart(currentTime); - mpris?.updateSeek(0); - if (isMpvPlayer) { - return mpvPlayer!.seekTo(0); - } - return currentPlayerRef.seekTo(0); - } - - const isFirstTrack = checkIsFirstTrack(); - - const handleRepeatAll = { - local: () => { - if (!isFirstTrack) { - const playerData = previous(); - updateSong(playerData.current.song); - setQueue(playerData); - } else { - const playerData = setCurrentIndex(queue.length - 1); - updateSong(playerData.current.song); - setQueue(playerData); - } - }, - web: () => { - if (isFirstTrack) { - const playerData = setCurrentIndex(queue.length - 1); - updateSong(playerData.current.song); - resetPlayers(); - } else { - const playerData = previous(); - updateSong(playerData.current.song); - resetPlayers(); - } - }, - }; - - const handleRepeatNone = { - local: () => { - if (isFirstTrack) { - const playerData = setCurrentIndex(0); - updateSong(playerData.current.song); - setQueue(playerData, true); - pause(); - } else { - const playerData = previous(); - updateSong(playerData.current.song); - setQueue(playerData); - } - }, - web: () => { - if (isFirstTrack) { - resetPlayers(); - pause(); - } else { - const playerData = previous(); - updateSong(playerData.current.song); - resetPlayers(); - } - }, - }; - - const handleRepeatOne = { - local: () => { - const playerData = previous(); - updateSong(playerData.current.song); - setQueue(playerData); - }, - web: () => { - const playerData = previous(); - updateSong(playerData.current.song); - resetPlayers(); - }, - }; - - switch (repeatStatus) { - case PlayerRepeat.ALL: - handleRepeatAll[playbackType](); - break; - case PlayerRepeat.NONE: - handleRepeatNone[playbackType](); - break; - case PlayerRepeat.ONE: - handleRepeatOne[playbackType](); - break; - - default: - break; - } - - return setCurrentTime(0); - }, [ - checkIsFirstTrack, - currentPlayerRef, - handleScrobbleFromSongRestart, - isMpvPlayer, - pause, - playbackType, - previous, - queue.length, - repeatStatus, - resetPlayers, - setCurrentIndex, - setCurrentTime, - ]); - - const handlePlayPause = useCallback(() => { - if (queue.length > 0) { - if (playerStatus === PlayerStatus.PAUSED) { - return handlePlay(); - } - - return handlePause(); - } - - return null; - }, [handlePause, handlePlay, playerStatus, queue]); - - const handleSkipBackward = (seconds: number) => { - const currentTime = isMpvPlayer - ? usePlayerStore.getState().current.time - : currentPlayerRef.getCurrentTime(); - - const evaluatedTime = currentTime - seconds; - const newTime = evaluatedTime < 0 ? 0 : evaluatedTime; - setCurrentTime(newTime, true); - mpris?.updateSeek(newTime); - - if (isMpvPlayer) { - mpvPlayer!.seek(-seconds); - } else { - resetNextPlayer(); - currentPlayerRef.seekTo(newTime, 'seconds'); - } - }; - - const handleSkipForward = (seconds: number) => { - const currentTime = isMpvPlayer - ? usePlayerStore.getState().current.time - : currentPlayerRef.getCurrentTime(); - - if (isMpvPlayer) { - const newTime = currentTime + seconds; - mpvPlayer!.seek(seconds); - mpris?.updateSeek(newTime); - setCurrentTime(newTime, true); - } else { - const checkNewTime = currentTime + seconds; - const songDuration = currentPlayerRef.player.player.duration; - - const newTime = checkNewTime >= songDuration ? songDuration - 1 : checkNewTime; - mpris?.updateSeek(newTime); - - resetNextPlayer(); - setCurrentTime(newTime, true); - currentPlayerRef.seekTo(newTime, 'seconds'); - } - }; - - const debouncedSeek = debounce((e: number) => { - if (isMpvPlayer) { - mpvPlayer!.seekTo(e); - } else { - currentPlayerRef.seekTo(e, 'seconds'); - } - }, 100); - - const handleSeekSlider = useCallback( - (e: any | number) => { - setCurrentTime(e, true); - handleScrobbleFromSeek(e); - debouncedSeek(e); - - mpris?.updateSeek(e); - }, - [debouncedSeek, handleScrobbleFromSeek, setCurrentTime], - ); - - const handleQuit = useCallback(() => { - mpvPlayer!.quit(); - }, []); - - const handleError = useCallback( - (message: string) => { - toast.error({ - id: 'mpv-error', - message, - title: t('error.playbackError', { postProcess: 'sentenceCase' }), - }); - pause(); - mpvPlayer!.pause(); - }, - [pause, t], - ); - - useEffect(() => { - if (mpvPlayerListener) { - mpvPlayerListener.rendererPlayPause(() => { - handlePlayPause(); - }); - - mpvPlayerListener.rendererNext(() => { - handleNextTrack(); - }); - - mpvPlayerListener.rendererPrevious(() => { - handlePrevTrack(); - }); - - mpvPlayerListener.rendererPlay(() => { - handlePlay(); - }); - - mpvPlayerListener.rendererPause(() => { - handlePause(); - }); - - mpvPlayerListener.rendererStop(() => { - handleStop(); - }); - - mpvPlayerListener.rendererCurrentTime((_event: any, time: number) => { - setCurrentTime(time); - }); - - mpvPlayerListener.rendererAutoNext(() => { - handleAutoNext(); - }); - - mpvPlayerListener.rendererToggleShuffle(() => { - handleToggleShuffle(); - }); - - mpvPlayerListener.rendererToggleRepeat(() => { - handleToggleRepeat(); - }); - - mpvPlayerListener.rendererError((_event: any, message: string) => { - handleError(message); - }); - } - - return () => { - ipc?.removeAllListeners('renderer-player-play-pause'); - ipc?.removeAllListeners('renderer-player-next'); - ipc?.removeAllListeners('renderer-player-previous'); - ipc?.removeAllListeners('renderer-player-play'); - ipc?.removeAllListeners('renderer-player-pause'); - ipc?.removeAllListeners('renderer-player-stop'); - ipc?.removeAllListeners('renderer-player-current-time'); - ipc?.removeAllListeners('renderer-player-auto-next'); - ipc?.removeAllListeners('renderer-player-toggle-shuffle'); - ipc?.removeAllListeners('renderer-player-toggle-repeat'); - ipc?.removeAllListeners('renderer-player-error'); - }; - }, [ - autoNext, - handleAutoNext, - handleError, - handleNextTrack, - handlePause, - handlePlay, - handlePlayPause, - handlePrevTrack, - handleQuit, - handleStop, - handleToggleRepeat, - handleToggleShuffle, - isMpvPlayer, - next, - pause, - play, - previous, - setCurrentTime, - ]); - - useEffect(() => { - if (!isElectron() && mediaSession) { - mediaSession.setActionHandler('nexttrack', () => { - handleNextTrack(); - }); - - mediaSession.setActionHandler('pause', () => { - handlePause(); - }); - - mediaSession.setActionHandler('play', () => { - handlePlay(); - }); - - mediaSession.setActionHandler('previoustrack', () => { - handlePrevTrack(); - }); - - mediaSession.setActionHandler('seekto', (evt) => { - const time = evt.seekTime; - - if (time !== undefined) { - handleSeekSlider(time); - } - }); - - mediaSession.setActionHandler('stop', () => { - handleStop(); - }); - - return () => { - mediaSession.setActionHandler('nexttrack', null); - mediaSession.setActionHandler('pause', null); - mediaSession.setActionHandler('play', null); - mediaSession.setActionHandler('previoustrack', null); - mediaSession.setActionHandler('seekto', null); - mediaSession.setActionHandler('stop', null); - }; - } - - return () => {}; - }, [ - handleNextTrack, - handlePause, - handlePlay, - handlePrevTrack, - handleSeekSlider, - handleStop, - setCurrentTime, - ]); - - useEffect(() => { - if (remote) { - const unsubCurrentTime = usePlayerStore.subscribe( - (state) => state.current.time, - (time) => { - remote.updatePosition(time); - }, - ); - - return () => { - unsubCurrentTime(); - }; - } - - return () => {}; - }, []); - - useEffect(() => { - if (utils?.isLinux()) { - mpris!.requestToggleRepeat((_e: any, data: { repeat: string }) => { - if (data.repeat === 'Playlist') { - usePlayerStore.getState().actions.setRepeat(PlayerRepeat.ALL); - } else if (data.repeat === 'Track') { - usePlayerStore.getState().actions.setRepeat(PlayerRepeat.ONE); - } else { - usePlayerStore.getState().actions.setRepeat(PlayerRepeat.NONE); - } - }); - - mpris!.requestToggleShuffle((_e: any, data: { shuffle: boolean }) => { - usePlayerStore - .getState() - .actions.setShuffle(data.shuffle ? PlayerShuffle.TRACK : PlayerShuffle.NONE); - }); - - return () => { - ipc?.removeAllListeners('mpris-request-toggle-repeat'); - ipc?.removeAllListeners('mpris-request-toggle-shuffle'); - }; - } - - return () => {}; - }, [handleSeekSlider, isMpvPlayer]); - - useEffect(() => { - if (remote) { - remote.requestPosition((_e: any, data: { position: number }) => { - const newTime = data.position; - handleSeekSlider(newTime); - }); - - remote.requestSeek((_e: any, data: { offset: number }) => { - const currentTime = usePlayerStore.getState().current.time; - const currentSongDuration = usePlayerStore.getState().current.song?.duration || 0; - const resultingTime = currentTime + data.offset; - - let newTime = resultingTime; - if (resultingTime > currentSongDuration) { - newTime = currentSongDuration - 1; - } - - if (resultingTime < 0) { - newTime = 0; - } - - handleSeekSlider(newTime); - }); - - remote.requestVolume((_e: any, data: { volume: number }) => { - usePlayerStore.getState().actions.setVolume(data.volume); - - if (isMpvPlayer) { - mpvPlayer!.volume(data.volume); - } - }); - - return () => { - ipc?.removeAllListeners('request-position'); - ipc?.removeAllListeners('request-seek'); - ipc?.removeAllListeners('request-volume'); - }; - } - - return () => {}; - }); - - return { - handleNextTrack, - handlePause, - handlePlay, - handlePlayPause, - handlePrevTrack, - handleSeekSlider, - handleSkipBackward, - handleSkipForward, - handleStop, - handleToggleRepeat, - handleToggleShuffle, - }; -}; +// import isElectron from 'is-electron'; +// import debounce from 'lodash/debounce'; +// import { useCallback, useEffect } from 'react'; +// import { useTranslation } from 'react-i18next'; + +// import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; +// import { updateSong } from '/@/renderer/features/player/update-remote-song'; +// import { +// usePlayerNum, +// usePlayerStatus, +// usePlayerQueue, +// usePlayerControls, +// usePlayerStore, +// useRepeatStatus, +// useSetCurrentTime, +// useShuffleStatus, +// } from '/@/renderer/store'; +// import { usePlaybackType } from '/@/renderer/store/settings.store'; +// import { setAutoNext, setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; +// import { toast } from '/@/shared/components/toast/toast'; +// import { PlaybackType, PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; + +// const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; +// const mpvPlayerListener = isElectron() ? window.api.mpvPlayerListener : null; +// const ipc = isElectron() ? window.api.ipc : null; +// const utils = isElectron() ? window.api.utils : null; +// const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null; +// const remote = isElectron() ? window.api.remote : null; +// const mediaSession = navigator.mediaSession; + +// export const useCenterControls = (args: { playersRef: any }) => { +// const { t } = useTranslation(); +// const { playersRef } = args; + +// const currentPlayer = usePlayerNum(); +// const { autoNext, next, pause, play, previous, setCurrentIndex, setRepeat, setShuffle } = +// usePlayerControls(); +// const setCurrentTime = useSetCurrentTime(); +// const queue = usePlayerQueue(); +// const playerStatus = usePlayerStatus(); +// const repeatStatus = useRepeatStatus(); +// const shuffleStatus = useShuffleStatus(); +// const playbackType = usePlaybackType(); +// const player1Ref = playersRef?.current?.player1; +// const player2Ref = playersRef?.current?.player2; +// const currentPlayerRef = currentPlayer === 1 ? player1Ref : player2Ref; +// const nextPlayerRef = currentPlayer === 1 ? player2Ref : player1Ref; + +// const { handleScrobbleFromSeek, handleScrobbleFromSongRestart } = useScrobble(); + +// useEffect(() => { +// if (mediaSession) { +// mediaSession.playbackState = +// playerStatus === PlayerStatus.PLAYING ? 'playing' : 'paused'; +// } + +// remote?.updatePlayback(playerStatus); +// }, [playerStatus]); + +// useEffect(() => { +// remote?.updateRepeat(repeatStatus); +// }, [repeatStatus]); + +// useEffect(() => { +// remote?.updateShuffle(shuffleStatus !== PlayerShuffle.NONE); +// }, [shuffleStatus]); + +// const resetPlayers = useCallback(() => { +// if (player1Ref.getInternalPlayer()) { +// player1Ref.getInternalPlayer().currentTime = 0; +// player1Ref.getInternalPlayer().pause(); +// } + +// if (player2Ref.getInternalPlayer()) { +// player2Ref.getInternalPlayer().currentTime = 0; +// player2Ref.getInternalPlayer().pause(); +// } +// }, [player1Ref, player2Ref]); + +// const resetNextPlayer = useCallback(() => { +// currentPlayerRef.getInternalPlayer().volume = 0.1; + +// const nextPlayer = nextPlayerRef.getInternalPlayer(); +// if (nextPlayer) { +// nextPlayer.currentTime = 0; +// nextPlayer.pause(); +// } +// }, [currentPlayerRef, nextPlayerRef]); + +// const stopPlayback = useCallback(() => { +// player1Ref.getInternalPlayer().pause(); +// player2Ref.getInternalPlayer().pause(); +// resetPlayers(); +// }, [player1Ref, player2Ref, resetPlayers]); + +// const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL; + +// const handlePlay = useCallback(() => { +// if (isMpvPlayer) { +// mpvPlayer?.volume(usePlayerStore.getState().volume); +// mpvPlayer!.play(); +// } else { +// currentPlayerRef +// .getInternalPlayer() +// ?.play() +// .catch(() => {}); +// } + +// play(); +// }, [currentPlayerRef, isMpvPlayer, play]); + +// const handlePause = useCallback(() => { +// if (isMpvPlayer) { +// mpvPlayer!.pause(); +// } + +// pause(); +// }, [isMpvPlayer, pause]); + +// const handleStop = useCallback(() => { +// if (isMpvPlayer) { +// mpvPlayer!.stop(); +// } else { +// stopPlayback(); +// } + +// setCurrentTime(0); +// pause(); +// }, [isMpvPlayer, pause, setCurrentTime, stopPlayback]); + +// const handleToggleShuffle = useCallback(() => { +// if (shuffleStatus === PlayerShuffle.NONE) { +// const playerData = setShuffle(PlayerShuffle.TRACK); +// remote?.updateShuffle(true); +// return setQueueNext(playerData); +// } + +// const playerData = setShuffle(PlayerShuffle.NONE); +// remote?.updateShuffle(false); +// return setQueueNext(playerData); +// }, [setShuffle, shuffleStatus]); + +// const handleToggleRepeat = useCallback(() => { +// if (repeatStatus === PlayerRepeat.NONE) { +// const playerData = setRepeat(PlayerRepeat.ALL); +// remote?.updateRepeat(PlayerRepeat.ALL); +// return setQueueNext(playerData); +// } + +// if (repeatStatus === PlayerRepeat.ALL) { +// const playerData = setRepeat(PlayerRepeat.ONE); +// remote?.updateRepeat(PlayerRepeat.ONE); +// return setQueueNext(playerData); +// } + +// const playerData = setRepeat(PlayerRepeat.NONE); +// remote?.updateRepeat(PlayerRepeat.NONE); +// return setQueueNext(playerData); +// }, [repeatStatus, setRepeat]); + +// const checkIsLastTrack = useCallback(() => { +// return usePlayerStore.getState().actions.checkIsLastTrack(); +// }, []); + +// const checkIsFirstTrack = useCallback(() => { +// return usePlayerStore.getState().actions.checkIsFirstTrack(); +// }, []); + +// const handleAutoNext = useCallback(() => { +// const isLastTrack = checkIsLastTrack(); + +// const handleRepeatAll = { +// local: () => { +// const playerData = autoNext(); +// updateSong(playerData.current.song); +// setAutoNext(playerData); +// play(); +// }, +// web: () => { +// const playerData = autoNext(); +// updateSong(playerData.current.song); +// }, +// }; + +// const handleRepeatNone = { +// local: () => { +// if (isLastTrack) { +// const playerData = setCurrentIndex(0); +// updateSong(playerData.current.song); +// setQueue(playerData, true); +// pause(); +// } else { +// const playerData = autoNext(); +// updateSong(playerData.current.song); +// setAutoNext(playerData); +// play(); +// } +// }, +// web: () => { +// if (isLastTrack) { +// resetPlayers(); +// pause(); +// } else { +// const playerData = autoNext(); +// updateSong(playerData.current.song); +// resetPlayers(); +// } +// }, +// }; + +// const handleRepeatOne = { +// local: () => { +// const playerData = autoNext(); +// updateSong(playerData.current.song); +// setAutoNext(playerData); +// play(); +// }, +// web: () => { +// if (isLastTrack) { +// resetPlayers(); +// } else { +// autoNext(); +// resetPlayers(); +// } +// }, +// }; + +// switch (repeatStatus) { +// case PlayerRepeat.ALL: +// handleRepeatAll[playbackType](); +// break; +// case PlayerRepeat.NONE: +// handleRepeatNone[playbackType](); +// break; +// case PlayerRepeat.ONE: +// handleRepeatOne[playbackType](); +// break; + +// default: +// break; +// } +// }, [ +// autoNext, +// checkIsLastTrack, +// pause, +// play, +// playbackType, +// repeatStatus, +// resetPlayers, +// setCurrentIndex, +// ]); + +// const handleNextTrack = useCallback(() => { +// const isLastTrack = checkIsLastTrack(); +// setCurrentTime(0); + +// const handleRepeatAll = { +// local: () => { +// const playerData = next(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// }, +// web: () => { +// const playerData = next(); +// updateSong(playerData.current.song); +// }, +// }; + +// const handleRepeatNone = { +// local: () => { +// if (isLastTrack) { +// const playerData = setCurrentIndex(0); +// updateSong(playerData.current.song); +// setQueue(playerData, true); +// pause(); +// } else { +// const playerData = next(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// } +// }, +// web: () => { +// if (isLastTrack) { +// const playerData = setCurrentIndex(0); +// updateSong(playerData.current.song); +// resetPlayers(); +// pause(); +// } else { +// const playerData = next(); +// updateSong(playerData.current.song); +// resetPlayers(); +// } +// }, +// }; + +// const handleRepeatOne = { +// local: () => { +// if (!isLastTrack) { +// const playerData = next(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// } +// }, +// web: () => { +// if (!isLastTrack) { +// const playerData = next(); +// updateSong(playerData.current.song); +// } +// }, +// }; + +// switch (repeatStatus) { +// case PlayerRepeat.ALL: +// handleRepeatAll[playbackType](); +// break; +// case PlayerRepeat.NONE: +// handleRepeatNone[playbackType](); +// break; +// case PlayerRepeat.ONE: +// handleRepeatOne[playbackType](); +// break; + +// default: +// break; +// } + +// setCurrentTime(0); +// }, [ +// checkIsLastTrack, +// next, +// pause, +// playbackType, +// repeatStatus, +// resetPlayers, +// setCurrentIndex, +// setCurrentTime, +// ]); + +// const handlePrevTrack = useCallback(() => { +// const currentTime = isMpvPlayer +// ? usePlayerStore.getState().current.time +// : currentPlayerRef.getCurrentTime(); + +// // Reset the current track more than 10 seconds have elapsed +// if (currentTime >= 10) { +// setCurrentTime(0, true); +// handleScrobbleFromSongRestart(currentTime); +// mpris?.updateSeek(0); +// if (isMpvPlayer) { +// return mpvPlayer!.seekTo(0); +// } +// return currentPlayerRef.seekTo(0); +// } + +// const isFirstTrack = checkIsFirstTrack(); + +// const handleRepeatAll = { +// local: () => { +// if (!isFirstTrack) { +// const playerData = previous(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// } else { +// const playerData = setCurrentIndex(queue.length - 1); +// updateSong(playerData.current.song); +// setQueue(playerData); +// } +// }, +// web: () => { +// if (isFirstTrack) { +// const playerData = setCurrentIndex(queue.length - 1); +// updateSong(playerData.current.song); +// resetPlayers(); +// } else { +// const playerData = previous(); +// updateSong(playerData.current.song); +// resetPlayers(); +// } +// }, +// }; + +// const handleRepeatNone = { +// local: () => { +// if (isFirstTrack) { +// const playerData = setCurrentIndex(0); +// updateSong(playerData.current.song); +// setQueue(playerData, true); +// pause(); +// } else { +// const playerData = previous(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// } +// }, +// web: () => { +// if (isFirstTrack) { +// resetPlayers(); +// pause(); +// } else { +// const playerData = previous(); +// updateSong(playerData.current.song); +// resetPlayers(); +// } +// }, +// }; + +// const handleRepeatOne = { +// local: () => { +// const playerData = previous(); +// updateSong(playerData.current.song); +// setQueue(playerData); +// }, +// web: () => { +// const playerData = previous(); +// updateSong(playerData.current.song); +// resetPlayers(); +// }, +// }; + +// switch (repeatStatus) { +// case PlayerRepeat.ALL: +// handleRepeatAll[playbackType](); +// break; +// case PlayerRepeat.NONE: +// handleRepeatNone[playbackType](); +// break; +// case PlayerRepeat.ONE: +// handleRepeatOne[playbackType](); +// break; + +// default: +// break; +// } + +// return setCurrentTime(0); +// }, [ +// checkIsFirstTrack, +// currentPlayerRef, +// handleScrobbleFromSongRestart, +// isMpvPlayer, +// pause, +// playbackType, +// previous, +// queue.length, +// repeatStatus, +// resetPlayers, +// setCurrentIndex, +// setCurrentTime, +// ]); + +// const handlePlayPause = useCallback(() => { +// if (queue.length > 0) { +// if (playerStatus === PlayerStatus.PAUSED) { +// return handlePlay(); +// } + +// return handlePause(); +// } + +// return null; +// }, [handlePause, handlePlay, playerStatus, queue]); + +// const handleSkipBackward = (seconds: number) => { +// const currentTime = isMpvPlayer +// ? usePlayerStore.getState().current.time +// : currentPlayerRef.getCurrentTime(); + +// const evaluatedTime = currentTime - seconds; +// const newTime = evaluatedTime < 0 ? 0 : evaluatedTime; +// setCurrentTime(newTime, true); +// mpris?.updateSeek(newTime); + +// if (isMpvPlayer) { +// mpvPlayer!.seek(-seconds); +// } else { +// resetNextPlayer(); +// currentPlayerRef.seekTo(newTime, 'seconds'); +// } +// }; + +// const handleSkipForward = (seconds: number) => { +// const currentTime = isMpvPlayer +// ? usePlayerStore.getState().current.time +// : currentPlayerRef.getCurrentTime(); + +// if (isMpvPlayer) { +// const newTime = currentTime + seconds; +// mpvPlayer!.seek(seconds); +// mpris?.updateSeek(newTime); +// setCurrentTime(newTime, true); +// } else { +// const checkNewTime = currentTime + seconds; +// const songDuration = currentPlayerRef.player.player.duration; + +// const newTime = checkNewTime >= songDuration ? songDuration - 1 : checkNewTime; +// mpris?.updateSeek(newTime); + +// resetNextPlayer(); +// setCurrentTime(newTime, true); +// currentPlayerRef.seekTo(newTime, 'seconds'); +// } +// }; + +// const debouncedSeek = debounce((e: number) => { +// if (isMpvPlayer) { +// mpvPlayer!.seekTo(e); +// } else { +// currentPlayerRef.seekTo(e, 'seconds'); +// } +// }, 100); + +// const handleSeekSlider = useCallback( +// (e: any | number) => { +// setCurrentTime(e, true); +// handleScrobbleFromSeek(e); +// debouncedSeek(e); + +// mpris?.updateSeek(e); +// }, +// [debouncedSeek, handleScrobbleFromSeek, setCurrentTime], +// ); + +// const handleQuit = useCallback(() => { +// mpvPlayer!.quit(); +// }, []); + +// const handleError = useCallback( +// (message: string) => { +// toast.error({ +// id: 'mpv-error', +// message, +// title: t('error.playbackError', { postProcess: 'sentenceCase' }), +// }); +// pause(); +// mpvPlayer!.pause(); +// }, +// [pause, t], +// ); + +// useEffect(() => { +// if (mpvPlayerListener) { +// mpvPlayerListener.rendererPlayPause(() => { +// handlePlayPause(); +// }); + +// mpvPlayerListener.rendererNext(() => { +// handleNextTrack(); +// }); + +// mpvPlayerListener.rendererPrevious(() => { +// handlePrevTrack(); +// }); + +// mpvPlayerListener.rendererPlay(() => { +// handlePlay(); +// }); + +// mpvPlayerListener.rendererPause(() => { +// handlePause(); +// }); + +// mpvPlayerListener.rendererStop(() => { +// handleStop(); +// }); + +// mpvPlayerListener.rendererCurrentTime((_event: any, time: number) => { +// setCurrentTime(time); +// }); + +// mpvPlayerListener.rendererAutoNext(() => { +// handleAutoNext(); +// }); + +// mpvPlayerListener.rendererToggleShuffle(() => { +// handleToggleShuffle(); +// }); + +// mpvPlayerListener.rendererToggleRepeat(() => { +// handleToggleRepeat(); +// }); + +// mpvPlayerListener.rendererError((_event: any, message: string) => { +// handleError(message); +// }); +// } + +// return () => { +// ipc?.removeAllListeners('renderer-player-play-pause'); +// ipc?.removeAllListeners('renderer-player-next'); +// ipc?.removeAllListeners('renderer-player-previous'); +// ipc?.removeAllListeners('renderer-player-play'); +// ipc?.removeAllListeners('renderer-player-pause'); +// ipc?.removeAllListeners('renderer-player-stop'); +// ipc?.removeAllListeners('renderer-player-current-time'); +// ipc?.removeAllListeners('renderer-player-auto-next'); +// ipc?.removeAllListeners('renderer-player-toggle-shuffle'); +// ipc?.removeAllListeners('renderer-player-toggle-repeat'); +// ipc?.removeAllListeners('renderer-player-error'); +// }; +// }, [ +// autoNext, +// handleAutoNext, +// handleError, +// handleNextTrack, +// handlePause, +// handlePlay, +// handlePlayPause, +// handlePrevTrack, +// handleQuit, +// handleStop, +// handleToggleRepeat, +// handleToggleShuffle, +// isMpvPlayer, +// next, +// pause, +// play, +// previous, +// setCurrentTime, +// ]); + +// useEffect(() => { +// if (!isElectron() && mediaSession) { +// mediaSession.setActionHandler('nexttrack', () => { +// handleNextTrack(); +// }); + +// mediaSession.setActionHandler('pause', () => { +// handlePause(); +// }); + +// mediaSession.setActionHandler('play', () => { +// handlePlay(); +// }); + +// mediaSession.setActionHandler('previoustrack', () => { +// handlePrevTrack(); +// }); + +// mediaSession.setActionHandler('seekto', (evt) => { +// const time = evt.seekTime; + +// if (time !== undefined) { +// handleSeekSlider(time); +// } +// }); + +// mediaSession.setActionHandler('stop', () => { +// handleStop(); +// }); + +// return () => { +// mediaSession.setActionHandler('nexttrack', null); +// mediaSession.setActionHandler('pause', null); +// mediaSession.setActionHandler('play', null); +// mediaSession.setActionHandler('previoustrack', null); +// mediaSession.setActionHandler('seekto', null); +// mediaSession.setActionHandler('stop', null); +// }; +// } + +// return () => {}; +// }, [ +// handleNextTrack, +// handlePause, +// handlePlay, +// handlePrevTrack, +// handleSeekSlider, +// handleStop, +// setCurrentTime, +// ]); + +// useEffect(() => { +// if (remote) { +// const unsubCurrentTime = usePlayerStore.subscribe( +// (state) => state.current.time, +// (time) => { +// remote.updatePosition(time); +// }, +// ); + +// return () => { +// unsubCurrentTime(); +// }; +// } + +// return () => {}; +// }, []); + +// useEffect(() => { +// if (utils?.isLinux()) { +// mpris!.requestToggleRepeat((_e: any, data: { repeat: string }) => { +// if (data.repeat === 'Playlist') { +// usePlayerStore.getState().actions.setRepeat(PlayerRepeat.ALL); +// } else if (data.repeat === 'Track') { +// usePlayerStore.getState().actions.setRepeat(PlayerRepeat.ONE); +// } else { +// usePlayerStore.getState().actions.setRepeat(PlayerRepeat.NONE); +// } +// }); + +// mpris!.requestToggleShuffle((_e: any, data: { shuffle: boolean }) => { +// usePlayerStore +// .getState() +// .actions.setShuffle(data.shuffle ? PlayerShuffle.TRACK : PlayerShuffle.NONE); +// }); + +// return () => { +// ipc?.removeAllListeners('mpris-request-toggle-repeat'); +// ipc?.removeAllListeners('mpris-request-toggle-shuffle'); +// }; +// } + +// return () => {}; +// }, [handleSeekSlider, isMpvPlayer]); + +// useEffect(() => { +// if (remote) { +// remote.requestPosition((_e: any, data: { position: number }) => { +// const newTime = data.position; +// handleSeekSlider(newTime); +// }); + +// remote.requestSeek((_e: any, data: { offset: number }) => { +// const currentTime = usePlayerStore.getState().current.time; +// const currentSongDuration = usePlayerStore.getState().current.song?.duration || 0; +// const resultingTime = currentTime + data.offset; + +// let newTime = resultingTime; +// if (resultingTime > currentSongDuration) { +// newTime = currentSongDuration - 1; +// } + +// if (resultingTime < 0) { +// newTime = 0; +// } + +// handleSeekSlider(newTime); +// }); + +// remote.requestVolume((_e: any, data: { volume: number }) => { +// usePlayerStore.getState().actions.setVolume(data.volume); + +// if (isMpvPlayer) { +// mpvPlayer!.volume(data.volume); +// } +// }); + +// return () => { +// ipc?.removeAllListeners('request-position'); +// ipc?.removeAllListeners('request-seek'); +// ipc?.removeAllListeners('request-volume'); +// }; +// } + +// return () => {}; +// }); + +// return { +// handleNextTrack, +// handlePause, +// handlePlay, +// handlePlayPause, +// handlePrevTrack, +// handleSeekSlider, +// handleSkipBackward, +// handleSkipForward, +// handleStop, +// handleToggleRepeat, +// handleToggleShuffle, +// }; +// }; diff --git a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts index 8a8ccbdc4..54f5b4d55 100644 --- a/src/renderer/features/player/hooks/use-handle-playqueue-add.ts +++ b/src/renderer/features/player/hooks/use-handle-playqueue-add.ts @@ -1,226 +1,226 @@ -import { useQueryClient } from '@tanstack/react-query'; -import isElectron from 'is-electron'; -import { nanoid } from 'nanoid/non-secure'; -import { useCallback, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; +// import { useQueryClient } from '@tanstack/react-query'; +// import isElectron from 'is-electron'; +// import { nanoid } from 'nanoid/non-secure'; +// import { useCallback, useRef } from 'react'; +// import { useTranslation } from 'react-i18next'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { PlayersRef } from '/@/renderer/features/player/ref/players-ref'; -import { updateSong } from '/@/renderer/features/player/update-remote-song'; -import { - getAlbumArtistSongsById, - getAlbumSongsById, - getArtistSongsById, - getGenreSongsById, - getPlaylistSongsById, - getSongById, - getSongsByQuery, -} from '/@/renderer/features/player/utils'; -import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store'; -import { useGeneralSettings, usePlaybackType } from '/@/renderer/store/settings.store'; -import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; -import { toast } from '/@/shared/components/toast/toast'; -import { - instanceOfCancellationError, - LibraryItem, - QueueSong, - Song, - SongListResponse, -} from '/@/shared/types/domain-types'; -import { Play, PlaybackType, PlayQueueAddOptions } from '/@/shared/types/types'; +// import { queryKeys } from '/@/renderer/api/query-keys'; +// import { PlayersRef } from '/@/renderer/features/player/ref/players-ref'; +// import { updateSong } from '/@/renderer/features/player/update-remote-song'; +// import { +// getAlbumArtistSongsById, +// getAlbumSongsById, +// getArtistSongsById, +// getGenreSongsById, +// getPlaylistSongsById, +// getSongById, +// getSongsByQuery, +// } from '/@/renderer/features/player/utils'; +// import { useCurrentServer, usePlayerStore } from '/@/renderer/store'; +// import { useGeneralSettings, usePlaybackType } from '/@/renderer/store/settings.store'; +// import { setQueue, setQueueNext } from '/@/renderer/utils/set-transcoded-queue-data'; +// import { toast } from '/@/shared/components/toast/toast'; +// import { +// instanceOfCancellationError, +// LibraryItem, +// QueueSong, +// Song, +// SongListResponse, +// } from '/@/shared/types/domain-types'; +// import { Play, PlaybackType, PlayQueueAddOptions } from '/@/shared/types/types'; -const getRootQueryKey = (itemType: LibraryItem, serverId: string) => { - let queryKey; +// 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; - } +// 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; -}; +// return queryKey; +// }; -const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; +// const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; -const addToQueue = usePlayerStore.getState().actions.addToQueue; +// 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>>({}); +// export const useHandlePlayQueueAdd = () => { +// const { t } = useTranslation(); +// const queryClient = useQueryClient(); +// const playbackType = usePlaybackType(); +// const server = useCurrentServer(); +// const { play } = usePlayerControls(); +// const timeoutIds = useRef>>({}); - const { doubleClickQueueAll } = useGeneralSettings(); +// const { doubleClickQueueAll } = useGeneralSettings(); - const handlePlayQueueAdd = useCallback( - async (options: PlayQueueAddOptions) => { - if (!server) return toast.error({ message: 'No server selected', type: 'error' }); - const { byData, byItemType, initialIndex, initialSongId, playType, query } = options; - let songs: null | QueueSong[] = null; - // Allow this to be undefined for "play shuffled". If undefined, default to 0, - // otherwise, choose the selected item in the queue - let initialSongIndex: number | undefined; - let toastId: null | string = null; +// const handlePlayQueueAdd = useCallback( +// async (options: PlayQueueAddOptions) => { +// if (!server) return toast.error({ message: 'No server selected', type: 'error' }); +// const { byData, byItemType, initialIndex, initialSongId, playType, query } = options; +// let songs: null | QueueSong[] = null; +// // Allow this to be undefined for "play shuffled". If undefined, default to 0, +// // otherwise, choose the selected item in the queue +// let initialSongIndex: number | undefined; +// let toastId: null | string = null; - if (byItemType) { - let songList: SongListResponse | undefined; - const { id, type: itemType } = byItemType; +// if (byItemType) { +// let songList: SongListResponse | undefined; +// const { id, type: itemType } = byItemType; - const fetchId = nanoid(); - timeoutIds.current = { - ...timeoutIds.current, - [fetchId]: setTimeout(() => { - toastId = toast.info({ - autoClose: false, - message: t('player.playbackFetchCancel', { - postProcess: 'sentenceCase', - }), - onClose: () => { - queryClient.cancelQueries({ - exact: false, - queryKey: getRootQueryKey(itemType, server?.id), - }); - }, - title: t('player.playbackFetchInProgress', { - postProcess: 'sentenceCase', - }), - }); - }, 2000), - }; +// const fetchId = nanoid(); +// timeoutIds.current = { +// ...timeoutIds.current, +// [fetchId]: setTimeout(() => { +// toastId = toast.info({ +// autoClose: false, +// 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.ARTIST) { - songList = await getArtistSongsById({ - 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 if (!doubleClickQueueAll && initialSongId) { - songList = await getSongById({ - id: initialSongId, - queryClient, - server, - }); - } else { - songList = await getSongsByQuery({ query, queryClient, server }); - } - } +// 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.ARTIST) { +// songList = await getArtistSongsById({ +// 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 if (!doubleClickQueueAll && initialSongId) { +// songList = await getSongById({ +// id: initialSongId, +// queryClient, +// server, +// }); +// } else { +// songList = await getSongsByQuery({ query, queryClient, server }); +// } +// } - clearTimeout(timeoutIds.current[fetchId] as ReturnType); - delete timeoutIds.current[fetchId]; - if (toastId) { - toast.hide(toastId); - } - } catch (err: any) { - if (instanceOfCancellationError(err)) { - return null; - } +// clearTimeout(timeoutIds.current[fetchId] as ReturnType); +// delete timeoutIds.current[fetchId]; +// if (toastId) { +// toast.hide(toastId); +// } +// } catch (err: any) { +// if (instanceOfCancellationError(err)) { +// return null; +// } - clearTimeout(timeoutIds.current[fetchId] as ReturnType); - delete timeoutIds.current[fetchId]; - toast.hide(fetchId); +// 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, - }); - } +// 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; - } +// 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', { postProcess: 'sentenceCase' }), - }); +// if (!songs || songs?.length === 0) +// return toast.warn({ +// message: t('common.noResultsFromQuery', { postProcess: 'sentenceCase' }), +// title: t('player.playbackFetchNoResults', { postProcess: 'sentenceCase' }), +// }); - if (initialIndex) { - initialSongIndex = initialIndex; - } else if (initialSongId) { - initialSongIndex = songs.findIndex((song) => song.id === initialSongId); - } +// 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 }); +// const hadSong = usePlayerStore.getState().queue.default.length > 0; +// const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs }); - updateSong(playerData.current.song); +// updateSong(playerData.current.song); - const replacesQueue = playType === Play.NOW || playType === Play.SHUFFLE; +// const replacesQueue = playType === Play.NOW || playType === Play.SHUFFLE; - if (playbackType === PlaybackType.LOCAL) { - mpvPlayer!.volume(usePlayerStore.getState().volume); +// if (playbackType === PlaybackType.LOCAL) { +// mpvPlayer!.volume(usePlayerStore.getState().volume); - if (replacesQueue || !hadSong) { - mpvPlayer!.pause(); - setQueue(playerData, false); - } else { - setQueueNext(playerData); - } - } else { - const player = - playerData.current.player === 1 - ? PlayersRef.current?.player1 - : PlayersRef.current?.player2; - const underlying = player?.getInternalPlayer(); - if (underlying && replacesQueue) { - underlying.currentTime = 0; - } - } +// if (replacesQueue || !hadSong) { +// mpvPlayer!.pause(); +// setQueue(playerData, false); +// } else { +// setQueueNext(playerData); +// } +// } else { +// const player = +// playerData.current.player === 1 +// ? PlayersRef.current?.player1 +// : PlayersRef.current?.player2; +// const underlying = player?.getInternalPlayer(); +// if (underlying && replacesQueue) { +// underlying.currentTime = 0; +// } +// } - // We should only play if the queue was empty, or we are doing play NOW - // (override the queue). - if (replacesQueue || !hadSong) { - play(); - } +// // We should only play if the queue was empty, or we are doing play NOW +// // (override the queue). +// if (replacesQueue || !hadSong) { +// play(); +// } - return null; - }, - [doubleClickQueueAll, play, playbackType, queryClient, server, t], - ); +// return null; +// }, +// [doubleClickQueueAll, play, playbackType, queryClient, server, t], +// ); - return handlePlayQueueAdd; -}; +// return handlePlayQueueAdd; +// }; diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index 3bcdfc001..557c1f59c 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -1,8 +1,8 @@ import { useEffect } from 'react'; import { - useCurrentSong, - useCurrentStatus, + usePlayerSong, + usePlayerStatus, usePlaybackSettings, useSettingsStore, } from '/@/renderer/store'; @@ -28,8 +28,8 @@ export const useMediaSession = ({ handleStop: () => void; }) => { const { mediaSession: mediaSessionEnabled } = usePlaybackSettings(); - const playerStatus = useCurrentStatus(); - const currentSong = useCurrentSong(); + const playerStatus = usePlayerStatus(); + const currentSong = usePlayerSong(); const mediaSession = navigator.mediaSession; const skip = useSettingsStore((state) => state.general.skipButtons); diff --git a/src/renderer/features/player/hooks/use-power-save-blocker.ts b/src/renderer/features/player/hooks/use-power-save-blocker.ts index 26a71cfab..6bbe8ae2d 100644 --- a/src/renderer/features/player/hooks/use-power-save-blocker.ts +++ b/src/renderer/features/player/hooks/use-power-save-blocker.ts @@ -1,14 +1,14 @@ import isElectron from 'is-electron'; import { useCallback, useEffect } from 'react'; -import { useCurrentStatus } from '/@/renderer/store'; +import { usePlayerStatus } from '/@/renderer/store'; import { useWindowSettings } from '/@/renderer/store'; import { PlayerStatus } from '/@/shared/types/types'; const ipc = isElectron() ? window.api.ipc : null; export const usePowerSaveBlocker = () => { - const status = useCurrentStatus(); + const status = usePlayerStatus(); const { preventSleepOnPlayback } = useWindowSettings(); const startPowerSaveBlocker = useCallback(async () => { diff --git a/src/renderer/features/player/hooks/use-right-controls.ts b/src/renderer/features/player/hooks/use-right-controls.ts index 526291ca6..bf188a649 100644 --- a/src/renderer/features/player/hooks/use-right-controls.ts +++ b/src/renderer/features/player/hooks/use-right-controls.ts @@ -1,13 +1,7 @@ import isElectron from 'is-electron'; import { useCallback, useEffect, WheelEvent } from 'react'; -import { - useMuted, - usePlayerControls, - useSetCurrentSpeed, - useSpeed, - useVolume, -} from '/@/renderer/store'; +import { usePlayerMuted, usePlayerSpeed, usePlayerVolume } from '/@/renderer/store'; import { useGeneralSettings } from '/@/renderer/store/settings.store'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -40,12 +34,12 @@ const calculateVolumeDown = (volume: number, volumeWheelStep: number) => { }; export const useRightControls = () => { - const { setMuted, setVolume } = usePlayerControls(); - const volume = useVolume(); - const muted = useMuted(); + // const { setMuted, setVolume } = usePlayerControls(); + const volume = usePlayerVolume(); + const muted = usePlayerMuted(); const { volumeWheelStep } = useGeneralSettings(); - const speed = useSpeed(); - const setCurrentSpeed = useSetCurrentSpeed(); + const speed = usePlayerSpeed(); + // const setCurrentSpeed = useSetCurrentSpeed(); // Ensure that the mpv player volume is set on startup useEffect(() => { @@ -63,40 +57,37 @@ export const useRightControls = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handleSpeed = useCallback( - (e: number) => { - setCurrentSpeed(e); - if (mpvPlayer) { - mpvPlayer?.setProperties({ speed: e }); - } - }, - [setCurrentSpeed], - ); + const handleSpeed = useCallback((e: number) => { + // setCurrentSpeed(e); + if (mpvPlayer) { + mpvPlayer?.setProperties({ speed: e }); + } + }, []); const handleVolumeSlider = (e: number) => { mpvPlayer?.volume(e); remote?.updateVolume(e); - setVolume(e); + // setVolume(e); }; const handleVolumeSliderState = (e: number) => { remote?.updateVolume(e); - setVolume(e); + // setVolume(e); }; const handleVolumeDown = useCallback(() => { const volumeToSet = calculateVolumeDown(volume, volumeWheelStep); mpvPlayer?.volume(volumeToSet); remote?.updateVolume(volumeToSet); - setVolume(volumeToSet); - }, [setVolume, volume, volumeWheelStep]); + // setVolume(volumeToSet); + }, [volume, volumeWheelStep]); const handleVolumeUp = useCallback(() => { const volumeToSet = calculateVolumeUp(volume, volumeWheelStep); mpvPlayer?.volume(volumeToSet); remote?.updateVolume(volumeToSet); - setVolume(volumeToSet); - }, [setVolume, volume, volumeWheelStep]); + // setVolume(volumeToSet); + }, [volume, volumeWheelStep]); const handleVolumeWheel = useCallback( (e: WheelEvent) => { @@ -109,15 +100,15 @@ export const useRightControls = () => { mpvPlayer?.volume(volumeToSet); remote?.updateVolume(volumeToSet); - setVolume(volumeToSet); + // setVolume(volumeToSet); }, - [setVolume, volume, volumeWheelStep], + [volume, volumeWheelStep], ); const handleMute = useCallback(() => { - setMuted(!muted); + // setMuted(!muted); mpvPlayer?.mute(!muted); - }, [muted, setMuted]); + }, [muted]); useEffect(() => { if (isElectron()) { diff --git a/src/renderer/features/player/mutations/scrobble-mutation.ts b/src/renderer/features/player/mutations/scrobble-mutation.ts index a96efa80a..350feaa79 100644 --- a/src/renderer/features/player/mutations/scrobble-mutation.ts +++ b/src/renderer/features/player/mutations/scrobble-mutation.ts @@ -3,12 +3,11 @@ import { AxiosError } from 'axios'; import { api } from '/@/renderer/api'; import { MutationOptions } from '/@/renderer/lib/react-query'; -import { useIncrementQueuePlayCount } from '/@/renderer/store'; import { usePlayEvent } from '/@/renderer/store/event.store'; import { ScrobbleArgs, ScrobbleResponse } from '/@/shared/types/domain-types'; export const useSendScrobble = (options?: MutationOptions) => { - const incrementPlayCount = useIncrementQueuePlayCount(); + // const incrementPlayCount = useIncrementQueuePlayCount(); const sendPlayEvent = usePlayEvent(); return useMutation({ @@ -21,7 +20,7 @@ export const useSendScrobble = (options?: MutationOptions) => { onSuccess: (_data, variables) => { // Manually increment the play count for the song in the queue if scrobble was submitted if (variables.query.submission) { - incrementPlayCount([variables.query.id]); + // incrementPlayCount([variables.query.id]); sendPlayEvent(variables.query.id); } }, diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx index 4e355f129..c815a6f8a 100644 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx @@ -29,8 +29,8 @@ import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-a import { useAppFocus } from '/@/renderer/hooks'; import { useCurrentServer, - useCurrentSong, - useCurrentStatus, + usePlayerSong, + usePlayerStatus, usePlaylistDetailStore, usePlaylistDetailTablePagination, useSetPlaylistDetailTable, @@ -58,9 +58,9 @@ interface PlaylistDetailContentProps { export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetailContentProps) => { const { playlistId } = useParams() as { playlistId: string }; const queryClient = useQueryClient(); - const status = useCurrentStatus(); + const status = usePlayerStatus(); const isFocused = useAppFocus(); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const server = useCurrentServer(); const page = usePlaylistDetailStore(); const filters: PlaylistSongListQueryClientSide = useMemo(() => { diff --git a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx index 1f7f6d933..60f2958fd 100644 --- a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx @@ -7,7 +7,7 @@ import { } from '/@/renderer/features/settings/components/settings-section'; import { useHotkeySettings, usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store'; import { Switch } from '/@/shared/components/switch/switch'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; const localSettings = isElectron() ? window.api.localSettings : null; const isWindows = isElectron() ? window.api.utils.isWindows() : false; @@ -25,9 +25,7 @@ export const WindowHotkeySettings = () => { defaultChecked={settings.globalMediaHotkeys} disabled={ !isElectron() || - (enableWindowsMediaSession && - isWindows && - playbackType === PlaybackType.WEB) + (enableWindowsMediaSession && isWindows && playbackType === PlayerType.WEB) } onChange={(e) => { setSettings({ diff --git a/src/renderer/features/settings/components/playback/audio-settings.tsx b/src/renderer/features/settings/components/playback/audio-settings.tsx index 6bb2be791..8c41b0973 100644 --- a/src/renderer/features/settings/components/playback/audio-settings.tsx +++ b/src/renderer/features/settings/components/playback/audio-settings.tsx @@ -6,14 +6,14 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; -import { useCurrentStatus, usePlayerStore } from '/@/renderer/store'; +import { usePlayerStatus, usePlayerStore } from '/@/renderer/store'; import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { setQueue } from '/@/renderer/utils/set-transcoded-queue-data'; import { Select } from '/@/shared/components/select/select'; import { Slider } from '/@/shared/components/slider/slider'; import { Switch } from '/@/shared/components/switch/switch'; import { toast } from '/@/shared/components/toast/toast'; -import { CrossfadeStyle, PlaybackStyle, PlaybackType, PlayerStatus } from '/@/shared/types/types'; +import { CrossfadeStyle, PlayerStyle, PlayerType, PlayerStatus } from '/@/shared/types/types'; const ipc = isElectron() ? window.api.ipc : null; @@ -26,7 +26,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); - const status = useCurrentStatus(); + const status = usePlayerStatus(); const [audioDevices, setAudioDevices] = useState<{ label: string; value: string }[]>([]); @@ -43,7 +43,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => ); }; - if (settings.type === PlaybackType.WEB) { + if (settings.type === PlayerType.WEB) { getAudioDevices(); } }, [settings.type, t]); @@ -56,16 +56,16 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => { disabled: !isElectron(), label: 'MPV', - value: PlaybackType.LOCAL, + value: PlayerType.LOCAL, }, - { label: 'Web', value: PlaybackType.WEB }, + { label: 'Web', value: PlayerType.WEB }, ]} defaultValue={settings.type} disabled={status === PlayerStatus.PLAYING} onChange={(e) => { - setSettings({ playback: { ...settings, type: e as PlaybackType } }); + setSettings({ playback: { ...settings, type: e as PlayerType } }); ipc?.send('settings-set', { property: 'playbackType', value: e }); - if (isElectron() && e === PlaybackType.LOCAL) { + if (isElectron() && e === PlayerType.LOCAL) { const queueData = usePlayerStore.getState().actions.getPlayerData(); setQueue(queueData); } @@ -89,7 +89,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => clearable data={audioDevices} defaultValue={settings.audioDeviceId} - disabled={settings.type !== PlaybackType.WEB} + disabled={settings.type !== PlayerType.WEB} onChange={(e) => setSettings({ playback: { ...settings, audioDeviceId: e } })} /> ), @@ -97,7 +97,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: !isElectron() || settings.type !== PlaybackType.WEB, + isHidden: !isElectron() || settings.type !== PlayerType.WEB, title: t('setting.audioDevice', { postProcess: 'sentenceCase' }), }, { @@ -109,20 +109,20 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'optionNormal', postProcess: 'titleCase', }), - value: PlaybackStyle.GAPLESS, + value: PlayerStyle.GAPLESS, }, { label: t('setting.playbackStyle', { context: 'optionCrossFade', postProcess: 'titleCase', }), - value: PlaybackStyle.CROSSFADE, + value: PlayerStyle.CROSSFADE, }, ]} defaultValue={settings.style} - disabled={settings.type !== PlaybackType.WEB || status === PlayerStatus.PLAYING} + disabled={settings.type !== PlayerType.WEB || status === PlayerStatus.PLAYING} onChange={(e) => - setSettings({ playback: { ...settings, style: e as PlaybackStyle } }) + setSettings({ playback: { ...settings, style: e as PlayerStyle } }) } /> ), @@ -130,7 +130,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.WEB, + isHidden: settings.type !== PlayerType.WEB, note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined, title: t('setting.playbackStyle', { context: 'description', @@ -152,7 +152,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.WEB, + isHidden: settings.type !== PlayerType.WEB, note: t('common.restartRequired', { postProcess: 'sentenceCase' }), title: t('setting.webAudio', { postProcess: 'sentenceCase', @@ -173,7 +173,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.WEB, + isHidden: settings.type !== PlayerType.WEB, title: t('setting.preservePitch', { postProcess: 'sentenceCase', }), @@ -183,8 +183,8 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.WEB, + isHidden: settings.type !== PlayerType.WEB, note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined, title: t('setting.crossfadeDuration', { postProcess: 'sentenceCase', @@ -224,8 +224,8 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => ]} defaultValue={settings.crossfadeStyle} disabled={ - settings.type !== PlaybackType.WEB || - settings.style !== PlaybackStyle.CROSSFADE || + settings.type !== PlayerType.WEB || + settings.style !== PlayerStyle.CROSSFADE || status === PlayerStatus.PLAYING } onChange={(e) => { @@ -241,7 +241,7 @@ export const AudioSettings = ({ hasFancyAudio }: { hasFancyAudio: boolean }) => context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.WEB, + isHidden: settings.type !== PlayerType.WEB, note: status === PlayerStatus.PLAYING ? 'Player must be paused' : undefined, title: t('setting.crossfadeStyle', { postProcess: 'sentenceCase' }), }, diff --git a/src/renderer/features/settings/components/playback/media-session-settings.tsx b/src/renderer/features/settings/components/playback/media-session-settings.tsx index faab7d694..4967f02c1 100644 --- a/src/renderer/features/settings/components/playback/media-session-settings.tsx +++ b/src/renderer/features/settings/components/playback/media-session-settings.tsx @@ -7,7 +7,7 @@ import { } from '/@/renderer/features/settings/components/settings-section'; import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { Switch } from '/@/shared/components/switch/switch'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; const isWindows = isElectron() ? window.api.utils.isWindows() : null; const isDesktop = isElectron(); @@ -30,7 +30,7 @@ export const MediaSessionSettings = () => { ), diff --git a/src/renderer/features/settings/components/playback/mpv-settings.tsx b/src/renderer/features/settings/components/playback/mpv-settings.tsx index 354e07d79..1bf0fd1b8 100644 --- a/src/renderer/features/settings/components/playback/mpv-settings.tsx +++ b/src/renderer/features/settings/components/playback/mpv-settings.tsx @@ -6,11 +6,9 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; -import { usePlayerControls, usePlayerStore, useQueueControls } from '/@/renderer/store'; import { SettingsState, usePlaybackSettings, - useSettingsStore, useSettingsStoreActions, } from '/@/renderer/store/settings.store'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; @@ -22,7 +20,7 @@ import { Switch } from '/@/shared/components/switch/switch'; import { TextInput } from '/@/shared/components/text-input/text-input'; import { Text } from '/@/shared/components/text/text'; import { Textarea } from '/@/shared/components/textarea/textarea'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; const localSettings = isElectron() ? window.api.localSettings : null; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -74,8 +72,8 @@ export const MpvSettings = () => { const { t } = useTranslation(); const settings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); - const { pause } = usePlayerControls(); - const { clearQueue } = useQueueControls(); + // const { pause } = usePlayerControls(); + // const { clearQueue } = useQueueControls(); const [mpvPath, setMpvPath] = useState( (localSettings?.get('mpv_path') as string | undefined) || '', @@ -129,21 +127,21 @@ export const MpvSettings = () => { mpvPlayer?.setProperties(mpvSetting); }; - const handleReloadMpv = () => { - pause(); - clearQueue(); + // const handleReloadMpv = () => { + // pause(); + // clearQueue(); - const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; - const properties: Record = { - speed: usePlayerStore.getState().speed, - ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), - }; - mpvPlayer?.restart({ - binaryPath: mpvPath || undefined, - extraParameters, - properties, - }); - }; + // const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters; + // const properties: Record = { + // speed: usePlayerStore.getState().speed, + // ...getMpvProperties(useSettingsStore.getState().playback.mpvProperties), + // }; + // mpvPlayer?.restart({ + // binaryPath: mpvPath || undefined, + // extraParameters, + // properties, + // }); + // }; const handleSetExtraParameters = (data: string[]) => { setSettings({ @@ -160,7 +158,7 @@ export const MpvSettings = () => { { context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.LOCAL, + isHidden: settings.type !== PlayerType.LOCAL, note: 'Restart required', title: t('setting.mpvExecutablePath', { postProcess: 'sentenceCase' }), }, @@ -235,7 +233,7 @@ export const MpvSettings = () => { ), - isHidden: settings.type !== PlaybackType.LOCAL, + isHidden: settings.type !== PlayerType.LOCAL, note: t('common.restartRequired', { postProcess: 'sentenceCase', }), @@ -268,7 +266,7 @@ export const MpvSettings = () => { context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.LOCAL, + isHidden: settings.type !== PlayerType.LOCAL, title: t('setting.gaplessAudio', { postProcess: 'sentenceCase' }), }, { @@ -311,7 +309,7 @@ export const MpvSettings = () => { context: 'description', postProcess: 'sentenceCase', }), - isHidden: settings.type !== PlaybackType.LOCAL, + isHidden: settings.type !== PlayerType.LOCAL, title: t('setting.audioExclusiveMode', { postProcess: 'sentenceCase' }), }, ]; diff --git a/src/renderer/features/settings/components/playback/playback-tab.tsx b/src/renderer/features/settings/components/playback/playback-tab.tsx index 9effec07f..54b63198f 100644 --- a/src/renderer/features/settings/components/playback/playback-tab.tsx +++ b/src/renderer/features/settings/components/playback/playback-tab.tsx @@ -8,7 +8,7 @@ import { ScrobbleSettings } from '/@/renderer/features/settings/components/playb import { TranscodeSettings } from '/@/renderer/features/settings/components/playback/transcode-settings'; import { useSettingsStore } from '/@/renderer/store'; import { Stack } from '/@/shared/components/stack/stack'; -import { PlaybackType } from '/@/shared/types/types'; +import { PlayerType } from '/@/shared/types/types'; const MpvSettings = lazy(() => import('/@/renderer/features/settings/components/playback/mpv-settings').then((module) => { @@ -22,7 +22,7 @@ export const PlaybackTab = () => { const hasFancyAudio = useMemo(() => { return ( - (isElectron() && audioType === PlaybackType.LOCAL) || + (isElectron() && audioType === PlayerType.LOCAL) || (useWebAudio && 'AudioContext' in window) ); }, [audioType, useWebAudio]); diff --git a/src/renderer/features/shared/mutations/create-favorite-mutation.ts b/src/renderer/features/shared/mutations/create-favorite-mutation.ts index 5defd8ff1..1be383204 100644 --- a/src/renderer/features/shared/mutations/create-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/create-favorite-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; +import { useSetAlbumListItemDataById } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumArtistDetailResponse, @@ -21,7 +21,7 @@ export const useCreateFavorite = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); - const setQueueFavorite = useSetQueueFavorite(); + // const setQueueFavorite = useSetQueueFavorite(); const setFavoriteEvent = useFavoriteEvent(); return useMutation({ @@ -46,7 +46,7 @@ export const useCreateFavorite = (args: MutationHookArgs) => { if (variables.query.type === LibraryItem.SONG) { remote?.updateFavorite(true, serverId, variables.query.id); - setQueueFavorite(variables.query.id, true); + // setQueueFavorite(variables.query.id, true); setFavoriteEvent(variables.query.id, true); } diff --git a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts index 58431b579..34c630237 100644 --- a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { useSetAlbumListItemDataById, useSetQueueFavorite } from '/@/renderer/store'; +import { useSetAlbumListItemDataById } from '/@/renderer/store'; import { useFavoriteEvent } from '/@/renderer/store/event.store'; import { AlbumArtistDetailResponse, @@ -21,7 +21,7 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); - const setQueueFavorite = useSetQueueFavorite(); + // const setQueueFavorite = useSetQueueFavorite(); const setFavoriteEvent = useFavoriteEvent(); return useMutation({ @@ -44,7 +44,7 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { if (variables.query.type === LibraryItem.SONG) { remote?.updateFavorite(false, serverId, variables.query.id); - setQueueFavorite(variables.query.id, false); + // setQueueFavorite(variables.query.id, false); setFavoriteEvent(variables.query.id, false); } diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index a5fab7ecf..3a7fda56b 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -5,7 +5,7 @@ import isElectron from 'is-electron'; import { api } from '/@/renderer/api'; import { queryKeys } from '/@/renderer/api/query-keys'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; -import { useSetAlbumListItemDataById, useSetQueueRating } from '/@/renderer/store'; +import { useSetAlbumListItemDataById } from '/@/renderer/store'; import { useRatingEvent } from '/@/renderer/store/event.store'; import { Album, @@ -24,7 +24,7 @@ export const useSetRating = (args: MutationHookArgs) => { const { options } = args || {}; const queryClient = useQueryClient(); const setAlbumListData = useSetAlbumListItemDataById(); - const setQueueRating = useSetQueueRating(); + // const setQueueRating = useSetQueueRating(); const setRatingEvent = useRatingEvent(); return useMutation< @@ -46,7 +46,7 @@ export const useSetRating = (args: MutationHookArgs) => { setAlbumListData(item.id, { userRating: item.userRating }); break; case LibraryItem.SONG: - setQueueRating([item.id], item.userRating); + // setQueueRating([item.id], item.userRating); setRatingEvent([item.id], item.userRating); break; } @@ -67,7 +67,7 @@ export const useSetRating = (args: MutationHookArgs) => { } if (songIds.length > 0) { - setQueueRating(songIds, variables.query.rating); + // setQueueRating(songIds, variables.query.rating); setRatingEvent(songIds, variables.query.rating); } diff --git a/src/renderer/features/sidebar/components/sidebar.tsx b/src/renderer/features/sidebar/components/sidebar.tsx index 229200625..9d96d0225 100644 --- a/src/renderer/features/sidebar/components/sidebar.tsx +++ b/src/renderer/features/sidebar/components/sidebar.tsx @@ -17,7 +17,7 @@ import { } from '/@/renderer/features/sidebar/components/sidebar-playlist-list'; import { useAppStoreActions, - useCurrentSong, + usePlayerSong, useFullScreenPlayerStore, useSetFullScreenPlayerStore, useSidebarStore, @@ -42,7 +42,7 @@ export const Sidebar = () => { const sidebar = useSidebarStore(); const { setSideBar } = useAppStoreActions(); const { sidebarPlaylistList } = useGeneralSettings(); - const currentSong = useCurrentSong(); + const currentSong = usePlayerSong(); const translatedSidebarItemMap = useMemo( () => ({ diff --git a/src/renderer/features/similar-songs/components/similar-songs-list.tsx b/src/renderer/features/similar-songs/components/similar-songs-list.tsx index 905481e86..258a20e74 100644 --- a/src/renderer/features/similar-songs/components/similar-songs-list.tsx +++ b/src/renderer/features/similar-songs/components/similar-songs-list.tsx @@ -9,7 +9,6 @@ import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-tabl import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { useHandleTableContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu'; -import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store'; import { Spinner } from '/@/shared/components/spinner/spinner'; @@ -24,8 +23,6 @@ export type SimilarSongsListProps = { export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListProps) => { const tableRef = useRef | null>(null); const tableConfig = useTableSettings(fullScreen ? 'fullScreen' : 'songs'); - const handlePlayQueueAdd = useHandlePlayQueueAdd(); - const playButtonBehavior = usePlayButtonBehavior(); const songQuery = useQuery( songsQueries.similar({ @@ -51,11 +48,11 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { if (!e.data || !songQuery.data) return; - handlePlayQueueAdd?.({ - byData: songQuery.data, - initialSongId: e.data.id, - playType: playButtonBehavior, - }); + // handlePlayQueueAdd?.({ + // byData: songQuery.data, + // initialSongId: e.data.id, + // playType: playButtonBehavior, + // }); }; return songQuery.isLoading ? ( diff --git a/src/renderer/features/songs/components/song-list-table-view.tsx b/src/renderer/features/songs/components/song-list-table-view.tsx index 2b5a0a773..bce2c448d 100644 --- a/src/renderer/features/songs/components/song-list-table-view.tsx +++ b/src/renderer/features/songs/components/song-list-table-view.tsx @@ -12,8 +12,8 @@ import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/conte import { useAppFocus } from '/@/renderer/hooks'; import { useCurrentServer, - useCurrentSong, - useCurrentStatus, + usePlayerSong, + usePlayerStatus, usePlayButtonBehavior, } from '/@/renderer/store'; import { LibraryItem, QueueSong, SongListQuery } from '/@/shared/types/domain-types'; @@ -27,8 +27,8 @@ export const SongListTableView = ({ itemCount, tableRef }: SongListTableViewProp const server = useCurrentServer(); const { customFilters, handlePlay, id, pageKey } = useListContext(); const isFocused = useAppFocus(); - const currentSong = useCurrentSong(); - const status = useCurrentStatus(); + const currentSong = usePlayerSong(); + const status = usePlayerStatus(); const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); diff --git a/src/renderer/layouts/default-layout.tsx b/src/renderer/layouts/default-layout.tsx index 4c0079b19..2d74b844d 100644 --- a/src/renderer/layouts/default-layout.tsx +++ b/src/renderer/layouts/default-layout.tsx @@ -1,22 +1,16 @@ import { HotkeyItem, useHotkeys } from '@mantine/hooks'; import clsx from 'clsx'; import isElectron from 'is-electron'; -import { lazy, useMemo } from 'react'; +import { lazy } from 'react'; import { useNavigate } from 'react-router'; import styles from './default-layout.module.css'; -import { ContextMenuProvider } from '/@/renderer/features/context-menu/context-menu-provider'; import { CommandPalette } from '/@/renderer/features/search/components/command-palette'; import { MainContent } from '/@/renderer/layouts/default-layout/main-content'; import { PlayerBar } from '/@/renderer/layouts/default-layout/player-bar'; import { AppRoute } from '/@/renderer/router/routes'; -import { - useAppStore, - useCommandPalette, - useCurrentStatus, - useQueueStatus, -} from '/@/renderer/store'; +import { useAppStore, useCommandPalette } from '/@/renderer/store'; import { useGeneralSettings, useHotkeySettings, @@ -24,13 +18,13 @@ import { useSettingsStoreActions, useWindowSettings, } from '/@/renderer/store/settings.store'; -import { Platform, PlaybackType, PlayerStatus } from '/@/shared/types/types'; +import { Platform, PlayerType } from '/@/shared/types/types'; if (!isElectron()) { useSettingsStore.getState().actions.setSettings({ playback: { ...useSettingsStore.getState().playback, - type: PlaybackType.WEB, + type: PlayerType.WEB, }, }); } @@ -53,8 +47,7 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => { const localSettings = isElectron() ? window.api.localSettings : null; const settings = useGeneralSettings(); const { setSettings } = useSettingsStoreActions(); - const playerStatus = useCurrentStatus(); - const { currentSong, index, length } = useQueueStatus(); + const { privateMode } = useAppStore(); const updateZoom = (increase: number) => { @@ -83,21 +76,21 @@ export const DefaultLayout = ({ shell }: DefaultLayoutProps) => { ...(isElectron() ? zoomHotkeys : []), ]); - const title = useMemo(() => { - const statusString = playerStatus === PlayerStatus.PAUSED ? '(Paused) ' : ''; - const queueString = length ? `(${index + 1} / ${length}) ` : ''; - const privateModeString = privateMode ? '(Private mode)' : ''; - const title = `${ - length - ? `${statusString}${queueString}${currentSong?.name}${currentSong?.artistName ? ` — ${currentSong?.artistName} — Feishin` : ''}` - : 'Feishin' - }${privateMode ? ` ${privateModeString}` : ''}`; - document.title = title; - return title; - }, [currentSong?.artistName, currentSong?.name, index, length, playerStatus, privateMode]); + // const title = useMemo(() => { + // const statusString = playerStatus === PlayerStatus.PAUSED ? '(Paused) ' : ''; + // const queueString = length ? `(${index + 1} / ${length}) ` : ''; + // const privateModeString = privateMode ? '(Private mode)' : ''; + // const title = `${ + // length + // ? `${statusString}${queueString}${currentSong?.name}${currentSong?.artistName ? ` — ${currentSong?.artistName} — Feishin` : ''}` + // : 'Feishin' + // }${privateMode ? ` ${privateModeString}` : ''}`; + // document.title = title; + // return title; + // }, [currentSong?.artistName, currentSong?.name, index, length, playerStatus, privateMode]); return ( - + <>
{ })} id="default-layout" > - {windowBarStyle !== Platform.WEB && } + {windowBarStyle !== Platform.WEB && }
-
+ ); }; diff --git a/src/renderer/router/app-outlet.tsx b/src/renderer/router/app-outlet.tsx index 6418db928..a0a107329 100644 --- a/src/renderer/router/app-outlet.tsx +++ b/src/renderer/router/app-outlet.tsx @@ -4,7 +4,7 @@ import { Navigate, Outlet } from 'react-router-dom'; import { useServerAuthenticated } from '/@/renderer/hooks/use-server-authenticated'; import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useSetPlayerFallback } from '/@/renderer/store'; +import { useCurrentServer } from '/@/renderer/store'; import { Center } from '/@/shared/components/center/center'; import { Spinner } from '/@/shared/components/spinner/spinner'; import { toast } from '/@/shared/components/toast/toast'; @@ -16,7 +16,7 @@ const mpvPlayerListener = isElectron() ? window.api.mpvPlayerListener : null; export const AppOutlet = () => { const currentServer = useCurrentServer(); - const setFallback = useSetPlayerFallback(); + // const setFallback = useSetPlayerFallback(); const authState = useServerAuthenticated(); const isActionsRequired = useMemo(() => { @@ -28,20 +28,20 @@ export const AppOutlet = () => { return isActionRequired; }, [currentServer]); - useEffect(() => { - utils?.mainMessageListener((_event, data) => { - toast.show(data); - }); + // useEffect(() => { + // utils?.mainMessageListener((_event, data) => { + // toast.show(data); + // }); - mpvPlayerListener?.rendererPlayerFallback((_event, data) => { - setFallback(data); - }); + // mpvPlayerListener?.rendererPlayerFallback((_event, data) => { + // setFallback(data); + // }); - return () => { - ipc?.removeAllListeners('toast-from-main'); - ipc?.removeAllListeners('renderer-player-fallback'); - }; - }, [setFallback]); + // return () => { + // ipc?.removeAllListeners('toast-from-main'); + // ipc?.removeAllListeners('renderer-player-fallback'); + // }; + // }, [setFallback]); if (authState === AuthState.LOADING) { return ( diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 79a8ca571..7798bb726 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -10,40 +10,14 @@ import { useShallow } from 'zustand/react/shallow'; import { createSelectors } from '/@/renderer/lib/zustand'; import { shuffleInPlace } from '/@/renderer/utils/shuffle'; import { QueueSong, Song } from '/@/shared/types/domain-types'; - -export enum PlayerRepeat { - ALBUM = 'album', - ALL = 'all', - OFF = 'off', - ONE = 'one', -} - -export enum PlayerShuffle { - OFF = 'off', - TRACK = 'track', -} - -export enum PlayerStatus { - PAUSED = 'paused', - PLAYING = 'playing', -} - -export enum PlayerTransition { - CROSSFADE = 'crossfade', - GAPLESS = 'gapless', -} - -export enum PlayType { - INDEX = 'index', - LAST = 'last', - NEXT = 'next', - NOW = 'now', -} - -export enum QueueType { - DEFAULT = 'default', - PRIORITY = 'priority', -} +import { + Play, + PlayerQueueType, + PlayerRepeat, + PlayerShuffle, + PlayerStatus, + PlayerStyle, +} from '/@/shared/types/types'; export interface PlayerData { currentTrack: QueueSong | undefined; @@ -56,7 +30,7 @@ export interface PlayerData { shuffle: PlayerShuffle; speed: number; status: PlayerStatus; - transitionType: PlayerTransition; + transitionType: PlayerStyle; volume: number; }; player1: QueueSong | undefined; @@ -75,7 +49,7 @@ export interface QueueData { export type QueueGroupingProperty = keyof QueueSong; interface Actions { - addToQueueByType: (items: Song[], playType: PlayType) => void; + addToQueueByType: (items: Song[], playType: Play) => void; addToQueueByUniqueId: (items: Song[], uniqueId: string, edge: 'bottom' | 'top') => void; clearQueue: () => void; clearSelected: (items: QueueSong[]) => void; @@ -103,11 +77,11 @@ interface Actions { moveSelectedToTop: (items: QueueSong[]) => void; setCrossfadeDuration: (duration: number) => void; setProgress: (timestamp: number) => void; - setQueueType: (queueType: QueueType) => void; + setQueueType: (queueType: PlayerQueueType) => void; setRepeat: (repeat: PlayerRepeat) => void; setShuffle: (shuffle: PlayerShuffle) => void; setSpeed: (speed: number) => void; - setTransitionType: (transitionType: PlayerTransition) => void; + setTransitionType: (transitionType: PlayerStyle) => void; setVolume: (volume: number) => void; shuffle: () => void; shuffleAll: () => void; @@ -125,7 +99,7 @@ interface State { index: number; muted: boolean; playerNum: 1 | 2; - queueType: QueueType; + queueType: PlayerQueueType; repeat: PlayerRepeat; seekToTimestamp: string; shuffle: PlayerShuffle; @@ -134,7 +108,7 @@ interface State { stepBackward: number; stepForward: number; timestamp: number; - transitionType: PlayerTransition; + transitionType: PlayerStyle; volume: number; }; queue: QueueData; @@ -150,9 +124,9 @@ export const usePlayerStoreBase = create()( const queueType = getQueueType(); switch (queueType) { - case QueueType.DEFAULT: { + case PlayerQueueType.DEFAULT: { switch (playType) { - case PlayType.LAST: { + case Play.LAST: { set((state) => { const currentIndex = state.player.index; @@ -171,7 +145,7 @@ export const usePlayerStoreBase = create()( }); break; } - case PlayType.NEXT: { + case Play.NEXT: { set((state) => { const currentIndex = state.player.index; @@ -194,7 +168,7 @@ export const usePlayerStoreBase = create()( }); break; } - case PlayType.NOW: { + case Play.NOW: { set((state) => { state.queue.default = []; state.player.index = 0; @@ -214,9 +188,9 @@ export const usePlayerStoreBase = create()( } break; } - case QueueType.PRIORITY: { + case PlayerQueueType.PRIORITY: { switch (playType) { - case PlayType.LAST: { + case Play.LAST: { set((state) => { state.queue.priority = [ ...state.queue.priority, @@ -230,7 +204,7 @@ export const usePlayerStoreBase = create()( }); break; } - case PlayType.NEXT: { + case Play.NEXT: { set((state) => { const currentIndex = state.player.index; const isInPriority = @@ -260,7 +234,7 @@ export const usePlayerStoreBase = create()( }); break; } - case PlayType.NOW: { + case Play.NOW: { set((state) => { state.queue.default = []; state.player.status = PlayerStatus.PLAYING; @@ -337,7 +311,7 @@ export const usePlayerStoreBase = create()( const queueType = getQueueType(); set((state) => { - if (queueType === QueueType.DEFAULT) { + if (queueType === PlayerQueueType.DEFAULT) { const index = state.queue.default.findIndex( (item) => item.uniqueId === uniqueId, ); @@ -445,7 +419,7 @@ export const usePlayerStoreBase = create()( const queue = get().getQueueOrder(); const queueType = getQueueType(); - if (!groupBy || queueType === QueueType.PRIORITY) { + if (!groupBy || queueType === PlayerQueueType.PRIORITY) { return queue; } @@ -483,7 +457,7 @@ export const usePlayerStoreBase = create()( getQueueOrder: () => { const queueType = getQueueType(); - if (queueType === QueueType.PRIORITY) { + if (queueType === PlayerQueueType.PRIORITY) { const defaultQueue = get().queue.default; const priorityQueue = get().queue.priority; @@ -645,7 +619,7 @@ export const usePlayerStoreBase = create()( set((state) => { const uniqueIdMap = new Map(items.map((item) => [item.uniqueId, item])); - if (queueType == QueueType.DEFAULT) { + if (queueType == PlayerQueueType.DEFAULT) { // Find the index of the drop target const index = state.queue.default.findIndex( (item) => item.uniqueId === uniqueId, @@ -738,7 +712,7 @@ export const usePlayerStoreBase = create()( set((state) => { const uniqueIds = items.map((item) => item.uniqueId); - if (state.player.queueType === QueueType.PRIORITY) { + if (state.player.queueType === PlayerQueueType.PRIORITY) { const priorityFiltered = state.queue.priority.filter( (item) => !uniqueIds.includes(item.uniqueId), ); @@ -779,7 +753,7 @@ export const usePlayerStoreBase = create()( const uniqueIds = items.map((item) => item.uniqueId); - if (queueType === QueueType.DEFAULT) { + if (queueType === PlayerQueueType.DEFAULT) { const currentIndex = state.player.index; const filtered = state.queue.default.filter( (item) => !uniqueIds.includes(item.uniqueId), @@ -861,7 +835,7 @@ export const usePlayerStoreBase = create()( set((state) => { const uniqueIds = items.map((item) => item.uniqueId); - if (state.player.queueType === QueueType.PRIORITY) { + if (state.player.queueType === PlayerQueueType.PRIORITY) { const priorityFiltered = state.queue.priority.filter( (item) => !uniqueIds.includes(item.uniqueId), ); @@ -896,16 +870,16 @@ export const usePlayerStoreBase = create()( index: -1, muted: false, playerNum: 1, - queueType: QueueType.DEFAULT, - repeat: PlayerRepeat.OFF, + queueType: PlayerQueueType.DEFAULT, + repeat: PlayerRepeat.NONE, seekToTimestamp: uniqueSeekToTimestamp(0), - shuffle: PlayerShuffle.OFF, + shuffle: PlayerShuffle.NONE, speed: 1, status: PlayerStatus.PAUSED, stepBackward: 10, stepForward: 10, timestamp: 0, - transitionType: PlayerTransition.GAPLESS, + transitionType: PlayerStyle.GAPLESS, volume: 30, }, queue: { @@ -924,10 +898,10 @@ export const usePlayerStoreBase = create()( state.player.timestamp = timestamp; }); }, - setQueueType: (queueType: QueueType) => { + setQueueType: (queueType: PlayerQueueType) => { set((state) => { // From default -> priority, move all items from default to priority - if (queueType === QueueType.PRIORITY) { + if (queueType === PlayerQueueType.PRIORITY) { state.queue.priority = [ ...state.queue.default, ...state.queue.priority, @@ -960,7 +934,7 @@ export const usePlayerStoreBase = create()( state.player.speed = normalizedSpeed; }); }, - setTransitionType: (transitionType: PlayerTransition) => { + setTransitionType: (transitionType: PlayerStyle) => { set((state) => { state.player.transitionType = transitionType; }); @@ -1053,7 +1027,7 @@ export const usePlayerActions = () => { ); }; -export type AddToQueueByPlayType = PlayType; +export type AddToQueueByPlayType = Play; export type AddToQueueByUniqueId = { edge: 'bottom' | 'left' | 'right' | 'top' | null; @@ -1254,16 +1228,6 @@ export const usePlayerProperties = () => { ); }; -export const useCurrentTrack = () => { - return usePlayerStoreBase( - useShallow((state) => { - const queue = state.getQueue(); - const index = state.player.index; - return { index, length: queue.items.length, track: queue.items[index] }; - }), - ); -}; - export const usePlayerProgress = () => { return usePlayerStoreBase((state) => state.player.timestamp); }; @@ -1321,32 +1285,67 @@ export const updateQueueFavorites = (ids: string[], favorite: boolean) => { }); }; -export function usePlayerMuted() { +export const usePlayerMuted = () => { return usePlayerStoreBase((state) => state.player.muted); -} +}; -export function usePlayerQueueType() { +export const usePlayerQueueType = () => { return usePlayerStoreBase((state) => state.player.queueType); -} +}; -export function usePlayerRepeat() { +export const usePlayerRepeat = () => { return usePlayerStoreBase((state) => state.player.repeat); -} +}; -export function usePlayerShuffle() { +export const usePlayerShuffle = () => { return usePlayerStoreBase((state) => state.player.shuffle); -} +}; -export function usePlayerStatus() { +export const usePlayerStatus = () => { return usePlayerStoreBase((state) => state.player.status); -} +}; -export function usePlayerVolume() { +export const usePlayerVolume = () => { return usePlayerStoreBase((state) => state.player.volume); -} +}; + +export const usePlayerSpeed = () => { + return usePlayerStoreBase((state) => state.player.speed); +}; + +export const usePlayerTimestamp = () => { + return usePlayerStoreBase((state) => state.player.timestamp); +}; + +export const usePlayerSong = () => { + return usePlayerStoreBase((state) => { + const queue = state.getQueue(); + const index = state.player.index; + return queue.items[index]; + }); +}; + +export const usePlayerNum = () => { + return usePlayerStoreBase((state) => state.player.playerNum); +}; + +export const usePlayerQueue = () => { + return usePlayerStoreBase((state) => { + const queueType = state.player.queueType; + + switch (queueType) { + case PlayerQueueType.DEFAULT: + return state.queue.default; + case PlayerQueueType.PRIORITY: + return state.queue.priority; + default: + return state.queue.default; + } + }); +}; function getQueueType() { - const queueType: QueueType = usePlayerStore.getState().player.queueType; + const queueType: PlayerQueueType = usePlayerStore.getState().player.queueType; return queueType; } diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index e867093fa..676c39573 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -21,8 +21,8 @@ import { FontType, Platform, Play, - PlaybackStyle, - PlaybackType, + PlayerStyle, + PlayerType, TableColumn, TableType, } from '/@/shared/types/types'; @@ -258,9 +258,9 @@ const PlaybackSettingsSchema = z.object({ muted: z.boolean(), preservePitch: z.boolean(), scrobble: ScrobbleSettingsSchema, - style: z.nativeEnum(PlaybackStyle), + style: z.nativeEnum(PlayerStyle), transcode: TranscodingConfigSchema, - type: z.nativeEnum(PlaybackType), + type: z.nativeEnum(PlayerType), webAudio: z.boolean(), }); @@ -652,11 +652,11 @@ const initialState: SettingsState = { scrobbleAtDuration: 240, scrobbleAtPercentage: 75, }, - style: PlaybackStyle.GAPLESS, + style: PlayerStyle.GAPLESS, transcode: { enabled: false, }, - type: PlaybackType.WEB, + type: PlayerType.WEB, webAudio: true, }, remote: { @@ -843,7 +843,7 @@ export const useSettingsStore = createWithEqualityFn()( ...initialState, playback: { ...initialState.playback, - type: PlaybackType.WEB, + type: PlayerType.WEB, }, }); } else { @@ -970,7 +970,7 @@ export const usePlaybackType = () => const isFallback = usePlayerStore.getState().fallback; if (isFallback) { - return PlaybackType.WEB; + return PlayerType.WEB; } return state.playback.type; diff --git a/src/shared/types/types.ts b/src/shared/types/types.ts index a00d0f610..9494c3469 100644 --- a/src/shared/types/types.ts +++ b/src/shared/types/types.ts @@ -101,20 +101,16 @@ export enum FontType { } export enum Play { + INDEX = 'index', LAST = 'last', NEXT = 'next', NOW = 'now', SHUFFLE = 'shuffle', } -export enum PlaybackStyle { - CROSSFADE = 'crossfade', - GAPLESS = 'gapless', -} - -export enum PlaybackType { - LOCAL = 'local', - WEB = 'web', +export enum PlayerQueueType { + DEFAULT = 'default', + PRIORITY = 'priority', } export enum PlayerRepeat { @@ -134,6 +130,16 @@ export enum PlayerStatus { PLAYING = 'playing', } +export enum PlayerStyle { + CROSSFADE = 'crossfade', + GAPLESS = 'gapless', +} + +export enum PlayerType { + LOCAL = 'local', + WEB = 'web', +} + export enum TableColumn { ACTIONS = 'actions', ALBUM = 'album',