mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
feat(player): add server-side autosave capability
This commit is contained in:
@@ -721,6 +721,10 @@
|
|||||||
"autoDJ_itemCount_description": "the number of items attempted to be added to the queue when auto DJ is enabled",
|
"autoDJ_itemCount_description": "the number of items attempted to be added to the queue when auto DJ is enabled",
|
||||||
"autoDJ_timing": "timing",
|
"autoDJ_timing": "timing",
|
||||||
"autoDJ_timing_description": "the number of songs remaining in the queue before auto DJ is triggered",
|
"autoDJ_timing_description": "the number of songs remaining in the queue before auto DJ is triggered",
|
||||||
|
"autosave": "automatically save play queue",
|
||||||
|
"autosave_description": "enable automatically saving the play queue to your server. this is only possible when using Navidrome/Subsonic, and you cannot have a mixed play queue.",
|
||||||
|
"autosaveCount": "automatic play queue save frequency",
|
||||||
|
"autosaveCount_description": "how many track changes before the queue is saved. 1 (minimum) means every song change",
|
||||||
"accentColor_description": "sets the accent color for the application",
|
"accentColor_description": "sets the accent color for the application",
|
||||||
"accentColor": "accent color",
|
"accentColor": "accent color",
|
||||||
"useThemeAccentColor": "use theme accent color",
|
"useThemeAccentColor": "use theme accent color",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { MpvPlayer } from '/@/renderer/features/player/audio-player/mpv-player';
|
|||||||
import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player';
|
import { WebPlayer } from '/@/renderer/features/player/audio-player/web-player';
|
||||||
import { SleepTimerHook } from '/@/renderer/features/player/components/sleep-timer-button';
|
import { SleepTimerHook } from '/@/renderer/features/player/components/sleep-timer-button';
|
||||||
import { AutoDJHook } from '/@/renderer/features/player/hooks/use-auto-dj';
|
import { AutoDJHook } from '/@/renderer/features/player/hooks/use-auto-dj';
|
||||||
|
import { AutosaveHook } from '/@/renderer/features/player/hooks/use-autosave';
|
||||||
import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-session';
|
import { MediaSessionHook } from '/@/renderer/features/player/hooks/use-media-session';
|
||||||
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';
|
||||||
@@ -64,6 +65,7 @@ export const AudioPlayers = () => {
|
|||||||
<UpdateCurrentSongHook />
|
<UpdateCurrentSongHook />
|
||||||
<RadioAudioInstanceHook />
|
<RadioAudioInstanceHook />
|
||||||
<RadioMetadataHook />
|
<RadioMetadataHook />
|
||||||
|
<AutosaveHook />
|
||||||
<AudioPlayersContent
|
<AudioPlayersContent
|
||||||
audioContext={audioContext}
|
audioContext={audioContext}
|
||||||
audioDeviceId={audioDeviceId}
|
audioDeviceId={audioDeviceId}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useSaveQueue } from '/@/renderer/features/player/hooks/use-queue-restore';
|
||||||
|
import { useCurrentServer, usePlayerSong, useSettingsStore } from '/@/renderer/store';
|
||||||
|
import { ServerType } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const useAutosave = () => {
|
||||||
|
const server = useCurrentServer();
|
||||||
|
const currentSong = usePlayerSong();
|
||||||
|
const priorSongId = useRef<string | undefined>(undefined);
|
||||||
|
const songCount = useRef(0);
|
||||||
|
const { count, enabled } = useSettingsStore((state) => state.general.autoSave);
|
||||||
|
const { mutate: savePlayQueue } = useSaveQueue();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (enabled && server.type !== ServerType.JELLYFIN) {
|
||||||
|
if (currentSong?._uniqueId !== priorSongId.current) {
|
||||||
|
if (songCount.current === count) {
|
||||||
|
savePlayQueue();
|
||||||
|
songCount.current = 1;
|
||||||
|
} else {
|
||||||
|
songCount.current += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
priorSongId.current = currentSong?._uniqueId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [enabled, count, currentSong?._uniqueId, savePlayQueue, server.type]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AutosaveHook = () => {
|
||||||
|
useAutosave();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -89,8 +89,7 @@ export const useSaveQueue = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
toast.success({
|
toast.success({
|
||||||
message: '',
|
message: t('form.saveQueue.success', { postProcess: 'sentenceCase' }),
|
||||||
title: t('form.saveQueue.success', { postProcess: 'sentenceCase' }),
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error({
|
toast.error({
|
||||||
|
|||||||
@@ -688,6 +688,59 @@ export const ApplicationSettings = memo(() => {
|
|||||||
isHidden: false,
|
isHidden: false,
|
||||||
title: t('setting.playerbarOpenDrawer', { postProcess: 'sentenceCase' }),
|
title: t('setting.playerbarOpenDrawer', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label={t('setting.autosave', { postProcess: 'sentenceCase' })}
|
||||||
|
defaultChecked={settings.autoSave.enabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
autoSave: {
|
||||||
|
...settings.autoSave,
|
||||||
|
enabled: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.autosave', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
title: t('setting.autosave', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<NumberInput
|
||||||
|
min={1}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (!e) return;
|
||||||
|
const newVal = e.currentTarget.value
|
||||||
|
? Math.max(Number(e.currentTarget.value), 1)
|
||||||
|
: settings.autoSave.count;
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
...settings,
|
||||||
|
autoSave: {
|
||||||
|
...settings.autoSave,
|
||||||
|
count: newVal,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
value={settings.autoSave.count}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.autosaveCount', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !settings.autoSave.enabled,
|
||||||
|
title: t('setting.autosaveCount', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -430,6 +430,11 @@ export enum HomeFeatureStyle {
|
|||||||
SINGLE = 'single',
|
SINGLE = 'single',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AutoSaveSchema = z.object({
|
||||||
|
count: z.number().min(0),
|
||||||
|
enabled: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
export const GeneralSettingsSchema = z.object({
|
export const GeneralSettingsSchema = z.object({
|
||||||
accent: z
|
accent: z
|
||||||
.string()
|
.string()
|
||||||
@@ -446,6 +451,7 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
||||||
artistRadioCount: z.number(),
|
artistRadioCount: z.number(),
|
||||||
artistReleaseTypeItems: z.array(SortableItemSchema(ArtistReleaseTypeItemSchema)),
|
artistReleaseTypeItems: z.array(SortableItemSchema(ArtistReleaseTypeItemSchema)),
|
||||||
|
autoSave: AutoSaveSchema,
|
||||||
blurExplicitImages: z.boolean(),
|
blurExplicitImages: z.boolean(),
|
||||||
buttonSize: z.number(),
|
buttonSize: z.number(),
|
||||||
collections: z.array(CollectionSchema),
|
collections: z.array(CollectionSchema),
|
||||||
@@ -1094,6 +1100,10 @@ const initialState: SettingsState = {
|
|||||||
artistItems,
|
artistItems,
|
||||||
artistRadioCount: 20,
|
artistRadioCount: 20,
|
||||||
artistReleaseTypeItems,
|
artistReleaseTypeItems,
|
||||||
|
autoSave: {
|
||||||
|
count: 10,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
blurExplicitImages: false,
|
blurExplicitImages: false,
|
||||||
buttonSize: 15,
|
buttonSize: 15,
|
||||||
collections: [],
|
collections: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user