From bd5cd672ef9bc528c879fc4765ad9b257604843a Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 8 Jul 2026 19:56:47 -0700 Subject: [PATCH] refactor player timestamp hook with fixed interval --- .../player/components/playerbar-slider.tsx | 3 +-- .../player/components/scrobble-status.tsx | 13 ++++++++--- src/renderer/store/scrobble-debug.store.ts | 23 +++++++++++++++++++ src/renderer/store/timestamp.store.ts | 23 ++++++++++++++++++- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/renderer/features/player/components/playerbar-slider.tsx b/src/renderer/features/player/components/playerbar-slider.tsx index fa5198a50..096adcd6d 100644 --- a/src/renderer/features/player/components/playerbar-slider.tsx +++ b/src/renderer/features/player/components/playerbar-slider.tsx @@ -32,7 +32,6 @@ export const PlayerbarSlider = () => { const formattedDuration = formatDuration(songDuration * 1000 || 0); const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0); - const formattedTime = formatDuration(currentTime * 1000 || 0); const showTimeRemaining = useAppStore((state) => state.showTimeRemaining); const { setShowTimeRemaining } = useAppStoreActions(); @@ -43,7 +42,7 @@ export const PlayerbarSlider = () => { <>
- +
{isWaveform ? ( diff --git a/src/renderer/features/player/components/scrobble-status.tsx b/src/renderer/features/player/components/scrobble-status.tsx index 85faed6a1..1ca1d834b 100644 --- a/src/renderer/features/player/components/scrobble-status.tsx +++ b/src/renderer/features/player/components/scrobble-status.tsx @@ -1,10 +1,16 @@ +import formatDuration from 'format-duration'; import { useTranslation } from 'react-i18next'; import { invokeScrobbleForceSubmit, invokeScrobbleResetListenedState, } from '/@/renderer/features/player/hooks/use-scrobble'; -import { useAppStore, useScrobbleDebugStore, useSettingsStore } from '/@/renderer/store'; +import { + useAppStore, + usePlayerTimestamp, + useScrobbleDebugSnapshot, + useSettingsStore, +} from '/@/renderer/store'; import { Button } from '/@/shared/components/button/button'; import { Group } from '/@/shared/components/group/group'; import { HoverCard } from '/@/shared/components/hover-card/hover-card'; @@ -32,11 +38,12 @@ const ScrobbleConditionProgress = ({ value }: { value: number }) => ( ); -export const ScrobbleStatus = ({ formattedTime }: { formattedTime: string }) => { +export const ScrobbleStatus = () => { const { t } = useTranslation(); const scrobbleEnabled = useSettingsStore((state) => state.playback.scrobble.enabled); const privateMode = useAppStore((state) => state.privateMode); - const snapshot = useScrobbleDebugStore((state) => state.snapshot); + const snapshot = useScrobbleDebugSnapshot(); + const formattedTime = formatDuration(usePlayerTimestamp() * 1000 || 0); const hookInactive = !scrobbleEnabled || privateMode; diff --git a/src/renderer/store/scrobble-debug.store.ts b/src/renderer/store/scrobble-debug.store.ts index e06e743c2..af23daf78 100644 --- a/src/renderer/store/scrobble-debug.store.ts +++ b/src/renderer/store/scrobble-debug.store.ts @@ -1,7 +1,10 @@ +import { useEffect, useState } from 'react'; import { createWithEqualityFn } from 'zustand/traditional'; import { PlayerStatus } from '/@/shared/types/types'; +const SCROBBLE_DEBUG_POLL_INTERVAL_MS = 1000; + export type ScrobbleDebugSnapshot = { eligibilityMet: boolean; lastListenSampleTimeSec: null | number; @@ -38,6 +41,26 @@ export const useScrobbleDebugStore = createWithEqualityFn()( snapshot: initialSnapshot, })); +export const useScrobbleDebugSnapshot = () => { + const [snapshot, setLocalSnapshot] = useState(() => useScrobbleDebugStore.getState().snapshot); + + useEffect(() => { + const syncSnapshot = () => { + const nextSnapshot = useScrobbleDebugStore.getState().snapshot; + setLocalSnapshot((prevSnapshot) => + prevSnapshot !== nextSnapshot ? nextSnapshot : prevSnapshot, + ); + }; + + syncSnapshot(); + const interval = setInterval(syncSnapshot, SCROBBLE_DEBUG_POLL_INTERVAL_MS); + + return () => clearInterval(interval); + }, []); + + return snapshot; +}; + export const publishScrobbleDebug = (partial: Partial) => { useScrobbleDebugStore.setState((state) => ({ snapshot: { ...state.snapshot, ...partial }, diff --git a/src/renderer/store/timestamp.store.ts b/src/renderer/store/timestamp.store.ts index 3b729eff0..83616ab82 100644 --- a/src/renderer/store/timestamp.store.ts +++ b/src/renderer/store/timestamp.store.ts @@ -1,7 +1,10 @@ import { del, get, set } from 'idb-keyval'; +import { useEffect, useState } from 'react'; import { persist, subscribeWithSelector } from 'zustand/middleware'; import { createWithEqualityFn } from 'zustand/traditional'; +const PLAYER_TIMESTAMP_POLL_INTERVAL_MS = 500; + interface TimestampState { setTimestamp: (timestamp: number) => void; timestamp: number; @@ -60,7 +63,25 @@ export const usePlayerProgress = () => { }; export const usePlayerTimestamp = () => { - return useTimestampStoreBase((state) => state.timestamp); + const [timestamp, setLocalTimestamp] = useState( + () => useTimestampStoreBase.getState().timestamp, + ); + + useEffect(() => { + const syncTimestamp = () => { + const nextTimestamp = useTimestampStoreBase.getState().timestamp; + setLocalTimestamp((prevTimestamp) => + prevTimestamp !== nextTimestamp ? nextTimestamp : prevTimestamp, + ); + }; + + syncTimestamp(); + const interval = setInterval(syncTimestamp, PLAYER_TIMESTAMP_POLL_INTERVAL_MS); + + return () => clearInterval(interval); + }, []); + + return timestamp; }; export const setTimestamp = (timestamp: number) => {