import { useEffect, useRef, useState } from 'react'; import { usePlayerStore } from '../store/playerStore'; import { fetchLyrics, parseLrc, LrcLine } from '../api/lrclib'; import { useTranslation } from 'react-i18next'; import type { Track } from '../store/playerStore'; interface Props { currentTrack: Track | null; } export default function LyricsPane({ currentTrack }: Props) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [syncedLines, setSyncedLines] = useState(null); const [plainLyrics, setPlainLyrics] = useState(null); const [notFound, setNotFound] = useState(false); const [fetchedFor, setFetchedFor] = useState(null); const hasSynced = syncedLines !== null && syncedLines.length > 0; const currentTime = usePlayerStore(s => hasSynced ? s.currentTime : 0); const lineRefs = useRef<(HTMLDivElement | null)[]>([]); const prevActive = useRef(-1); useEffect(() => { if (!currentTrack || currentTrack.id === fetchedFor) return; let cancelled = false; setSyncedLines(null); setPlainLyrics(null); setNotFound(false); setLoading(true); lineRefs.current = []; prevActive.current = -1; fetchLyrics( currentTrack.artist ?? '', currentTrack.title, currentTrack.album ?? '', currentTrack.duration ?? 0, ).then(result => { if (cancelled) return; setLoading(false); setFetchedFor(currentTrack.id); if (!result || (!result.syncedLyrics && !result.plainLyrics)) { setNotFound(true); return; } if (result.syncedLyrics) { const lines = parseLrc(result.syncedLyrics); setSyncedLines(lines.length > 0 ? lines : null); } setPlainLyrics(result.plainLyrics); }).catch(() => { if (!cancelled) { setLoading(false); setNotFound(true); } }); return () => { cancelled = true; }; }, [currentTrack?.id]); // eslint-disable-line react-hooks/exhaustive-deps // Reset when track changes useEffect(() => { setFetchedFor(null); }, [currentTrack?.id]); const activeIdx = hasSynced ? syncedLines!.reduce((acc, line, i) => (currentTime >= line.time ? i : acc), -1) : -1; useEffect(() => { if (activeIdx < 0 || activeIdx === prevActive.current) return; prevActive.current = activeIdx; lineRefs.current[activeIdx]?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, [activeIdx]); if (!currentTrack) { return (

{t('player.lyricsNotFound')}

); } return (
{loading &&

{t('player.lyricsLoading')}

} {notFound && !loading &&

{t('player.lyricsNotFound')}

} {hasSynced && (
{syncedLines!.map((line, i) => (
{ lineRefs.current[i] = el; }} className={`lyrics-line${i === activeIdx ? ' active' : ''}`} > {line.text || '\u00A0'}
))}
)} {!hasSynced && plainLyrics && (
{plainLyrics.split('\n').map((line, i) => (

{line || '\u00A0'}

))}
)}
); }