From 8f40894926148fe23b42692963d7677039af18f4 Mon Sep 17 00:00:00 2001 From: Tiago Simionato <57016670+TiagoSimionato@users.noreply.github.com> Date: Thu, 21 May 2026 04:16:46 -0300 Subject: [PATCH 1/4] feat: persist player timestamp (#2043) * feat: persist player timestamp --- .../player/components/audio-players.tsx | 6 +- .../player/hooks/use-queue-restore.ts | 77 ++++++++++++++++++- src/renderer/store/timestamp.store.ts | 36 +++++++-- 3 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/renderer/features/player/components/audio-players.tsx b/src/renderer/features/player/components/audio-players.tsx index cb5485e83..1ada81279 100644 --- a/src/renderer/features/player/components/audio-players.tsx +++ b/src/renderer/features/player/components/audio-players.tsx @@ -14,7 +14,10 @@ import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-se 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 { + InitialTimestampRestoreHook, + 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'; @@ -134,6 +137,7 @@ export const AudioPlayers = () => { + diff --git a/src/renderer/features/player/hooks/use-queue-restore.ts b/src/renderer/features/player/hooks/use-queue-restore.ts index 74c280e7a..9b06feebf 100644 --- a/src/renderer/features/player/hooks/use-queue-restore.ts +++ b/src/renderer/features/player/hooks/use-queue-restore.ts @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { t } from 'i18next'; -import { useCallback } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { api } from '/@/renderer/api'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; @@ -9,13 +9,18 @@ import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { setTimestamp, useCurrentServerId, + usePlayerActions, + usePlayerHydrated, + usePlayerSong, + usePlayerStatus, usePlayerStore, useTimestampStoreBase, } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; +import { PlayerStatus } from '/@/shared/types/types'; export const useQueueRestoreTimestamp = () => { - const player = usePlayerStore(); + const { mediaSeekToTimestamp } = usePlayerActions(); usePlayerEvents( { @@ -24,7 +29,7 @@ export const useQueueRestoreTimestamp = () => { setTimeout(() => { setTimestamp(position); - player.mediaSeekToTimestamp(position); + mediaSeekToTimestamp(position); }, 100); }, }, @@ -37,6 +42,72 @@ export const QueueRestoreTimestampHook = () => { return null; }; +export const useInitialTimestampRestore = () => { + const { mediaSeekToTimestamp } = usePlayerActions(); + const playerHydrated = usePlayerHydrated(); + const currentSong = usePlayerSong(); + const playerStatus = usePlayerStatus(); + const timestamp = useTimestampStoreBase((state) => state.timestamp); + + const startupRestoreInitializedRef = useRef(false); + const startupSeekArmedRef = useRef(null); + const startupSeekAppliedRef = useRef(false); + + const applyStartupSeek = useCallback(() => { + if (startupSeekAppliedRef.current) { + return; + } + + const seekTimestamp = startupSeekArmedRef.current; + if (!seekTimestamp || seekTimestamp <= 0) { + return; + } + + startupSeekAppliedRef.current = true; + startupSeekArmedRef.current = null; + + setTimeout(() => { + mediaSeekToTimestamp(seekTimestamp); + }, 100); + }, [mediaSeekToTimestamp]); + + useEffect(() => { + if (startupRestoreInitializedRef.current) { + return; + } + + if (!playerHydrated || !currentSong) { + return; + } + + startupRestoreInitializedRef.current = true; + + if (timestamp > 0) { + startupSeekArmedRef.current = timestamp; + } + + if (playerStatus === PlayerStatus.PLAYING) { + applyStartupSeek(); + } + }, [applyStartupSeek, currentSong, playerHydrated, playerStatus, timestamp]); + + usePlayerEvents( + { + onPlayerStatus: (properties) => { + if (properties.status === PlayerStatus.PLAYING) { + applyStartupSeek(); + } + }, + }, + [applyStartupSeek], + ); +}; + +export const InitialTimestampRestoreHook = () => { + useInitialTimestampRestore(); + return null; +}; + export const useSaveQueue = () => { const serverId = useCurrentServerId(); diff --git a/src/renderer/store/timestamp.store.ts b/src/renderer/store/timestamp.store.ts index 9e6581158..3b729eff0 100644 --- a/src/renderer/store/timestamp.store.ts +++ b/src/renderer/store/timestamp.store.ts @@ -1,4 +1,5 @@ -import { subscribeWithSelector } from 'zustand/middleware'; +import { del, get, set } from 'idb-keyval'; +import { persist, subscribeWithSelector } from 'zustand/middleware'; import { createWithEqualityFn } from 'zustand/traditional'; interface TimestampState { @@ -6,13 +7,36 @@ interface TimestampState { timestamp: number; } +const timestampStorage = { + getItem: async (name: string) => { + const value = await get(name); + if (value === undefined) { + return null; + } + return { state: { timestamp: value }, version: 1 } as const; + }, + removeItem: async (name: string) => { + await del(name); + }, + setItem: async (name: string, value: { state: { timestamp: number }; version?: number }) => { + await set(name, value.state.timestamp); + }, +}; + export const useTimestampStoreBase = createWithEqualityFn()( - subscribeWithSelector((set) => ({ - setTimestamp: (timestamp: number) => { - set({ timestamp }); + persist( + subscribeWithSelector((set) => ({ + setTimestamp: (timestamp: number) => { + set({ timestamp }); + }, + timestamp: 0, + })), + { + name: 'player-timestamp', + storage: timestampStorage, + version: 1, }, - timestamp: 0, - })), + ), ); export const subscribePlayerProgress = ( From edfc64d790e083932dafa2921066930fbcc3c677 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 21 May 2026 11:17:15 -0700 Subject: [PATCH 2/4] update to electron v41 (#2047) --- electron-builder-alpha.yml | 4 ++-- electron-builder-beta.yml | 4 ++-- electron-builder.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/electron-builder-alpha.yml b/electron-builder-alpha.yml index 9d7a54881..63bcc8d67 100644 --- a/electron-builder-alpha.yml +++ b/electron-builder-alpha.yml @@ -1,7 +1,7 @@ appId: org.jeffvli.feishin productName: Feishin artifactName: ${productName}-${version}-${os}-${arch}.${ext} -electronVersion: 39.4.0 +electronVersion: 41.7.0 directories: buildResources: assets files: @@ -47,7 +47,7 @@ mac: gatekeeperAssess: false notarize: false extendInfo: - NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin" + NSAudioCaptureUsageDescription: 'System audio access is required for mpv visualizer capture in Feishin' NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin' dmg: diff --git a/electron-builder-beta.yml b/electron-builder-beta.yml index d88f7ca9b..bbd185431 100644 --- a/electron-builder-beta.yml +++ b/electron-builder-beta.yml @@ -1,7 +1,7 @@ appId: org.jeffvli.feishin productName: Feishin artifactName: ${productName}-${version}-${os}-${arch}.${ext} -electronVersion: 39.4.0 +electronVersion: 41.7.0 directories: buildResources: assets files: @@ -47,7 +47,7 @@ mac: gatekeeperAssess: false notarize: false extendInfo: - NSAudioCaptureUsageDescription: "System audio access is required for mpv visualizer capture in Feishin" + NSAudioCaptureUsageDescription: 'System audio access is required for mpv visualizer capture in Feishin' NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin' dmg: diff --git a/electron-builder.yml b/electron-builder.yml index 55050f5fa..c3024c5c7 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,7 +1,7 @@ appId: org.jeffvli.feishin productName: Feishin artifactName: ${productName}-${version}-${os}-${arch}.${ext} -electronVersion: 39.4.0 +electronVersion: 41.7.0 directories: buildResources: assets files: From fad2534d4441a4bbac923d4a34466880acf5ca58 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 21 May 2026 11:28:13 -0700 Subject: [PATCH 3/4] update to electron v41 in deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3379f5233..10d1af25a 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "babel-plugin-react-compiler": "^1.0.0", "concurrently": "^9.2.1", "cross-env": "^10.1.0", - "electron": "^39.8.6", + "electron": "^41.7.0", "electron-builder": "^26.8.2", "electron-devtools-installer": "^4.0.0", "electron-vite": "^4.0.1", From 0e163543fc32d5099d17edad9b1e59922da41db1 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 21 May 2026 11:30:09 -0700 Subject: [PATCH 4/4] update lockfile for electron v41 --- pnpm-lock.yaml | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b67d5172..a5415464b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,10 +23,10 @@ importers: version: 1.1.0 '@electron-toolkit/preload': specifier: ^3.0.2 - version: 3.0.2(electron@39.8.6) + version: 3.0.2(electron@41.7.0) '@electron-toolkit/utils': specifier: ^4.0.0 - version: 4.0.0(electron@39.8.6) + version: 4.0.0(electron@41.7.0) '@mantine/colors-generator': specifier: ^9.1.1 version: 9.1.1(chroma-js@3.1.2) @@ -269,8 +269,8 @@ importers: specifier: ^10.1.0 version: 10.1.0 electron: - specifier: ^39.8.6 - version: 39.8.6 + specifier: ^41.7.0 + version: 41.7.0 electron-builder: specifier: ^26.8.2 version: 26.8.2(electron-builder-squirrel-windows@26.8.2) @@ -2126,9 +2126,6 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@22.19.17': - resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} - '@types/node@24.12.2': resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} @@ -3001,8 +2998,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@39.8.6: - resolution: {integrity: sha512-uWX6Jh5LmwL13VwOSKBjebI+ck+03GOwc8V2Sgbmr9pJVJ/cHfli/PkjXuRDr+hq+SLHQuT9mGHSIfScebApRA==} + electron@41.7.0: + resolution: {integrity: sha512-U6KAKivjk6YQ0Z5+eloJBjwhbHRE206gvy1UBMw2bSluWtMh5waeXMvX6AT/Ujm5ymYXVJOp7g9N7vOFw16wBQ==} engines: {node: '>= 12.20.55'} hasBin: true @@ -5507,9 +5504,6 @@ packages: underscore.string@3.3.6: resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -6684,17 +6678,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron-toolkit/preload@3.0.2(electron@39.8.6)': + '@electron-toolkit/preload@3.0.2(electron@41.7.0)': dependencies: - electron: 39.8.6 + electron: 41.7.0 '@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)': dependencies: '@types/node': 24.12.2 - '@electron-toolkit/utils@4.0.0(electron@39.8.6)': + '@electron-toolkit/utils@4.0.0(electron@41.7.0)': dependencies: - electron: 39.8.6 + electron: 41.7.0 '@electron/asar@3.4.1': dependencies: @@ -7675,7 +7669,7 @@ snapshots: '@types/electron-localshortcut@3.1.3': dependencies: - electron: 39.8.6 + electron: 41.7.0 transitivePeerDependencies: - supports-color @@ -7703,10 +7697,6 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@22.19.17': - dependencies: - undici-types: 6.21.0 - '@types/node@24.12.2': dependencies: undici-types: 7.16.0 @@ -8800,10 +8790,10 @@ snapshots: transitivePeerDependencies: - supports-color - electron@39.8.6: + electron@41.7.0: dependencies: '@electron/get': 2.0.3 - '@types/node': 22.19.17 + '@types/node': 24.12.2 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -11588,8 +11578,6 @@ snapshots: sprintf-js: 1.1.3 util-deprecate: 1.0.2 - undici-types@6.21.0: {} - undici-types@7.16.0: {} undici@6.24.1: {}