From 3f78c3f42026f82bdb8d4c57ea0e32d3a1c5a5f4 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 5 Jun 2023 02:50:01 -0700 Subject: [PATCH] Move all lyrics fetching logic into query --- src/renderer/features/lyrics/lyrics.tsx | 153 ++++-------------- .../features/lyrics/queries/lyric-query.ts | 98 ++++++++++- 2 files changed, 120 insertions(+), 131 deletions(-) diff --git a/src/renderer/features/lyrics/lyrics.tsx b/src/renderer/features/lyrics/lyrics.tsx index f652eb10..d96fe5a8 100644 --- a/src/renderer/features/lyrics/lyrics.tsx +++ b/src/renderer/features/lyrics/lyrics.tsx @@ -1,132 +1,33 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; import { Center, Group } from '@mantine/core'; -import isElectron from 'is-electron'; import { ErrorBoundary } from 'react-error-boundary'; -import { ErrorFallback } from '/@/renderer/features/action-required'; -import { getServerById, useCurrentSong } from '/@/renderer/store'; -import { UnsynchronizedLyrics } from '/@/renderer/features/lyrics/unsynchronized-lyrics'; import { RiInformationFill } from 'react-icons/ri'; -import { TextTitle } from '/@/renderer/components'; -import { - InternetProviderLyricResponse, - LyricOverride, - LyricsResponse, - SynchronizedLyricsArray, -} from '/@/renderer/api/types'; -import { useSongLyrics } from '/@/renderer/features/lyrics/queries/lyric-query'; +import { useSongLyrics } from './queries/lyric-query'; import { SynchronizedLyrics } from './synchronized-lyrics'; - -const lyrics = isElectron() ? window.electron.lyrics : null; - -const ipc = isElectron() ? window.electron.ipc : null; - -// use by https://github.com/ustbhuangyi/lyric-parser -const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)\n/g; +import { Spinner, TextTitle } from '/@/renderer/components'; +import { ErrorFallback } from '/@/renderer/features/action-required'; +import { UnsynchronizedLyrics } from '/@/renderer/features/lyrics/unsynchronized-lyrics'; +import { getServerById, useCurrentSong } from '/@/renderer/store'; export const Lyrics = () => { const currentSong = useCurrentSong(); const currentServer = getServerById(currentSong?.serverId); - const [overrideLyrics, setOverrideLyrics] = useState(null); - const [overrideData, setOverrideData] = useState(null); - const [source, setSource] = useState(null); - const [songLyrics, setSongLyrics] = useState(null); - - const remoteLyrics = useSongLyrics({ - options: { enabled: !!currentSong }, - query: { songId: currentSong?.id ?? '' }, - serverId: currentServer?.id, - }); - - const songRef = useRef(null); - - useEffect(() => { - lyrics?.remoteLyricsListener( - ( - _event: any, - songName: string, - lyricSource: string, - lyric: InternetProviderLyricResponse, - ) => { - if (songName === songRef.current) { - const { lyrics, ...rest } = lyric; - setSource(lyricSource); - - setOverrideData(rest); - setOverrideLyrics(lyrics); - } - }, - ); - - return () => { - ipc?.removeAllListeners('lyric-get'); - }; - }, []); - - useEffect(() => { - const hasTaggedLyrics = currentSong && currentSong.lyrics; - const hasLyricsResponse = - !remoteLyrics.isLoading && remoteLyrics?.isSuccess && remoteLyrics?.data !== null; - - if (!hasTaggedLyrics && !hasLyricsResponse) { - lyrics?.fetchRemoteLyrics(currentSong); - } - - songRef.current = currentSong?.name ?? null; - - setOverrideData(null); - setOverrideLyrics(null); - setSource(null); - }, [currentSong, remoteLyrics.isLoading, remoteLyrics?.data, remoteLyrics?.isSuccess]); - - useEffect(() => { - let lyrics: string | null = null; - - if (currentSong?.lyrics) { - lyrics = currentSong.lyrics; - - setSource(currentServer?.name ?? 'music server'); - } else if (overrideLyrics) { - lyrics = overrideLyrics; - } else if (remoteLyrics.data) { - setSource(currentServer?.name ?? 'music server'); - setSongLyrics(remoteLyrics.data); - return; - } - - if (lyrics) { - const synchronizedLines = lyrics.matchAll(timeExp); - - const synchronizedTimes: SynchronizedLyricsArray = []; - - for (const line of synchronizedLines) { - const [, minute, sec, ms, text] = line; - const minutes = parseInt(minute, 10); - const seconds = parseInt(sec, 10); - const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10; - - const timeInMilis = (minutes * 60 + seconds) * 1000 + milis; - synchronizedTimes.push([timeInMilis, text]); - } - - if (synchronizedTimes.length === 0) { - setSongLyrics(lyrics); - } else { - setSongLyrics(synchronizedTimes); - } - } else { - setSongLyrics(null); - } - }, [currentServer?.name, currentSong, overrideLyrics, remoteLyrics.data]); - - const clearOverride = useCallback(() => { - setOverrideData(null); - setOverrideLyrics(null); - }, []); + const { data, isLoading } = useSongLyrics( + { + query: { songId: currentSong?.id || '' }, + serverId: currentServer?.id, + }, + currentSong, + ); return ( - {!songLyrics ? ( + {isLoading ? ( + + ) : !data?.lyrics ? (
@@ -140,19 +41,19 @@ export const Lyrics = () => {
) : ( <> - {Array.isArray(songLyrics) ? ( + {Array.isArray(data.lyrics) ? ( {}} /> ) : ( {}} /> )} diff --git a/src/renderer/features/lyrics/queries/lyric-query.ts b/src/renderer/features/lyrics/queries/lyric-query.ts index 8db081a7..ab859192 100644 --- a/src/renderer/features/lyrics/queries/lyric-query.ts +++ b/src/renderer/features/lyrics/queries/lyric-query.ts @@ -1,12 +1,43 @@ import { useQuery } from '@tanstack/react-query'; -import { LyricsQuery } from '/@/renderer/api/types'; +import { + LyricsQuery, + QueueSong, + SynchronizedLyricsArray, + InternetProviderLyricResponse, +} from '/@/renderer/api/types'; import { QueryHookArgs } from '/@/renderer/lib/react-query'; -import { getServerById } from '/@/renderer/store'; -import { controller } from '/@/renderer/api/controller'; +import { getServerById, useLyricsSettings } from '/@/renderer/store'; import { queryKeys } from '/@/renderer/api/query-keys'; import { ServerType } from '/@/renderer/types'; +import { api } from '/@/renderer/api'; +import isElectron from 'is-electron'; -export const useSongLyrics = (args: QueryHookArgs) => { +const lyricsIpc = isElectron() ? window.electron.lyrics : null; + +// use by https://github.com/ustbhuangyi/lyric-parser +const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)\n/g; + +const formatLyrics = (lyrics: string) => { + const synchronizedLines = lyrics.matchAll(timeExp); + const formattedLyrics: SynchronizedLyricsArray = []; + + for (const line of synchronizedLines) { + const [, minute, sec, ms, text] = line; + const minutes = parseInt(minute, 10); + const seconds = parseInt(sec, 10); + const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10; + + const timeInMilis = (minutes * 60 + seconds) * 1000 + milis; + formattedLyrics.push([timeInMilis, text]); + } + + // If no synchronized lyrics were found, return the original lyrics + if (formattedLyrics.length === 0) return lyrics; + + return formattedLyrics; +}; + +export const useServerLyrics = (args: QueryHookArgs) => { const { query, serverId } = args; const server = getServerById(serverId); @@ -18,8 +49,65 @@ export const useSongLyrics = (args: QueryHookArgs) => { 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 }); + return api.controller.getLyrics({ apiClientProps: { server, signal }, query }); }, queryKey: queryKeys.songs.lyrics(server?.id || '', query), }); }; + +export const useSongLyrics = (args: QueryHookArgs, song: QueueSong | undefined) => { + const { query } = args; + const { fetch } = useLyricsSettings(); + const server = getServerById(song?.serverId); + + return useQuery({ + cacheTime: 1000 * 60 * 10, + enabled: !!song && !!server, + onError: () => {}, + queryFn: async ({ signal }) => { + if (!server) throw new Error('Server not found'); + if (!song) return null; + + if (song.lyrics) { + return { + artist: song.artists?.[0]?.name, + lyrics: formatLyrics(song.lyrics), + name: song.name, + source: server?.name ?? 'music server', + }; + } + + if (server.type === ServerType.JELLYFIN) { + const jfLyrics = await api.controller.getLyrics({ + apiClientProps: { server, signal }, + query: { songId: song.id }, + }); + + if (jfLyrics) { + return { + artist: song.artists?.[0]?.name, + lyrics: jfLyrics, + name: song.name, + source: server?.name ?? 'music server', + }; + } + } + + if (fetch) { + const remoteLyricsResult: InternetProviderLyricResponse | null = + await lyricsIpc?.fetchRemoteLyrics(song); + + if (remoteLyricsResult) { + return { + ...remoteLyricsResult, + lyrics: formatLyrics(remoteLyricsResult.lyrics), + }; + } + } + + return null; + }, + queryKey: queryKeys.songs.lyrics(server?.id || '', query), + staleTime: 1000 * 60 * 2, + }); +};