mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
add jellyfin, improvements
This commit is contained in:
parent
85d2576bdc
commit
58f38b2655
11 changed files with 168 additions and 17 deletions
|
|
@ -3,9 +3,14 @@ import isElectron from 'is-electron';
|
|||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ErrorFallback } from '/@/renderer/features/action-required';
|
||||
import { useCurrentServer, useCurrentSong } from '/@/renderer/store';
|
||||
import { SynchronizedLyricsArray, SynchronizedLyrics } from './synchronized-lyrics';
|
||||
import { SynchronizedLyrics } from './synchronized-lyrics';
|
||||
import { UnsynchronizedLyrics } from '/@/renderer/features/lyrics/unsynchronized-lyrics';
|
||||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||
import { Center, Group } from '@mantine/core';
|
||||
import { RiInformationFill } from 'react-icons/ri';
|
||||
import { TextTitle } from '/@/renderer/components';
|
||||
import { SynchronizedLyricsArray } from '/@/renderer/api/types';
|
||||
import { useSongLyrics } from '/@/renderer/features/lyrics/queries/lyric-query';
|
||||
|
||||
const lyrics = isElectron() ? window.electron.lyrics : null;
|
||||
|
||||
|
|
@ -22,6 +27,11 @@ export const Lyrics = () => {
|
|||
const [source, setSource] = useState<string | null>(null);
|
||||
const [songLyrics, setSongLyrics] = useState<SynchronizedLyricsArray | string | null>(null);
|
||||
|
||||
const remoteLyrics = useSongLyrics({
|
||||
query: { songId: currentSong?.id ?? '' },
|
||||
serverId: currentServer?.id,
|
||||
});
|
||||
|
||||
const songRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -38,7 +48,7 @@ export const Lyrics = () => {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSong && !currentSong.lyrics) {
|
||||
if (currentSong && !currentSong.lyrics && !remoteLyrics.isLoading && !remoteLyrics.isSuccess) {
|
||||
lyrics?.fetchLyrics(currentSong);
|
||||
}
|
||||
|
||||
|
|
@ -46,7 +56,7 @@ export const Lyrics = () => {
|
|||
|
||||
setOverride(null);
|
||||
setSource(null);
|
||||
}, [currentSong]);
|
||||
}, [currentSong, remoteLyrics.isLoading, remoteLyrics.isSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
let lyrics: string | null = null;
|
||||
|
|
@ -57,6 +67,10 @@ export const Lyrics = () => {
|
|||
setSource(currentServer?.name ?? 'music server');
|
||||
} else if (override) {
|
||||
lyrics = override;
|
||||
} else if (remoteLyrics.isSuccess) {
|
||||
setSource(currentServer?.name ?? 'music server');
|
||||
setSongLyrics(remoteLyrics.data!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lyrics) {
|
||||
|
|
@ -82,16 +96,23 @@ export const Lyrics = () => {
|
|||
} else {
|
||||
setSongLyrics(null);
|
||||
}
|
||||
}, [currentServer?.name, currentSong, override]);
|
||||
}, [currentServer?.name, currentSong, override, remoteLyrics.data, remoteLyrics.isSuccess]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
{songLyrics &&
|
||||
(Array.isArray(songLyrics) ? (
|
||||
<SynchronizedLyrics lyrics={songLyrics} />
|
||||
) : (
|
||||
<UnsynchronizedLyrics lyrics={songLyrics} />
|
||||
))}
|
||||
{!songLyrics && (
|
||||
<Center>
|
||||
<Group>
|
||||
<RiInformationFill size="2rem" />
|
||||
<TextTitle
|
||||
order={3}
|
||||
weight={700}
|
||||
>
|
||||
No lyrics found
|
||||
</TextTitle>
|
||||
</Group>
|
||||
</Center>
|
||||
)}
|
||||
{source && (
|
||||
<LyricLine
|
||||
key="provided-by"
|
||||
|
|
@ -99,6 +120,12 @@ export const Lyrics = () => {
|
|||
text={`Provided by: ${source}`}
|
||||
/>
|
||||
)}
|
||||
{songLyrics &&
|
||||
(Array.isArray(songLyrics) ? (
|
||||
<SynchronizedLyrics lyrics={songLyrics} />
|
||||
) : (
|
||||
<UnsynchronizedLyrics lyrics={songLyrics} />
|
||||
))}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
25
src/renderer/features/lyrics/queries/lyric-query.ts
Normal file
25
src/renderer/features/lyrics/queries/lyric-query.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { LyricsQuery } from '/@/renderer/api/types';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { ServerType } from '/@/renderer/types';
|
||||
|
||||
export const useSongLyrics = (args: QueryHookArgs<LyricsQuery>) => {
|
||||
const { query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
// Note: This currently fetches for every song, even if it shouldn't have
|
||||
// lyrics, because for some reason HasLyrics is not exposed. Thus, ignore the error
|
||||
onError: () => {},
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
// This should only be called for Jellyfin. Return null to ignore errors
|
||||
if (server.type !== ServerType.JELLYFIN) return null;
|
||||
return controller.getLyrics({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
|
||||
});
|
||||
};
|
||||
|
|
@ -10,11 +10,10 @@ import { PlaybackType, PlayerStatus } from '/@/renderer/types';
|
|||
import { LyricLine } from '/@/renderer/features/lyrics/lyric-line';
|
||||
import isElectron from 'is-electron';
|
||||
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
||||
import { SynchronizedLyricsArray } from '/@/renderer/api/types';
|
||||
|
||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||
|
||||
export type SynchronizedLyricsArray = Array<[number, string]>;
|
||||
|
||||
interface SynchronizedLyricsProps {
|
||||
lyrics: SynchronizedLyricsArray;
|
||||
}
|
||||
|
|
@ -40,7 +39,12 @@ export const SynchronizedLyrics = ({ lyrics }: SynchronizedLyricsProps) => {
|
|||
// whether to proceed or stop
|
||||
const timerEpoch = useRef(0);
|
||||
|
||||
const followRef = useRef<boolean>(settings.follow);
|
||||
const delayMsRef = useRef(settings.delayMs);
|
||||
const followRef = useRef(settings.follow);
|
||||
|
||||
useEffect(() => {
|
||||
delayMsRef.current = settings.delayMs;
|
||||
}, [settings.delayMs]);
|
||||
|
||||
useEffect(() => {
|
||||
// Copy the follow settings into a ref that can be accessed in the timeout
|
||||
|
|
@ -127,7 +131,7 @@ export const SynchronizedLyrics = ({ lyrics }: SynchronizedLyricsProps) => {
|
|||
}
|
||||
|
||||
if (index !== lyricRef.current!.length - 1) {
|
||||
const [nextTime] = lyricRef.current![index + 1];
|
||||
const nextTime = lyricRef.current![index + 1][0];
|
||||
|
||||
const elapsed = performance.now() - start;
|
||||
|
||||
|
|
@ -149,7 +153,7 @@ export const SynchronizedLyrics = ({ lyrics }: SynchronizedLyricsProps) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
setCurrentLyric(timeInSec * 1000);
|
||||
setCurrentLyric(timeInSec * 1000 + delayMsRef.current);
|
||||
|
||||
return true;
|
||||
})
|
||||
|
|
@ -185,7 +189,7 @@ export const SynchronizedLyrics = ({ lyrics }: SynchronizedLyricsProps) => {
|
|||
clearTimeout(lyricTimer.current);
|
||||
}
|
||||
|
||||
setCurrentLyric(now * 1000);
|
||||
setCurrentLyric(now * 1000 + delayMsRef.current);
|
||||
}, [now, seeked, setCurrentLyric, status]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Switch } from '@mantine/core';
|
||||
import { NumberInput, Switch } from '@mantine/core';
|
||||
import {
|
||||
SettingOption,
|
||||
SettingsSection,
|
||||
|
|
@ -82,6 +82,28 @@ export const LyricSettings = () => {
|
|||
isHidden: !isElectron(),
|
||||
title: 'Providers to fetch music',
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<NumberInput
|
||||
defaultValue={settings.delayMs}
|
||||
step={10}
|
||||
width={100}
|
||||
onBlur={(e) => {
|
||||
const value = Number(e.currentTarget.value);
|
||||
setSettings({
|
||||
lyrics: {
|
||||
...settings,
|
||||
delayMs: value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
),
|
||||
description:
|
||||
'Lyric offset (in milliseconds). Positive values mean that lyrics are shown later, and negative mean that lyrics are shown earlier',
|
||||
isHidden: !isElectron(),
|
||||
title: 'Lyric offset',
|
||||
},
|
||||
];
|
||||
|
||||
return <SettingsSection options={lyricOptions} />;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue