import { useQuery } from '@tanstack/react-query' import { ChevronDown, ListMusic, Pause, Play, Repeat2, Rewind, Shuffle, SkipForward, Trash2, Volume2 } from 'lucide-react' import { useMemo, useState } from 'react' import { FavoriteToggle } from '@/components/favorite-toggle' import { coverArtUrl, fetchFavorites } from '@/lib/api' import { usePlayerStore } from '@/stores/player-store' type LyricsLine = { time: number text: string } export function FullPlayer() { const currentTrack = usePlayerStore((state) => state.currentTrack) const queue = usePlayerStore((state) => state.queue) const isPlaying = usePlayerStore((state) => state.isPlaying) const currentTime = usePlayerStore((state) => state.currentTime) const duration = usePlayerStore((state) => state.duration) const volume = usePlayerStore((state) => state.volume) const shuffle = usePlayerStore((state) => state.shuffle) const repeatMode = usePlayerStore((state) => state.repeatMode) const togglePlayback = usePlayerStore((state) => state.togglePlayback) const playNext = usePlayerStore((state) => state.playNext) const playPrevious = usePlayerStore((state) => state.playPrevious) const playAtIndex = usePlayerStore((state) => state.playAtIndex) const removeFromQueue = usePlayerStore((state) => state.removeFromQueue) const toggleShuffle = usePlayerStore((state) => state.toggleShuffle) const cycleRepeatMode = usePlayerStore((state) => state.cycleRepeatMode) const setVolume = usePlayerStore((state) => state.setVolume) const seekTo = usePlayerStore((state) => state.seekTo) const setFullPlayerOpen = usePlayerStore((state) => state.setFullPlayerOpen) const [tab, setTab] = useState<'queue' | 'now' | 'lyrics'>('now') const favoritesQuery = useQuery({ queryKey: ['favorites'], queryFn: fetchFavorites, enabled: !!currentTrack, }) const lyricsQuery = useQuery({ queryKey: ['lrclib', currentTrack?.id], enabled: !!currentTrack, queryFn: async () => { const url = new URL('https://lrclib.net/api/get') url.searchParams.set('track_name', currentTrack?.title ?? '') url.searchParams.set('artist_name', currentTrack?.artistName ?? '') url.searchParams.set('album_name', currentTrack?.albumTitle ?? '') const response = await fetch(url.toString()) if (!response.ok) { throw new Error('lyrics not found') } return response.json() as Promise<{ syncedLyrics?: string; plainLyrics?: string }> }, }) const parsedLyrics = useMemo(() => parseLyrics(lyricsQuery.data?.syncedLyrics ?? lyricsQuery.data?.plainLyrics ?? ''), [lyricsQuery.data]) const favoriteTrackIds = useMemo(() => new Set((favoritesQuery.data?.tracks ?? []).map((item) => item.id)), [favoritesQuery.data]) const activeLine = useMemo(() => { if (parsedLyrics.length === 0) { return -1 } for (let index = parsedLyrics.length - 1; index >= 0; index -= 1) { if (parsedLyrics[index].time <= currentTime) { return index } } return -1 }, [currentTime, parsedLyrics]) if (!currentTrack) { return null } return (
setTab('queue')} /> setTab('now')} /> setTab('lyrics')} />
{tab === 'now' ? (
{currentTrack.coverArtId ? {currentTrack.title} : null}

{currentTrack.title}

{currentTrack.albumTitle} • {currentTrack.artistName}
Rock {new Date().getFullYear()} FLAC
) : null} {tab === 'lyrics' ? (
{parsedLyrics.length > 0 ? ( parsedLyrics.map((line, index) => (
{line.text}
)) ) : (
{lyricsQuery.isLoading ? 'Загружаю текст...' : 'Текст песни не найден в LRCLIB'}
)}
) : null} {tab === 'queue' ? (
{queue.map((track, index) => ( ))}
) : null}
{formatClock(currentTime)} seekTo(Number(event.target.value))} step={0.1} type="range" value={Math.min(currentTime, duration || 0)} /> {formatClock(duration)}
} onClick={toggleShuffle} /> } onClick={playPrevious} /> } onClick={playNext} /> } label={repeatMode === 'one' ? '1' : undefined} onClick={cycleRepeatMode} />
setVolume(Number(event.target.value))} step={0.01} type="range" value={volume} />
) } function PlayerTab({ active, label, onClick, }: { active: boolean label: string onClick: () => void }) { return ( ) } function MetaTag({ children }: { children: React.ReactNode }) { return
{children}
} function IconControl({ icon, active = false, label, onClick, }: { icon: React.ReactNode active?: boolean label?: string onClick?: () => void }) { return ( ) } function formatClock(value: number) { const minutes = Math.floor(value / 60) const seconds = Math.floor(value % 60) return `${minutes}:${seconds.toString().padStart(2, '0')}` } function parseLyrics(input: string): LyricsLine[] { if (!input) { return [] } return input .split('\n') .map((line) => { const match = line.match(/\[(\d{2}):(\d{2})(?:\.(\d{2}))?\](.*)/) if (!match) { return null } const minutes = Number(match[1]) const seconds = Number(match[2]) const hundredths = Number(match[3] ?? 0) return { time: minutes * 60 + seconds + hundredths / 100, text: match[4].trim(), } }) .filter((line): line is LyricsLine => !!line && !!line.text) }