add jellyfin, improvements

This commit is contained in:
Kendall Garner 2023-06-02 23:54:34 -07:00 committed by Jeff
parent 85d2576bdc
commit 58f38b2655
11 changed files with 168 additions and 17 deletions

View file

@ -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>
);
};

View 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),
});
};

View file

@ -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(() => {