mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-22 02:16:39 +02:00
Merge branch 'development' into enable-sandbox
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
appId: org.jeffvli.feishin
|
appId: org.jeffvli.feishin
|
||||||
productName: Feishin
|
productName: Feishin
|
||||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||||
electronVersion: 39.4.0
|
electronVersion: 41.7.0
|
||||||
directories:
|
directories:
|
||||||
buildResources: assets
|
buildResources: assets
|
||||||
files:
|
files:
|
||||||
@@ -47,7 +47,7 @@ mac:
|
|||||||
gatekeeperAssess: false
|
gatekeeperAssess: false
|
||||||
notarize: false
|
notarize: false
|
||||||
extendInfo:
|
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'
|
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||||
|
|
||||||
dmg:
|
dmg:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
appId: org.jeffvli.feishin
|
appId: org.jeffvli.feishin
|
||||||
productName: Feishin
|
productName: Feishin
|
||||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||||
electronVersion: 39.4.0
|
electronVersion: 41.7.0
|
||||||
directories:
|
directories:
|
||||||
buildResources: assets
|
buildResources: assets
|
||||||
files:
|
files:
|
||||||
@@ -47,7 +47,7 @@ mac:
|
|||||||
gatekeeperAssess: false
|
gatekeeperAssess: false
|
||||||
notarize: false
|
notarize: false
|
||||||
extendInfo:
|
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'
|
NSLocalNetworkUsageDescription: 'Local network is necessary for accessing servers hosted on the same system as Feishin'
|
||||||
|
|
||||||
dmg:
|
dmg:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
appId: org.jeffvli.feishin
|
appId: org.jeffvli.feishin
|
||||||
productName: Feishin
|
productName: Feishin
|
||||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||||
electronVersion: 39.4.0
|
electronVersion: 41.7.0
|
||||||
directories:
|
directories:
|
||||||
buildResources: assets
|
buildResources: assets
|
||||||
files:
|
files:
|
||||||
|
|||||||
+1
-1
@@ -160,7 +160,7 @@
|
|||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"electron": "^39.8.6",
|
"electron": "^41.7.0",
|
||||||
"electron-builder": "^26.8.2",
|
"electron-builder": "^26.8.2",
|
||||||
"electron-devtools-installer": "^4.0.0",
|
"electron-devtools-installer": "^4.0.0",
|
||||||
"electron-vite": "^4.0.1",
|
"electron-vite": "^4.0.1",
|
||||||
|
|||||||
Generated
+13
-25
@@ -23,10 +23,10 @@ importers:
|
|||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
'@electron-toolkit/preload':
|
'@electron-toolkit/preload':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2(electron@39.8.6)
|
version: 3.0.2(electron@41.7.0)
|
||||||
'@electron-toolkit/utils':
|
'@electron-toolkit/utils':
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0(electron@39.8.6)
|
version: 4.0.0(electron@41.7.0)
|
||||||
'@mantine/colors-generator':
|
'@mantine/colors-generator':
|
||||||
specifier: ^9.1.1
|
specifier: ^9.1.1
|
||||||
version: 9.1.1(chroma-js@3.1.2)
|
version: 9.1.1(chroma-js@3.1.2)
|
||||||
@@ -269,8 +269,8 @@ importers:
|
|||||||
specifier: ^10.1.0
|
specifier: ^10.1.0
|
||||||
version: 10.1.0
|
version: 10.1.0
|
||||||
electron:
|
electron:
|
||||||
specifier: ^39.8.6
|
specifier: ^41.7.0
|
||||||
version: 39.8.6
|
version: 41.7.0
|
||||||
electron-builder:
|
electron-builder:
|
||||||
specifier: ^26.8.2
|
specifier: ^26.8.2
|
||||||
version: 26.8.2(electron-builder-squirrel-windows@26.8.2)
|
version: 26.8.2(electron-builder-squirrel-windows@26.8.2)
|
||||||
@@ -2126,9 +2126,6 @@ packages:
|
|||||||
'@types/ms@2.1.0':
|
'@types/ms@2.1.0':
|
||||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
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':
|
'@types/node@24.12.2':
|
||||||
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
||||||
|
|
||||||
@@ -3001,8 +2998,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
electron@39.8.6:
|
electron@41.7.0:
|
||||||
resolution: {integrity: sha512-uWX6Jh5LmwL13VwOSKBjebI+ck+03GOwc8V2Sgbmr9pJVJ/cHfli/PkjXuRDr+hq+SLHQuT9mGHSIfScebApRA==}
|
resolution: {integrity: sha512-U6KAKivjk6YQ0Z5+eloJBjwhbHRE206gvy1UBMw2bSluWtMh5waeXMvX6AT/Ujm5ymYXVJOp7g9N7vOFw16wBQ==}
|
||||||
engines: {node: '>= 12.20.55'}
|
engines: {node: '>= 12.20.55'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -5507,9 +5504,6 @@ packages:
|
|||||||
underscore.string@3.3.6:
|
underscore.string@3.3.6:
|
||||||
resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==}
|
resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==}
|
||||||
|
|
||||||
undici-types@6.21.0:
|
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
|
||||||
|
|
||||||
undici-types@7.16.0:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
@@ -6684,17 +6678,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@electron-toolkit/preload@3.0.2(electron@39.8.6)':
|
'@electron-toolkit/preload@3.0.2(electron@41.7.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
electron: 39.8.6
|
electron: 41.7.0
|
||||||
|
|
||||||
'@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)':
|
'@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.12.2
|
'@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:
|
dependencies:
|
||||||
electron: 39.8.6
|
electron: 41.7.0
|
||||||
|
|
||||||
'@electron/asar@3.4.1':
|
'@electron/asar@3.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7675,7 +7669,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/electron-localshortcut@3.1.3':
|
'@types/electron-localshortcut@3.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
electron: 39.8.6
|
electron: 41.7.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7703,10 +7697,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/ms@2.1.0': {}
|
'@types/ms@2.1.0': {}
|
||||||
|
|
||||||
'@types/node@22.19.17':
|
|
||||||
dependencies:
|
|
||||||
undici-types: 6.21.0
|
|
||||||
|
|
||||||
'@types/node@24.12.2':
|
'@types/node@24.12.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
@@ -8800,10 +8790,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
electron@39.8.6:
|
electron@41.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@electron/get': 2.0.3
|
'@electron/get': 2.0.3
|
||||||
'@types/node': 22.19.17
|
'@types/node': 24.12.2
|
||||||
extract-zip: 2.0.1
|
extract-zip: 2.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -11588,8 +11578,6 @@ snapshots:
|
|||||||
sprintf-js: 1.1.3
|
sprintf-js: 1.1.3
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
|
||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
undici@6.24.1: {}
|
undici@6.24.1: {}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-se
|
|||||||
import { MPRISHook } from '/@/renderer/features/player/hooks/use-mpris';
|
import { MPRISHook } from '/@/renderer/features/player/hooks/use-mpris';
|
||||||
import { PlaybackHotkeysHook } from '/@/renderer/features/player/hooks/use-playback-hotkeys';
|
import { PlaybackHotkeysHook } from '/@/renderer/features/player/hooks/use-playback-hotkeys';
|
||||||
import { PowerSaveBlockerHook } from '/@/renderer/features/player/hooks/use-power-save-blocker';
|
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 { ScrobbleHook } from '/@/renderer/features/player/hooks/use-scrobble';
|
||||||
import { UpdateCurrentSongHook } 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';
|
||||||
@@ -134,6 +137,7 @@ export const AudioPlayers = () => {
|
|||||||
<RemoteHook />
|
<RemoteHook />
|
||||||
<AutoDJHook />
|
<AutoDJHook />
|
||||||
<QueueRestoreTimestampHook />
|
<QueueRestoreTimestampHook />
|
||||||
|
<InitialTimestampRestoreHook />
|
||||||
<UpdateCurrentSongHook />
|
<UpdateCurrentSongHook />
|
||||||
<RadioAudioInstanceHook />
|
<RadioAudioInstanceHook />
|
||||||
<RadioMetadataHook />
|
<RadioMetadataHook />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
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 {
|
import {
|
||||||
setTimestamp,
|
setTimestamp,
|
||||||
useCurrentServerId,
|
useCurrentServerId,
|
||||||
|
usePlayerActions,
|
||||||
|
usePlayerHydrated,
|
||||||
|
usePlayerSong,
|
||||||
|
usePlayerStatus,
|
||||||
usePlayerStore,
|
usePlayerStore,
|
||||||
useTimestampStoreBase,
|
useTimestampStoreBase,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const useQueueRestoreTimestamp = () => {
|
export const useQueueRestoreTimestamp = () => {
|
||||||
const player = usePlayerStore();
|
const { mediaSeekToTimestamp } = usePlayerActions();
|
||||||
|
|
||||||
usePlayerEvents(
|
usePlayerEvents(
|
||||||
{
|
{
|
||||||
@@ -24,7 +29,7 @@ export const useQueueRestoreTimestamp = () => {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setTimestamp(position);
|
setTimestamp(position);
|
||||||
player.mediaSeekToTimestamp(position);
|
mediaSeekToTimestamp(position);
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -37,6 +42,72 @@ export const QueueRestoreTimestampHook = () => {
|
|||||||
return null;
|
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 | number>(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 = () => {
|
export const useSaveQueue = () => {
|
||||||
const serverId = useCurrentServerId();
|
const serverId = useCurrentServerId();
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
import { createWithEqualityFn } from 'zustand/traditional';
|
||||||
|
|
||||||
interface TimestampState {
|
interface TimestampState {
|
||||||
@@ -6,13 +7,36 @@ interface TimestampState {
|
|||||||
timestamp: number;
|
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<TimestampState>()(
|
export const useTimestampStoreBase = createWithEqualityFn<TimestampState>()(
|
||||||
subscribeWithSelector((set) => ({
|
persist(
|
||||||
setTimestamp: (timestamp: number) => {
|
subscribeWithSelector((set) => ({
|
||||||
set({ timestamp });
|
setTimestamp: (timestamp: number) => {
|
||||||
|
set({ timestamp });
|
||||||
|
},
|
||||||
|
timestamp: 0,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
name: 'player-timestamp',
|
||||||
|
storage: timestampStorage,
|
||||||
|
version: 1,
|
||||||
},
|
},
|
||||||
timestamp: 0,
|
),
|
||||||
})),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const subscribePlayerProgress = (
|
export const subscribePlayerProgress = (
|
||||||
|
|||||||
Reference in New Issue
Block a user