diff --git a/src/renderer/features/discord-rpc/use-discord-rpc.ts b/src/renderer/features/discord-rpc/use-discord-rpc.ts index 5c56e356b..2b78b291d 100644 --- a/src/renderer/features/discord-rpc/use-discord-rpc.ts +++ b/src/renderer/features/discord-rpc/use-discord-rpc.ts @@ -1,6 +1,6 @@ import { SetActivity, StatusDisplayType } from '@xhayper/discord-rpc'; 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 { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; @@ -12,6 +12,7 @@ import { useLastfmApiKey, usePlayerSong, usePlayerStore, + useSettingsStore, useTimestampStoreBase, } from '/@/renderer/store'; import { sentenceCase } from '/@/renderer/utils'; @@ -410,3 +411,21 @@ export const useDiscordRpc = () => { 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); +}; diff --git a/src/renderer/features/player/audio-player/hooks/use-main-player-listener.tsx b/src/renderer/features/player/audio-player/hooks/use-main-player-listener.tsx index f298daf9b..315a43d92 100644 --- a/src/renderer/features/player/audio-player/hooks/use-main-player-listener.tsx +++ b/src/renderer/features/player/audio-player/hooks/use-main-player-listener.tsx @@ -150,3 +150,19 @@ export const useMainPlayerListener = () => { 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 ; +}; diff --git a/src/renderer/features/player/components/audio-players.tsx b/src/renderer/features/player/components/audio-players.tsx index f94efee5b..0acad2671 100644 --- a/src/renderer/features/player/components/audio-players.tsx +++ b/src/renderer/features/player/components/audio-players.tsx @@ -3,24 +3,24 @@ import { useEffect } from 'react'; import { eventEmitter } from '/@/renderer/events/event-emitter'; import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events'; -import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc'; -import { useMainPlayerListener } from '/@/renderer/features/player/audio-player/hooks/use-main-player-listener'; +import { DiscordRpcHook } from '/@/renderer/features/discord-rpc/use-discord-rpc'; +import { MainPlayerListenerHook } from '/@/renderer/features/player/audio-player/hooks/use-main-player-listener'; import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player'; import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player'; -import { useAutoDJ } from '/@/renderer/features/player/hooks/use-auto-dj'; -import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session'; -import { useMPRIS } from '/@/renderer/features/player/hooks/use-mpris'; -import { usePlaybackHotkeys } from '/@/renderer/features/player/hooks/use-playback-hotkeys'; -import { usePowerSaveBlocker } from '/@/renderer/features/player/hooks/use-power-save-blocker'; -import { useQueueRestoreTimestamp } from '/@/renderer/features/player/hooks/use-queue-restore'; -import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble'; -import { useUpdateCurrentSong } from '/@/renderer/features/player/hooks/use-update-current-song'; +import { AutoDJHook } from '/@/renderer/features/player/hooks/use-auto-dj'; +import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-session'; +import { MPRISHook } from '/@/renderer/features/player/hooks/use-mpris'; +import { PlaybackHotkeysHook } from '/@/renderer/features/player/hooks/use-playback-hotkeys'; +import { PowerSaveBlockerHook } from '/@/renderer/features/player/hooks/use-power-save-blocker'; +import { QueueRestoreTimestampHook } from '/@/renderer/features/player/hooks/use-queue-restore'; +import { ScrobbleHook } from '/@/renderer/features/player/hooks/use-scrobble'; +import { UpdateCurrentSongHook } from '/@/renderer/features/player/hooks/use-update-current-song'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; import { RadioWebPlayer } from '/@/renderer/features/radio/components/radio-web-player'; import { + RadioAudioInstanceHook, + RadioMetadataHook, useIsRadioActive, - useRadioAudioInstance, - useRadioMetadata, } from '/@/renderer/features/radio/hooks/use-radio-player'; import { updateQueueFavorites, @@ -46,19 +46,54 @@ export const AudioPlayers = () => { } = usePlaybackSettings(); const { setWebAudio, webAudio: audioContext } = useWebAudio(); - useScrobble(); - usePowerSaveBlocker(); - useDiscordRpc(); - useMPRIS(); - useMainPlayerListener(); - useMediaSession(); - usePlaybackHotkeys(); - useAutoDJ(); - useQueueRestoreTimestamp(); - useUpdateCurrentSong(); + return ( + <> + + + + + + + + + + + + + + + ); +}; - useRadioAudioInstance(); - useRadioMetadata(); +const AudioPlayersContent = ({ + audioContext, + audioDeviceId, + audioSampleRateHz, + playbackType, + resetSampleRate, + serverId, + setWebAudio, + webAudio, +}: { + audioContext: ReturnType['webAudio']; + audioDeviceId: null | string | undefined; + audioSampleRateHz: number | undefined; + playbackType: PlayerType; + resetSampleRate: ReturnType['resetSampleRate']; + serverId: null | string; + setWebAudio: ReturnType['setWebAudio']; + webAudio: boolean; +}) => { + const isRadioActive = useIsRadioActive(); useEffect(() => { if (webAudio && 'AudioContext' in window) { @@ -143,8 +178,6 @@ export const AudioPlayers = () => { }; }, [serverId]); - const isRadioActive = useIsRadioActive(); - if (isRadioActive && playbackType === PlayerType.LOCAL) { return ; } diff --git a/src/renderer/features/player/hooks/use-auto-dj.ts b/src/renderer/features/player/hooks/use-auto-dj.ts index 892233945..864142d52 100644 --- a/src/renderer/features/player/hooks/use-auto-dj.ts +++ b/src/renderer/features/player/hooks/use-auto-dj.ts @@ -1,5 +1,5 @@ import { useQueryClient } from '@tanstack/react-query'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { queryKeys } from '/@/renderer/api/query-keys'; import { eventEmitter } from '/@/renderer/events/event-emitter'; @@ -13,6 +13,7 @@ import { useCurrentServerId, usePlayerStore, usePlayerStoreBase, + useSettingsStore, } from '/@/renderer/store'; import { LogCategory, logFn } from '/@/renderer/utils/logger'; import { logMsg } from '/@/renderer/utils/logger-message'; @@ -232,3 +233,18 @@ export const useAutoDJ = () => { 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); +}; diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index 5af751267..e43ac4fdd 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -1,11 +1,12 @@ 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 { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlaybackSettings, + usePlaybackType, usePlayerStore, useSettingsStore, useSkipButtons, @@ -143,3 +144,25 @@ export const useMediaSession = () => { [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); +}; diff --git a/src/renderer/features/player/hooks/use-mpris.ts b/src/renderer/features/player/hooks/use-mpris.ts index d0c06af82..86d40bbd9 100644 --- a/src/renderer/features/player/hooks/use-mpris.ts +++ b/src/renderer/features/player/hooks/use-mpris.ts @@ -1,5 +1,5 @@ import isElectron from 'is-electron'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; 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); +}; diff --git a/src/renderer/features/player/hooks/use-playback-hotkeys.ts b/src/renderer/features/player/hooks/use-playback-hotkeys.ts index a50b8c8d8..bf9932b2e 100644 --- a/src/renderer/features/player/hooks/use-playback-hotkeys.ts +++ b/src/renderer/features/player/hooks/use-playback-hotkeys.ts @@ -38,3 +38,8 @@ export const usePlaybackHotkeys = () => { useHotkeys(playbackHotkeysItems); }; + +export const PlaybackHotkeysHook = () => { + usePlaybackHotkeys(); + return null; +}; 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 6bbe8ae2d..f797b0136 100644 --- a/src/renderer/features/player/hooks/use-power-save-blocker.ts +++ b/src/renderer/features/player/hooks/use-power-save-blocker.ts @@ -1,8 +1,7 @@ import isElectron from 'is-electron'; -import { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; -import { usePlayerStatus } from '/@/renderer/store'; -import { useWindowSettings } from '/@/renderer/store'; +import { usePlayerStatus, useSettingsStore, useWindowSettings } from '/@/renderer/store'; import { PlayerStatus } from '/@/shared/types/types'; const ipc = isElectron() ? window.api.ipc : null; @@ -48,3 +47,19 @@ export const usePowerSaveBlocker = () => { }; }, [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); +}; diff --git a/src/renderer/features/player/hooks/use-queue-restore.ts b/src/renderer/features/player/hooks/use-queue-restore.ts index 361e4904f..146018903 100644 --- a/src/renderer/features/player/hooks/use-queue-restore.ts +++ b/src/renderer/features/player/hooks/use-queue-restore.ts @@ -32,6 +32,11 @@ export const useQueueRestoreTimestamp = () => { ); }; +export const QueueRestoreTimestampHook = () => { + useQueueRestoreTimestamp(); + return null; +}; + export const useSaveQueue = () => { const serverId = useCurrentServerId(); diff --git a/src/renderer/features/player/hooks/use-scrobble.ts b/src/renderer/features/player/hooks/use-scrobble.ts index 3084412a4..3e0468e57 100644 --- a/src/renderer/features/player/hooks/use-scrobble.ts +++ b/src/renderer/features/player/hooks/use-scrobble.ts @@ -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 { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; @@ -8,6 +8,7 @@ import { usePlaybackSettings, usePlayerSong, usePlayerStore, + useSettingsStore, useTimestampStoreBase, } from '/@/renderer/store'; import { LogCategory, logFn } from '/@/renderer/utils/logger'; @@ -425,3 +426,19 @@ export const useScrobble = () => { [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); +}; diff --git a/src/renderer/features/player/hooks/use-update-current-song.ts b/src/renderer/features/player/hooks/use-update-current-song.ts index f403719e3..72ff1801d 100644 --- a/src/renderer/features/player/hooks/use-update-current-song.ts +++ b/src/renderer/features/player/hooks/use-update-current-song.ts @@ -80,3 +80,8 @@ export const useUpdateCurrentSong = () => { [handleSongChange], ); }; + +export const UpdateCurrentSongHook = () => { + useUpdateCurrentSong(); + return null; +}; diff --git a/src/renderer/features/radio/hooks/use-radio-player.ts b/src/renderer/features/radio/hooks/use-radio-player.ts index 30d417825..22bc3aab4 100644 --- a/src/renderer/features/radio/hooks/use-radio-player.ts +++ b/src/renderer/features/radio/hooks/use-radio-player.ts @@ -1,6 +1,6 @@ import IcecastMetadataStats from 'icecast-metadata-stats'; import isElectron from 'is-electron'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import { createWithEqualityFn } from 'zustand/traditional'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; @@ -287,3 +287,33 @@ export const useRadioMetadata = () => { }; }, [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); +};