mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-13 05:59:58 +02:00
add setting for lyrics follow alignment
This commit is contained in:
@@ -100,6 +100,8 @@ These variables override app settings **on first run** when no persisted setting
|
|||||||
| `lyrics.fetch` | `true` | `FS_LYRICS_FETCH` | `true` / `false` — Fetch lyrics. |
|
| `lyrics.fetch` | `true` | `FS_LYRICS_FETCH` | `true` / `false` — Fetch lyrics. |
|
||||||
| `lyrics.follow` | `true` | `FS_LYRICS_FOLLOW` | `true` / `false` — Follow current line. |
|
| `lyrics.follow` | `true` | `FS_LYRICS_FOLLOW` | `true` / `false` — Follow current line. |
|
||||||
| `lyrics.delayMs` | `0` | `FS_LYRICS_DELAY_MS` | Sync delay in milliseconds. |
|
| `lyrics.delayMs` | `0` | `FS_LYRICS_DELAY_MS` | Sync delay in milliseconds. |
|
||||||
|
| `lyrics.lineLeadTimeMs` | `800` | `FS_LYRICS_LINE_LEAD_TIME_MS` | Milliseconds before a line becomes active while following. |
|
||||||
|
| `lyrics.followScrollAlignment` | `0` | `FS_LYRICS_FOLLOW_SCROLL_ALIGNMENT` | Vertical follow position. `0` centers the active line; negative moves it higher, positive moves it lower (`-50` to `50`). |
|
||||||
| `lyrics.preferLocalLyrics` | `true` | `FS_LYRICS_PREFER_LOCAL` | `true` / `false` — Prefer local lyric files. |
|
| `lyrics.preferLocalLyrics` | `true` | `FS_LYRICS_PREFER_LOCAL` | `true` / `false` — Prefer local lyric files. |
|
||||||
| `lyrics.showMatch` | `true` | `FS_LYRICS_SHOW_MATCH` | `true` / `false`. |
|
| `lyrics.showMatch` | `true` | `FS_LYRICS_SHOW_MATCH` | `true` / `false`. |
|
||||||
| `lyrics.showProvider` | `true` | `FS_LYRICS_SHOW_PROVIDER` | `true` / `false`. |
|
| `lyrics.showProvider` | `true` | `FS_LYRICS_SHOW_PROVIDER` | `true` / `false`. |
|
||||||
@@ -110,6 +112,17 @@ These variables override app settings **on first run** when no persisted setting
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Lyrics display
|
||||||
|
|
||||||
|
Applies to the default lyrics display profile (`lyricsDisplay.default`).
|
||||||
|
|
||||||
|
| Setting path | Default | Env variable | Available values / Description |
|
||||||
|
|-------------|---------|--------------|--------------------------------|
|
||||||
|
| `lyricsDisplay.default.paddingLeft` | `0` | `FS_LYRICS_PADDING_LEFT` | Left content padding as a percentage (`0`–`20`). |
|
||||||
|
| `lyricsDisplay.default.paddingRight` | `0` | `FS_LYRICS_PADDING_RIGHT` | Right content padding as a percentage (`0`–`20`). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Auto DJ
|
## Auto DJ
|
||||||
|
|
||||||
| Setting path | Default | Env variable | Available values / Description |
|
| Setting path | Default | Env variable | Available values / Description |
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ window.FS_DISCORD_SHOW_STATE_ICON = "${FS_DISCORD_SHOW_STATE_ICON}";
|
|||||||
window.FS_LYRICS_FETCH = "${FS_LYRICS_FETCH}";
|
window.FS_LYRICS_FETCH = "${FS_LYRICS_FETCH}";
|
||||||
window.FS_LYRICS_FOLLOW = "${FS_LYRICS_FOLLOW}";
|
window.FS_LYRICS_FOLLOW = "${FS_LYRICS_FOLLOW}";
|
||||||
window.FS_LYRICS_DELAY_MS = "${FS_LYRICS_DELAY_MS}";
|
window.FS_LYRICS_DELAY_MS = "${FS_LYRICS_DELAY_MS}";
|
||||||
|
window.FS_LYRICS_LINE_LEAD_TIME_MS = "${FS_LYRICS_LINE_LEAD_TIME_MS}";
|
||||||
|
window.FS_LYRICS_FOLLOW_SCROLL_ALIGNMENT = "${FS_LYRICS_FOLLOW_SCROLL_ALIGNMENT}";
|
||||||
window.FS_LYRICS_PREFER_LOCAL = "${FS_LYRICS_PREFER_LOCAL}";
|
window.FS_LYRICS_PREFER_LOCAL = "${FS_LYRICS_PREFER_LOCAL}";
|
||||||
window.FS_LYRICS_SHOW_MATCH = "${FS_LYRICS_SHOW_MATCH}";
|
window.FS_LYRICS_SHOW_MATCH = "${FS_LYRICS_SHOW_MATCH}";
|
||||||
window.FS_LYRICS_SHOW_PROVIDER = "${FS_LYRICS_SHOW_PROVIDER}";
|
window.FS_LYRICS_SHOW_PROVIDER = "${FS_LYRICS_SHOW_PROVIDER}";
|
||||||
@@ -87,6 +89,8 @@ window.FS_LYRICS_ENABLE_AUTO_TRANSLATION = "${FS_LYRICS_ENABLE_AUTO_TRANSLATION}
|
|||||||
window.FS_LYRICS_TRANSLATION_API_KEY = "${FS_LYRICS_TRANSLATION_API_KEY}";
|
window.FS_LYRICS_TRANSLATION_API_KEY = "${FS_LYRICS_TRANSLATION_API_KEY}";
|
||||||
window.FS_LYRICS_TRANSLATION_TARGET_LANGUAGE = "${FS_LYRICS_TRANSLATION_TARGET_LANGUAGE}";
|
window.FS_LYRICS_TRANSLATION_TARGET_LANGUAGE = "${FS_LYRICS_TRANSLATION_TARGET_LANGUAGE}";
|
||||||
window.FS_LYRICS_ALIGNMENT = "${FS_LYRICS_ALIGNMENT}";
|
window.FS_LYRICS_ALIGNMENT = "${FS_LYRICS_ALIGNMENT}";
|
||||||
|
window.FS_LYRICS_PADDING_LEFT = "${FS_LYRICS_PADDING_LEFT}";
|
||||||
|
window.FS_LYRICS_PADDING_RIGHT = "${FS_LYRICS_PADDING_RIGHT}";
|
||||||
|
|
||||||
window.FS_AUTO_DJ_ALBUM_STRATEGY = "${FS_AUTO_DJ_ALBUM_STRATEGY}";
|
window.FS_AUTO_DJ_ALBUM_STRATEGY = "${FS_AUTO_DJ_ALBUM_STRATEGY}";
|
||||||
window.FS_AUTO_DJ_ENABLED = "${FS_AUTO_DJ_ENABLED}";
|
window.FS_AUTO_DJ_ENABLED = "${FS_AUTO_DJ_ENABLED}";
|
||||||
|
|||||||
@@ -548,6 +548,7 @@
|
|||||||
"dynamicImageBlur": "Image blur size",
|
"dynamicImageBlur": "Image blur size",
|
||||||
"dynamicIsImage": "Enable background image",
|
"dynamicIsImage": "Enable background image",
|
||||||
"followCurrentLyric": "Follow current lyric",
|
"followCurrentLyric": "Follow current lyric",
|
||||||
|
"lyricFollowScrollAlignment": "Lyrics follow alignment",
|
||||||
"lyricAlignment": "Lyric alignment",
|
"lyricAlignment": "Lyric alignment",
|
||||||
"lyricOffset": "Lyrics offset (ms)",
|
"lyricOffset": "Lyrics offset (ms)",
|
||||||
"lyricGap": "Lyric gap",
|
"lyricGap": "Lyric gap",
|
||||||
|
|||||||
@@ -212,6 +212,23 @@ export const LyricsSettingsForm = ({ settingsKey }: LyricsSettingsFormProps) =>
|
|||||||
description: '',
|
description: '',
|
||||||
title: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
title: t('page.fullscreenPlayer.config.followCurrentLyric'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Slider
|
||||||
|
defaultValue={lyricsSettings.followScrollAlignment ?? 0}
|
||||||
|
label={(value) => value.toString()}
|
||||||
|
max={50}
|
||||||
|
min={-50}
|
||||||
|
onChangeEnd={(value) => {
|
||||||
|
updateLyricsSetting({ followScrollAlignment: value });
|
||||||
|
}}
|
||||||
|
step={1}
|
||||||
|
w={100}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: '',
|
||||||
|
title: t('page.fullscreenPlayer.config.lyricFollowScrollAlignment'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ const LINE_ANIMATING_CLASS = 'lyrics-line-animating';
|
|||||||
const LINE_PRE_ANIMATING_CLASS = 'lyrics-line-pre-animating';
|
const LINE_PRE_ANIMATING_CLASS = 'lyrics-line-pre-animating';
|
||||||
|
|
||||||
const TIME_JUMP_THRESHOLD = 0.5;
|
const TIME_JUMP_THRESHOLD = 0.5;
|
||||||
|
const CENTER_SCROLL_POS_RATIO = 0.5;
|
||||||
const DEFAULT_ENDING_THRESHOLD = 0.5;
|
const DEFAULT_ENDING_THRESHOLD = 0.5;
|
||||||
const DEFAULT_LINE_LEAD_TIME_MS = 800;
|
const DEFAULT_LINE_LEAD_TIME_MS = 800;
|
||||||
const DEFAULT_SCROLL_POS_RATIO = 0.37;
|
|
||||||
const RICHSYNC_TIMING_OFFSET_MS = 150;
|
const RICHSYNC_TIMING_OFFSET_MS = 150;
|
||||||
const SYNC_TIMING_OFFSET_MS = 115;
|
const SYNC_TIMING_OFFSET_MS = 115;
|
||||||
const GLOW_DURATION_MULTIPLIER = 1.6;
|
const GLOW_DURATION_MULTIPLIER = 1.6;
|
||||||
@@ -90,6 +90,7 @@ export interface TickOptions {
|
|||||||
currentTimeMs: number;
|
currentTimeMs: number;
|
||||||
eventCreationTime: number;
|
eventCreationTime: number;
|
||||||
follow?: boolean;
|
follow?: boolean;
|
||||||
|
followScrollAlignment?: number;
|
||||||
forceResync?: boolean;
|
forceResync?: boolean;
|
||||||
isPlaying: boolean;
|
isPlaying: boolean;
|
||||||
lineLeadTimeMs?: number;
|
lineLeadTimeMs?: number;
|
||||||
@@ -99,6 +100,10 @@ export interface TickOptions {
|
|||||||
smoothScroll?: boolean;
|
smoothScroll?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getFollowScrollPositionRatio = (followScrollAlignment = 0): number => {
|
||||||
|
return Math.min(0.95, Math.max(0.05, CENTER_SCROLL_POS_RATIO + followScrollAlignment / 100));
|
||||||
|
};
|
||||||
|
|
||||||
const reflow = (element: HTMLElement): void => {
|
const reflow = (element: HTMLElement): void => {
|
||||||
void element.offsetHeight;
|
void element.offsetHeight;
|
||||||
};
|
};
|
||||||
@@ -735,7 +740,8 @@ export const tickLyricsAnimation = (state: AnimEngineState, opts: TickOptions):
|
|||||||
state.lastActiveElements = activeElems.filter((entry) => playbackTimeSec >= entry.time);
|
state.lastActiveElements = activeElems.filter((entry) => playbackTimeSec >= entry.time);
|
||||||
|
|
||||||
if (activeElems.length > 0) {
|
if (activeElems.length > 0) {
|
||||||
const scrollPosOffset = scrollHeight * DEFAULT_SCROLL_POS_RATIO;
|
const scrollPosRatio = getFollowScrollPositionRatio(opts.followScrollAlignment);
|
||||||
|
const scrollPosOffset = scrollHeight * scrollPosRatio;
|
||||||
const lastActive = activeElems[activeElems.length - 1];
|
const lastActive = activeElems[activeElems.length - 1];
|
||||||
const useLastActiveOnly = newLyricSelected || activeElems.length > 1;
|
const useLastActiveOnly = newLyricSelected || activeElems.length > 1;
|
||||||
const scrollLines = useLastActiveOnly ? [lastActive] : activeElems;
|
const scrollLines = useLastActiveOnly ? [lastActive] : activeElems;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface UseLyricsAnimationEngineOptions {
|
|||||||
containerRef: React.RefObject<HTMLElement | null>;
|
containerRef: React.RefObject<HTMLElement | null>;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
followRef?: React.RefObject<boolean>;
|
followRef?: React.RefObject<boolean>;
|
||||||
|
followScrollAlignmentRef?: React.RefObject<number>;
|
||||||
lineIdPrefix: 'karaoke-line' | 'lyric';
|
lineIdPrefix: 'karaoke-line' | 'lyric';
|
||||||
lineLeadTimeMsRef?: React.RefObject<number>;
|
lineLeadTimeMsRef?: React.RefObject<number>;
|
||||||
lyrics: SynchronizedLyrics;
|
lyrics: SynchronizedLyrics;
|
||||||
@@ -32,6 +33,7 @@ export const useLyricsAnimationEngine = ({
|
|||||||
containerRef,
|
containerRef,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
lineIdPrefix,
|
lineIdPrefix,
|
||||||
lineLeadTimeMsRef,
|
lineLeadTimeMsRef,
|
||||||
lyrics,
|
lyrics,
|
||||||
@@ -116,6 +118,7 @@ export const useLyricsAnimationEngine = ({
|
|||||||
currentTimeMs,
|
currentTimeMs,
|
||||||
eventCreationTime: options?.eventCreationTime ?? Date.now(),
|
eventCreationTime: options?.eventCreationTime ?? Date.now(),
|
||||||
follow: followRef?.current ?? true,
|
follow: followRef?.current ?? true,
|
||||||
|
followScrollAlignment: followScrollAlignmentRef?.current ?? 0,
|
||||||
forceResync: options?.forceResync ?? false,
|
forceResync: options?.forceResync ?? false,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
lineLeadTimeMs: lineLeadTimeMsRef?.current,
|
lineLeadTimeMs: lineLeadTimeMsRef?.current,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const useSynchronizedLyricsBase = (settingsKey = 'default', offsetMs?: nu
|
|||||||
const effectiveOffsetMs = offsetMs ?? 0;
|
const effectiveOffsetMs = offsetMs ?? 0;
|
||||||
const delayMsRef = useRef(effectiveOffsetMs);
|
const delayMsRef = useRef(effectiveOffsetMs);
|
||||||
const followRef = useRef(settings.follow);
|
const followRef = useRef(settings.follow);
|
||||||
|
const followScrollAlignmentRef = useRef(settings.followScrollAlignment);
|
||||||
const lineLeadTimeMsRef = useRef(settings.lineLeadTimeMs);
|
const lineLeadTimeMsRef = useRef(settings.lineLeadTimeMs);
|
||||||
const userScrollingRef = useRef(false);
|
const userScrollingRef = useRef(false);
|
||||||
const scrollTimeoutRef = useRef<null | ReturnType<typeof setTimeout>>(null);
|
const scrollTimeoutRef = useRef<null | ReturnType<typeof setTimeout>>(null);
|
||||||
@@ -155,6 +156,10 @@ export const useSynchronizedLyricsBase = (settingsKey = 'default', offsetMs?: nu
|
|||||||
followRef.current = settings.follow;
|
followRef.current = settings.follow;
|
||||||
}, [settings.follow]);
|
}, [settings.follow]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
followScrollAlignmentRef.current = settings.followScrollAlignment;
|
||||||
|
}, [settings.followScrollAlignment]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
lineLeadTimeMsRef.current = settings.lineLeadTimeMs;
|
lineLeadTimeMsRef.current = settings.lineLeadTimeMs;
|
||||||
}, [settings.lineLeadTimeMs]);
|
}, [settings.lineLeadTimeMs]);
|
||||||
@@ -224,6 +229,7 @@ export const useSynchronizedLyricsBase = (settingsKey = 'default', offsetMs?: nu
|
|||||||
containerStyle,
|
containerStyle,
|
||||||
delayMsRef,
|
delayMsRef,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
handleLineClick,
|
handleLineClick,
|
||||||
handleSeek,
|
handleSeek,
|
||||||
hideScrollbar,
|
hideScrollbar,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const SynchronizedKaraokeLyrics = ({
|
|||||||
containerStyle,
|
containerStyle,
|
||||||
delayMsRef,
|
delayMsRef,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
handleSeek,
|
handleSeek,
|
||||||
hideScrollbar,
|
hideScrollbar,
|
||||||
lineLeadTimeMsRef,
|
lineLeadTimeMsRef,
|
||||||
@@ -92,6 +93,7 @@ export const SynchronizedKaraokeLyrics = ({
|
|||||||
animStateRef: scrollAnimStateRef,
|
animStateRef: scrollAnimStateRef,
|
||||||
containerRef,
|
containerRef,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
lineIdPrefix: 'karaoke-line',
|
lineIdPrefix: 'karaoke-line',
|
||||||
lineLeadTimeMsRef,
|
lineLeadTimeMsRef,
|
||||||
lyrics: normalizedLyrics,
|
lyrics: normalizedLyrics,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export const SynchronizedLyrics = ({
|
|||||||
containerStyle,
|
containerStyle,
|
||||||
delayMsRef,
|
delayMsRef,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
handleLineClick,
|
handleLineClick,
|
||||||
hideScrollbar,
|
hideScrollbar,
|
||||||
lineLeadTimeMsRef,
|
lineLeadTimeMsRef,
|
||||||
@@ -80,6 +81,7 @@ export const SynchronizedLyrics = ({
|
|||||||
animStateRef: scrollAnimStateRef,
|
animStateRef: scrollAnimStateRef,
|
||||||
containerRef,
|
containerRef,
|
||||||
followRef,
|
followRef,
|
||||||
|
followScrollAlignmentRef,
|
||||||
lineIdPrefix: 'lyric',
|
lineIdPrefix: 'lyric',
|
||||||
lineLeadTimeMsRef,
|
lineLeadTimeMsRef,
|
||||||
lyrics: normalizedLyrics,
|
lyrics: normalizedLyrics,
|
||||||
|
|||||||
Vendored
+3
@@ -68,7 +68,10 @@ declare global {
|
|||||||
FS_LYRICS_ENABLE_AUTO_TRANSLATION?: string;
|
FS_LYRICS_ENABLE_AUTO_TRANSLATION?: string;
|
||||||
FS_LYRICS_FETCH?: string;
|
FS_LYRICS_FETCH?: string;
|
||||||
FS_LYRICS_FOLLOW?: string;
|
FS_LYRICS_FOLLOW?: string;
|
||||||
|
FS_LYRICS_FOLLOW_SCROLL_ALIGNMENT?: string;
|
||||||
FS_LYRICS_LINE_LEAD_TIME_MS?: string;
|
FS_LYRICS_LINE_LEAD_TIME_MS?: string;
|
||||||
|
FS_LYRICS_PADDING_LEFT?: string;
|
||||||
|
FS_LYRICS_PADDING_RIGHT?: string;
|
||||||
FS_LYRICS_PREFER_LOCAL?: string;
|
FS_LYRICS_PREFER_LOCAL?: string;
|
||||||
FS_LYRICS_SHOW_MATCH?: string;
|
FS_LYRICS_SHOW_MATCH?: string;
|
||||||
FS_LYRICS_SHOW_PROVIDER?: string;
|
FS_LYRICS_SHOW_PROVIDER?: string;
|
||||||
|
|||||||
@@ -115,7 +115,10 @@ const AUTO_DJ_MODES = new Set(['albums', 'songs']);
|
|||||||
const AUTO_DJ_STRATEGIES = new Set(['library_random', 'similar']);
|
const AUTO_DJ_STRATEGIES = new Set(['library_random', 'similar']);
|
||||||
|
|
||||||
export type EnvSettingsOverrides = DeepPartial<
|
export type EnvSettingsOverrides = DeepPartial<
|
||||||
Pick<SettingsState, 'autoDJ' | 'css' | 'discord' | 'font' | 'general' | 'lyrics' | 'playback'>
|
Pick<
|
||||||
|
SettingsState,
|
||||||
|
'autoDJ' | 'css' | 'discord' | 'font' | 'general' | 'lyrics' | 'lyricsDisplay' | 'playback'
|
||||||
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type DeepPartial<T> = {
|
type DeepPartial<T> = {
|
||||||
@@ -404,6 +407,15 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
|||||||
{ key: 'FS_LYRICS_FOLLOW', path: ['lyrics', 'follow'], type: 'bool' },
|
{ key: 'FS_LYRICS_FOLLOW', path: ['lyrics', 'follow'], type: 'bool' },
|
||||||
{ key: 'FS_LYRICS_DELAY_MS', path: ['lyrics', 'delayMs'], type: 'num' },
|
{ key: 'FS_LYRICS_DELAY_MS', path: ['lyrics', 'delayMs'], type: 'num' },
|
||||||
{ key: 'FS_LYRICS_LINE_LEAD_TIME_MS', path: ['lyrics', 'lineLeadTimeMs'], type: 'num' },
|
{ key: 'FS_LYRICS_LINE_LEAD_TIME_MS', path: ['lyrics', 'lineLeadTimeMs'], type: 'num' },
|
||||||
|
{
|
||||||
|
key: 'FS_LYRICS_FOLLOW_SCROLL_ALIGNMENT',
|
||||||
|
path: ['lyrics', 'followScrollAlignment'],
|
||||||
|
transform: (s) => {
|
||||||
|
const n = parseNum(s);
|
||||||
|
return n !== undefined ? Math.min(50, Math.max(-50, Math.round(n))) : undefined;
|
||||||
|
},
|
||||||
|
type: 'num',
|
||||||
|
},
|
||||||
{ key: 'FS_LYRICS_PREFER_LOCAL', path: ['lyrics', 'preferLocalLyrics'], type: 'bool' },
|
{ key: 'FS_LYRICS_PREFER_LOCAL', path: ['lyrics', 'preferLocalLyrics'], type: 'bool' },
|
||||||
{ key: 'FS_LYRICS_SHOW_MATCH', path: ['lyrics', 'showMatch'], type: 'bool' },
|
{ key: 'FS_LYRICS_SHOW_MATCH', path: ['lyrics', 'showMatch'], type: 'bool' },
|
||||||
{ key: 'FS_LYRICS_SHOW_PROVIDER', path: ['lyrics', 'showProvider'], type: 'bool' },
|
{ key: 'FS_LYRICS_SHOW_PROVIDER', path: ['lyrics', 'showProvider'], type: 'bool' },
|
||||||
@@ -425,6 +437,24 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
|||||||
path: ['lyrics', 'alignment'],
|
path: ['lyrics', 'alignment'],
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'FS_LYRICS_PADDING_LEFT',
|
||||||
|
path: ['lyricsDisplay', 'default', 'paddingLeft'],
|
||||||
|
transform: (s) => {
|
||||||
|
const n = parseNum(s);
|
||||||
|
return n !== undefined ? Math.min(20, Math.max(0, Math.round(n))) : undefined;
|
||||||
|
},
|
||||||
|
type: 'num',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'FS_LYRICS_PADDING_RIGHT',
|
||||||
|
path: ['lyricsDisplay', 'default', 'paddingRight'],
|
||||||
|
transform: (s) => {
|
||||||
|
const n = parseNum(s);
|
||||||
|
return n !== undefined ? Math.min(20, Math.max(0, Math.round(n))) : undefined;
|
||||||
|
},
|
||||||
|
type: 'num',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
enumSet: AUTO_DJ_STRATEGIES,
|
enumSet: AUTO_DJ_STRATEGIES,
|
||||||
key: 'FS_AUTO_DJ_ALBUM_STRATEGY',
|
key: 'FS_AUTO_DJ_ALBUM_STRATEGY',
|
||||||
|
|||||||
@@ -584,6 +584,7 @@ const LyricsSettingsSchema = z.object({
|
|||||||
enableRomaji: z.boolean().optional(),
|
enableRomaji: z.boolean().optional(),
|
||||||
fetch: z.boolean(),
|
fetch: z.boolean(),
|
||||||
follow: z.boolean(),
|
follow: z.boolean(),
|
||||||
|
followScrollAlignment: z.number(),
|
||||||
lineLeadTimeMs: z.number(),
|
lineLeadTimeMs: z.number(),
|
||||||
preferLocalLyrics: z.boolean(),
|
preferLocalLyrics: z.boolean(),
|
||||||
showMatch: z.boolean(),
|
showMatch: z.boolean(),
|
||||||
@@ -1858,6 +1859,7 @@ const initialState: SettingsState = {
|
|||||||
enableRomaji: false,
|
enableRomaji: false,
|
||||||
fetch: true,
|
fetch: true,
|
||||||
follow: true,
|
follow: true,
|
||||||
|
followScrollAlignment: 0,
|
||||||
lineLeadTimeMs: 800,
|
lineLeadTimeMs: 800,
|
||||||
preferLocalLyrics: true,
|
preferLocalLyrics: true,
|
||||||
showMatch: true,
|
showMatch: true,
|
||||||
@@ -2558,10 +2560,16 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version < 31) {
|
||||||
|
if (state.lyrics.followScrollAlignment === undefined) {
|
||||||
|
state.lyrics.followScrollAlignment = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return persistedState;
|
return persistedState;
|
||||||
},
|
},
|
||||||
name: 'store_settings',
|
name: 'store_settings',
|
||||||
version: 30,
|
version: 31,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user