refactor feature hooks to be conditionally initialized

This commit is contained in:
jeffvli
2026-01-21 02:23:25 -08:00
parent 9a4a8eb742
commit f4072c183b
12 changed files with 236 additions and 35 deletions
@@ -1,6 +1,6 @@
import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc'; import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
@@ -12,6 +12,7 @@ import {
useLastfmApiKey, useLastfmApiKey,
usePlayerSong, usePlayerSong,
usePlayerStore, usePlayerStore,
useSettingsStore,
useTimestampStoreBase, useTimestampStoreBase,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { sentenceCase } from '/@/renderer/utils'; import { sentenceCase } from '/@/renderer/utils';
@@ -410,3 +411,21 @@ export const useDiscordRpc = () => {
setActivity, setActivity,
]); ]);
}; };
const DiscordRpcHookInner = () => {
useDiscordRpc();
return null;
};
export const DiscordRpcHook = () => {
const isElectronEnv = isElectron();
const isDiscordRpcEnabled = useSettingsStore((state) => state.discord.enabled);
const isPrivateMode = useAppStore((state) => state.privateMode);
const discordRpc = isElectronEnv ? window.api.discordRpc : null;
if (!isElectronEnv || !discordRpc || !isDiscordRpcEnabled || isPrivateMode) {
return null;
}
return React.createElement(DiscordRpcHookInner);
};
@@ -150,3 +150,19 @@ export const useMainPlayerListener = () => {
toggleShuffle, toggleShuffle,
]); ]);
}; };
const MainPlayerListenerHookInner = () => {
useMainPlayerListener();
return null;
};
export const MainPlayerListenerHook = () => {
const isElectronEnv = isElectron();
const mpvPlayerListener = isElectronEnv ? window.api.mpvPlayerListener : null;
if (mpvPlayerListener === null) {
return null;
}
return <MainPlayerListenerHookInner />;
};
@@ -3,24 +3,24 @@ import { useEffect } from 'react';
import { eventEmitter } from '/@/renderer/events/event-emitter'; import { eventEmitter } from '/@/renderer/events/event-emitter';
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events'; import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc'; import { DiscordRpcHook } from '/@/renderer/features/discord-rpc/use-discord-rpc';
import { useMainPlayerListener } from '/@/renderer/features/player/audio-player/hooks/use-main-player-listener'; import { MainPlayerListenerHook } from '/@/renderer/features/player/audio-player/hooks/use-main-player-listener';
import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player'; import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player';
import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player'; import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player';
import { useAutoDJ } from '/@/renderer/features/player/hooks/use-auto-dj'; import { AutoDJHook } from '/@/renderer/features/player/hooks/use-auto-dj';
import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session'; import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-session';
import { useMPRIS } from '/@/renderer/features/player/hooks/use-mpris'; import { MPRISHook } from '/@/renderer/features/player/hooks/use-mpris';
import { usePlaybackHotkeys } from '/@/renderer/features/player/hooks/use-playback-hotkeys'; import { PlaybackHotkeysHook } from '/@/renderer/features/player/hooks/use-playback-hotkeys';
import { usePowerSaveBlocker } from '/@/renderer/features/player/hooks/use-power-save-blocker'; import { PowerSaveBlockerHook } from '/@/renderer/features/player/hooks/use-power-save-blocker';
import { useQueueRestoreTimestamp } from '/@/renderer/features/player/hooks/use-queue-restore'; import { QueueRestoreTimestampHook } from '/@/renderer/features/player/hooks/use-queue-restore';
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; import { ScrobbleHook } from '/@/renderer/features/player/hooks/use-scrobble';
import { useUpdateCurrentSong } from '/@/renderer/features/player/hooks/use-update-current-song'; import { UpdateCurrentSongHook } from '/@/renderer/features/player/hooks/use-update-current-song';
import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio';
import { RadioWebPlayer } from '/@/renderer/features/radio/components/radio-web-player'; import { RadioWebPlayer } from '/@/renderer/features/radio/components/radio-web-player';
import { import {
RadioAudioInstanceHook,
RadioMetadataHook,
useIsRadioActive, useIsRadioActive,
useRadioAudioInstance,
useRadioMetadata,
} from '/@/renderer/features/radio/hooks/use-radio-player'; } from '/@/renderer/features/radio/hooks/use-radio-player';
import { import {
updateQueueFavorites, updateQueueFavorites,
@@ -46,19 +46,54 @@ export const AudioPlayers = () => {
} = usePlaybackSettings(); } = usePlaybackSettings();
const { setWebAudio, webAudio: audioContext } = useWebAudio(); const { setWebAudio, webAudio: audioContext } = useWebAudio();
useScrobble(); return (
usePowerSaveBlocker(); <>
useDiscordRpc(); <ScrobbleHook />
useMPRIS(); <PowerSaveBlockerHook />
useMainPlayerListener(); <DiscordRpcHook />
useMediaSession(); <MPRISHook />
usePlaybackHotkeys(); <MainPlayerListenerHook />
useAutoDJ(); <MediaSessionHook />
useQueueRestoreTimestamp(); <PlaybackHotkeysHook />
useUpdateCurrentSong(); <AutoDJHook />
<QueueRestoreTimestampHook />
<UpdateCurrentSongHook />
<RadioAudioInstanceHook />
<RadioMetadataHook />
<AudioPlayersContent
audioContext={audioContext}
audioDeviceId={audioDeviceId}
audioSampleRateHz={audioSampleRateHz}
playbackType={playbackType}
resetSampleRate={resetSampleRate}
serverId={serverId}
setWebAudio={setWebAudio}
webAudio={webAudio}
/>
</>
);
};
useRadioAudioInstance(); const AudioPlayersContent = ({
useRadioMetadata(); audioContext,
audioDeviceId,
audioSampleRateHz,
playbackType,
resetSampleRate,
serverId,
setWebAudio,
webAudio,
}: {
audioContext: ReturnType<typeof useWebAudio>['webAudio'];
audioDeviceId: null | string | undefined;
audioSampleRateHz: number | undefined;
playbackType: PlayerType;
resetSampleRate: ReturnType<typeof useSettingsStoreActions>['resetSampleRate'];
serverId: null | string;
setWebAudio: ReturnType<typeof useWebAudio>['setWebAudio'];
webAudio: boolean;
}) => {
const isRadioActive = useIsRadioActive();
useEffect(() => { useEffect(() => {
if (webAudio && 'AudioContext' in window) { if (webAudio && 'AudioContext' in window) {
@@ -143,8 +178,6 @@ export const AudioPlayers = () => {
}; };
}, [serverId]); }, [serverId]);
const isRadioActive = useIsRadioActive();
if (isRadioActive && playbackType === PlayerType.LOCAL) { if (isRadioActive && playbackType === PlayerType.LOCAL) {
return <MpvPlayer />; return <MpvPlayer />;
} }
@@ -1,5 +1,5 @@
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { eventEmitter } from '/@/renderer/events/event-emitter'; import { eventEmitter } from '/@/renderer/events/event-emitter';
@@ -13,6 +13,7 @@ import {
useCurrentServerId, useCurrentServerId,
usePlayerStore, usePlayerStore,
usePlayerStoreBase, usePlayerStoreBase,
useSettingsStore,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { LogCategory, logFn } from '/@/renderer/utils/logger';
import { logMsg } from '/@/renderer/utils/logger-message'; import { logMsg } from '/@/renderer/utils/logger-message';
@@ -232,3 +233,18 @@ export const useAutoDJ = () => {
settings.timing, settings.timing,
]); ]);
}; };
const AutoDJHookInner = () => {
useAutoDJ();
return null;
};
export const AutoDJHook = () => {
const isAutoDJEnabled = useSettingsStore((state) => state.autoDJ.enabled);
if (!isAutoDJEnabled) {
return null;
}
return React.createElement(AutoDJHookInner);
};
@@ -1,11 +1,12 @@
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useCallback, useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { import {
usePlaybackSettings, usePlaybackSettings,
usePlaybackType,
usePlayerStore, usePlayerStore,
useSettingsStore, useSettingsStore,
useSkipButtons, useSkipButtons,
@@ -143,3 +144,25 @@ export const useMediaSession = () => {
[isMediaSessionEnabled, mediaSession], [isMediaSessionEnabled, mediaSession],
); );
}; };
const MediaSessionHookInner = () => {
useMediaSession();
return null;
};
export const MediaSessionHook = () => {
const isElectronEnv = isElectron();
const playbackType = usePlaybackType();
const isMediaSessionEnabled = useSettingsStore((state) => state.playback.mediaSession);
// We always want to enable media session when on web
// Otherwise, only enable if it is explicitly enabled in the settings AND using the web player
const shouldUseMediaSession =
!isElectronEnv || (isMediaSessionEnabled && playbackType === PlayerType.WEB);
if (!shouldUseMediaSession) {
return null;
}
return React.createElement(MediaSessionHookInner);
};
@@ -1,5 +1,5 @@
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
@@ -119,3 +119,20 @@ export const useMPRIS = () => {
[], [],
); );
}; };
const MPRISHookInner = () => {
useMPRIS();
return null;
};
export const MPRISHook = () => {
const isElectronEnv = isElectron();
const utils = isElectronEnv ? window.api.utils : null;
const mpris = isElectronEnv && utils?.isLinux() ? window.api.mpris : null;
if (mpris === null) {
return null;
}
return React.createElement(MPRISHookInner);
};
@@ -38,3 +38,8 @@ export const usePlaybackHotkeys = () => {
useHotkeys(playbackHotkeysItems); useHotkeys(playbackHotkeysItems);
}; };
export const PlaybackHotkeysHook = () => {
usePlaybackHotkeys();
return null;
};
@@ -1,8 +1,7 @@
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { usePlayerStatus } from '/@/renderer/store'; import { usePlayerStatus, useSettingsStore, useWindowSettings } from '/@/renderer/store';
import { useWindowSettings } from '/@/renderer/store';
import { PlayerStatus } from '/@/shared/types/types'; import { PlayerStatus } from '/@/shared/types/types';
const ipc = isElectron() ? window.api.ipc : null; const ipc = isElectron() ? window.api.ipc : null;
@@ -48,3 +47,19 @@ export const usePowerSaveBlocker = () => {
}; };
}, [stopPowerSaveBlocker]); }, [stopPowerSaveBlocker]);
}; };
const PowerSaveBlockerHookInner = () => {
usePowerSaveBlocker();
return null;
};
export const PowerSaveBlockerHook = () => {
const isElectronEnv = isElectron();
const preventSleepOnPlayback = useSettingsStore((state) => state.window.preventSleepOnPlayback);
if (!isElectronEnv || !preventSleepOnPlayback) {
return null;
}
return React.createElement(PowerSaveBlockerHookInner);
};
@@ -32,6 +32,11 @@ export const useQueueRestoreTimestamp = () => {
); );
}; };
export const QueueRestoreTimestampHook = () => {
useQueueRestoreTimestamp();
return null;
};
export const useSaveQueue = () => { export const useSaveQueue = () => {
const serverId = useCurrentServerId(); const serverId = useCurrentServerId();
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
@@ -8,6 +8,7 @@ import {
usePlaybackSettings, usePlaybackSettings,
usePlayerSong, usePlayerSong,
usePlayerStore, usePlayerStore,
useSettingsStore,
useTimestampStoreBase, useTimestampStoreBase,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { LogCategory, logFn } from '/@/renderer/utils/logger';
@@ -425,3 +426,19 @@ export const useScrobble = () => {
[handleScrobbleFromSongChange, handleProgressUpdate, handleScrobbleFromSeek], [handleScrobbleFromSongChange, handleProgressUpdate, handleScrobbleFromSeek],
); );
}; };
const ScrobbleHookInner = () => {
useScrobble();
return null;
};
export const ScrobbleHook = () => {
const isScrobbleEnabled = useSettingsStore((state) => state.playback.scrobble.enabled);
const privateMode = useAppStore((state) => state.privateMode);
if (!isScrobbleEnabled || privateMode) {
return null;
}
return React.createElement(ScrobbleHookInner);
};
@@ -80,3 +80,8 @@ export const useUpdateCurrentSong = () => {
[handleSongChange], [handleSongChange],
); );
}; };
export const UpdateCurrentSongHook = () => {
useUpdateCurrentSong();
return null;
};
@@ -1,6 +1,6 @@
import IcecastMetadataStats from 'icecast-metadata-stats'; import IcecastMetadataStats from 'icecast-metadata-stats';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useEffect } from 'react'; import React, { useEffect } from 'react';
import { createWithEqualityFn } from 'zustand/traditional'; import { createWithEqualityFn } from 'zustand/traditional';
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
@@ -287,3 +287,33 @@ export const useRadioMetadata = () => {
}; };
}, [currentStreamUrl, setMetadata, isUsingMpv]); }, [currentStreamUrl, setMetadata, isUsingMpv]);
}; };
const RadioAudioInstanceHookInner = () => {
useRadioAudioInstance();
return null;
};
export const RadioAudioInstanceHook = () => {
const isRadioActive = useIsRadioActive();
if (!isRadioActive) {
return null;
}
return React.createElement(RadioAudioInstanceHookInner);
};
const RadioMetadataHookInner = () => {
useRadioMetadata();
return null;
};
export const RadioMetadataHook = () => {
const isRadioActive = useIsRadioActive();
if (!isRadioActive) {
return null;
}
return React.createElement(RadioMetadataHookInner);
};