diff --git a/src/renderer/features/discord-rpc/use-discord-rpc.ts b/src/renderer/features/discord-rpc/use-discord-rpc.ts index b74227fe..eeac9105 100644 --- a/src/renderer/features/discord-rpc/use-discord-rpc.ts +++ b/src/renderer/features/discord-rpc/use-discord-rpc.ts @@ -1,168 +1,149 @@ import { SetActivity } from '@xhayper/discord-rpc'; import isElectron from 'is-electron'; -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { controller } from '/@/renderer/api/controller'; import { getServerById, - useCurrentSong, - useCurrentStatus, useDiscordSetttings, useGeneralSettings, usePlayerStore, } from '/@/renderer/store'; -import { ServerType } from '/@/shared/types/domain-types'; +import { QueueSong, ServerType } from '/@/shared/types/domain-types'; import { PlayerStatus } from '/@/shared/types/types'; const discordRpc = isElectron() ? window.api.discordRpc : null; export const useDiscordRpc = () => { - const intervalRef = useRef(0); const discordSettings = useDiscordSetttings(); const generalSettings = useGeneralSettings(); - const currentSong = useCurrentSong(); - const currentStatus = useCurrentStatus(); + const [lastUniqueId, setlastUniqueId] = useState(''); - const setActivity = useCallback(async () => { - if (!discordSettings.enableIdle && currentStatus === PlayerStatus.PAUSED) { - discordRpc?.clearActivity(); - return; - } + const setActivity = useCallback( + async ( + current: (number | PlayerStatus | QueueSong | undefined)[], + previous: (number | PlayerStatus | QueueSong | undefined)[], + ) => { + // No current song, or we switched to a new track and the player was paused (end of album, etc.) + if (!current[0] || (current[0] && current[2] === 'paused' && current[1] === 0)) + return discordRpc?.clearActivity(); - const song = currentSong?.id ? currentSong : null; + // Handle change detection + const song = current[0] as QueueSong; + const trackChanged = lastUniqueId !== song.uniqueId; - const currentTime = usePlayerStore.getState().current.time; + /* + 1. If we jump more then 1.2 seconds from last state, update status to match + 2. If the current song id is completely different, update status + 3. If the player state changed, update status + */ + if ( + Math.abs((current[1] as number) - (previous[1] as number)) > 1.2 || + trackChanged || + current[2] !== previous[2] + ) { + if (trackChanged) setlastUniqueId(song.uniqueId); - const now = Date.now(); - const start = currentTime ? Math.round(now - currentTime * 1000) : null; - const end = song?.duration && start ? Math.round(start + song.duration) : null; + const start = Math.round(Date.now() - (current[1] as number) * 1000); + const end = Math.round(start + song.duration); - const artists = song?.artists.map((artist) => artist.name).join(', '); + const artists = song?.artists.map((artist) => artist.name).join(', '); - const activity: SetActivity = { - details: song?.name.padEnd(2, ' ') || 'Idle', - instance: false, - largeImageKey: undefined, - largeImageText: song?.album || 'Unknown album', - smallImageKey: undefined, - smallImageText: currentStatus, - state: (artists && `By ${artists}`) || 'Unknown artist', - // I would love to use the actual type as opposed to hardcoding to 2, - // but manually installing the discord-types package appears to break things - type: discordSettings.showAsListening ? 2 : 0, - }; + const activity: SetActivity = { + details: song?.name.padEnd(2, ' ') || 'Idle', + instance: false, + largeImageKey: undefined, + largeImageText: song?.album || 'Unknown album', + smallImageKey: undefined, + smallImageText: current[2] as string, + state: (artists && `By ${artists}`) || 'Unknown artist', + // I would love to use the actual type as opposed to hardcoding to 2, + // but manually installing the discord-types package appears to break things + type: discordSettings.showAsListening ? 2 : 0, + }; - if (currentStatus === PlayerStatus.PLAYING) { - if (start && end) { - activity.startTimestamp = start; - activity.endTimestamp = end; - } - - activity.smallImageKey = 'playing'; - } else { - activity.smallImageKey = 'paused'; - } - - if (discordSettings.showServerImage && song) { - if (song.serverType === ServerType.JELLYFIN && song.imageUrl) { - activity.largeImageKey = song.imageUrl; - } else if (song.serverType === ServerType.NAVIDROME) { - const server = getServerById(song.serverId); - - try { - const info = await controller.getAlbumInfo({ - apiClientProps: { server }, - query: { id: song.albumId }, - }); - - if (info.imageUrl) { - activity.largeImageKey = info.imageUrl; + if ((current[2] as PlayerStatus) === PlayerStatus.PLAYING) { + if (start && end) { + activity.startTimestamp = start; + activity.endTimestamp = end; } - } catch { - /* empty */ + + activity.smallImageKey = 'playing'; + } else { + activity.smallImageKey = 'paused'; } + + if (discordSettings.showServerImage && song) { + if (song.serverType === ServerType.JELLYFIN && song.imageUrl) { + activity.largeImageKey = song.imageUrl; + } else if (song.serverType === ServerType.NAVIDROME) { + const server = getServerById(song.serverId); + + try { + const info = await controller.getAlbumInfo({ + apiClientProps: { server }, + query: { id: song.albumId }, + }); + + if (info.imageUrl) { + activity.largeImageKey = info.imageUrl; + } + } catch { + /* empty */ + } + } + } + + if ( + activity.largeImageKey === undefined && + generalSettings.lastfmApiKey && + song?.album && + song?.albumArtists.length + ) { + const albumInfo = await fetch( + `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&album=${encodeURIComponent(song.album)}&format=json`, + ); + + const albumInfoJson = await albumInfo.json(); + + if (albumInfoJson.album?.image?.[3]['#text']) { + activity.largeImageKey = albumInfoJson.album.image[3]['#text']; + } + } + + // Fall back to default icon if not set + if (!activity.largeImageKey) { + activity.largeImageKey = 'icon'; + } + + discordRpc?.setActivity(activity); } - } - - if ( - activity.largeImageKey === undefined && - generalSettings.lastfmApiKey && - song?.album && - song?.albumArtists.length - ) { - const albumInfo = await fetch( - `https://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=${generalSettings.lastfmApiKey}&artist=${encodeURIComponent(song.albumArtists[0].name)}&album=${encodeURIComponent(song.album)}&format=json`, - ); - - const albumInfoJson = await albumInfo.json(); - - if (albumInfoJson.album?.image?.[3]['#text']) { - activity.largeImageKey = albumInfoJson.album.image[3]['#text']; - } - } - - // Fall back to default icon if not set - if (!activity.largeImageKey) { - activity.largeImageKey = 'icon'; - } - - discordRpc?.setActivity(activity); - }, [ - currentSong, - currentStatus, - discordSettings.enableIdle, - discordSettings.showAsListening, - discordSettings.showServerImage, - generalSettings.lastfmApiKey, - ]); + }, + [ + discordSettings.showAsListening, + discordSettings.showServerImage, + generalSettings.lastfmApiKey, + lastUniqueId, + ], + ); useEffect(() => { - const initializeDiscordRpc = async () => { - discordRpc?.initialize(discordSettings.clientId); - }; - - if (discordSettings.enabled) { - initializeDiscordRpc(); - } else { - discordRpc?.quit(); - } + if (!discordSettings.enabled) return discordRpc?.quit(); + discordRpc?.initialize(discordSettings.clientId); return () => { discordRpc?.quit(); }; }, [discordSettings.clientId, discordSettings.enabled]); useEffect(() => { - if (discordSettings.enabled) { - let intervalSeconds = discordSettings.updateInterval; - if (intervalSeconds < 15) { - intervalSeconds = 15; - } - - intervalRef.current = window.setInterval(setActivity, intervalSeconds * 1000); - return () => clearInterval(intervalRef.current); - } - - return () => {}; - }, [discordSettings.enabled, discordSettings.updateInterval, setActivity]); - - // useEffect(() => { - // console.log( - // 'currentStatus, discordSettings.enableIdle', - // currentStatus, - // discordSettings.enableIdle, - // ); - - // if (discordSettings.enableIdle === false && currentStatus === PlayerStatus.PAUSED) { - // console.log('removing activity'); - // clearActivity(); - // clearInterval(intervalRef.current); - // } - // }, [ - // clearActivity, - // currentStatus, - // discordSettings.enableIdle, - // discordSettings.enabled, - // setActivity, - // ]); + if (!discordSettings.enabled) return; + const unsubSongChange = usePlayerStore.subscribe( + (state) => [state.current.song, state.current.time, state.current.status], + setActivity, + ); + return () => { + unsubSongChange(); + }; + }, [discordSettings.enabled, setActivity]); }; diff --git a/src/renderer/features/settings/components/window/discord-settings.tsx b/src/renderer/features/settings/components/window/discord-settings.tsx index e8957a75..0acb2e15 100644 --- a/src/renderer/features/settings/components/window/discord-settings.tsx +++ b/src/renderer/features/settings/components/window/discord-settings.tsx @@ -1,7 +1,7 @@ import isElectron from 'is-electron'; import { useTranslation } from 'react-i18next'; -import { NumberInput, Switch, TextInput } from '/@/renderer/components'; +import { Switch, TextInput } from '/@/renderer/components'; import { SettingOption, SettingsSection, @@ -73,58 +73,6 @@ export const DiscordSettings = () => { postProcess: 'sentenceCase', }), }, - { - control: ( - { - let value = e ? Number(e) : 0; - if (value < 15) { - value = 15; - } - - setSettings({ - discord: { - ...settings, - updateInterval: value, - }, - }); - }} - value={settings.updateInterval} - /> - ), - description: t('setting.discordUpdateInterval', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: !isElectron(), - title: t('setting.discordUpdateInterval', { - discord: 'Discord', - postProcess: 'sentenceCase', - }), - }, - { - control: ( - { - setSettings({ - discord: { - ...settings, - enableIdle: e.currentTarget.checked, - }, - }); - }} - /> - ), - description: t('setting.discordIdleStatus', { - context: 'description', - postProcess: 'sentenceCase', - }), - isHidden: !isElectron(), - title: t('setting.discordIdleStatus', { - postProcess: 'sentenceCase', - }), - }, { control: (