'use client'; import React, { useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import { useAudioPlayer } from '@/app/components/AudioPlayerContext'; import { Progress } from '@/components/ui/progress'; import { lrcLibClient } from '@/lib/lrclib'; import { FaPlay, FaPause, FaVolumeHigh, FaForward, FaBackward, FaVolumeXmark, FaShuffle, FaRepeat, FaXmark, FaQuoteLeft, FaListUl } from "react-icons/fa6"; import { Card, CardContent } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; interface LyricLine { time: number; text: string; } interface FullScreenPlayerProps { isOpen: boolean; onClose: () => void; onOpenQueue?: () => void; } export const FullScreenPlayer: React.FC = ({ isOpen, onClose, onOpenQueue }) => { const { currentTrack, playPreviousTrack, playNextTrack, shuffle, toggleShuffle } = useAudioPlayer(); const [progress, setProgress] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [volume, setVolume] = useState(1); const [showVolumeSlider, setShowVolumeSlider] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [dominantColor, setDominantColor] = useState('#1a1a1a'); const [lyrics, setLyrics] = useState([]); const [currentLyricIndex, setCurrentLyricIndex] = useState(-1); const [showLyrics, setShowLyrics] = useState(true); const lyricsRef = useRef(null); // Load lyrics when track changes useEffect(() => { const loadLyrics = async () => { if (!currentTrack) { setLyrics([]); return; } try { const lyricsData = await lrcLibClient.searchTrack( currentTrack.artist, currentTrack.name, currentTrack.album, currentTrack.duration ); if (lyricsData && lyricsData.syncedLyrics) { const parsedLyrics = lrcLibClient.parseSyncedLyrics(lyricsData.syncedLyrics); setLyrics(parsedLyrics); } else { setLyrics([]); } } catch (error) { console.error('Failed to load lyrics:', error); setLyrics([]); } }; loadLyrics(); }, [currentTrack]); // Update current lyric index based on time (with optimization to prevent unnecessary updates) useEffect(() => { const newIndex = lrcLibClient.getCurrentLyricIndex(lyrics, currentTime); if (newIndex !== currentLyricIndex) { setCurrentLyricIndex(newIndex); } }, [lyrics, currentTime, currentLyricIndex]); // Auto-scroll lyrics using lyricsRef useEffect(() => { if (currentLyricIndex >= 0 && lyrics.length > 0 && showLyrics && lyricsRef.current) { const scrollTimeout = setTimeout(() => { // Find the ScrollArea viewport const scrollViewport = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement; const currentLyricElement = lyricsRef.current?.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement; if (scrollViewport && currentLyricElement) { const containerHeight = scrollViewport.clientHeight; const elementTop = currentLyricElement.offsetTop; const elementHeight = currentLyricElement.offsetHeight; // Calculate scroll position to center the current lyric const targetScrollTop = elementTop - (containerHeight / 2) + (elementHeight / 2); scrollViewport.scrollTo({ top: Math.max(0, targetScrollTop), behavior: 'smooth' }); } }, 100); return () => clearTimeout(scrollTimeout); } }, [currentLyricIndex, showLyrics]); // Reset lyrics to top when song changes useEffect(() => { if (currentTrack && showLyrics && lyricsRef.current) { // Reset scroll position using lyricsRef const resetScroll = () => { const scrollViewport = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement; if (scrollViewport) { scrollViewport.scrollTo({ top: 0, behavior: 'instant' // Use instant for track changes }); } }; // Small delay to ensure DOM is ready const resetTimeout = setTimeout(() => { resetScroll(); setCurrentLyricIndex(-1); }, 50); return () => clearTimeout(resetTimeout); } }, [currentTrack?.id, showLyrics]); // Only reset when track ID changes // Sync with main audio player (improved responsiveness) useEffect(() => { const syncWithMainPlayer = () => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (mainAudio && currentTrack) { const newCurrentTime = mainAudio.currentTime; const newDuration = mainAudio.duration || 0; const newIsPlaying = !mainAudio.paused; // Always update playing state immediately setIsPlaying(newIsPlaying); setCurrentTime(newCurrentTime); setDuration(newDuration); setVolume(mainAudio.volume); if (newDuration > 0) { const newProgress = (newCurrentTime / newDuration) * 100; setProgress(newProgress); } } }; if (isOpen && currentTrack) { // Initial sync syncWithMainPlayer(); // Set up interval to keep syncing const interval = setInterval(syncWithMainPlayer, 100); return () => clearInterval(interval); } }, [isOpen, currentTrack?.id]); // React to track changes // Extract dominant color from cover art useEffect(() => { if (!currentTrack?.coverArt) return; const img = document.createElement('img'); img.crossOrigin = 'anonymous'; img.onload = () => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (ctx) { canvas.width = img.width; canvas.height = img.height; ctx.drawImage(img, 0, 0); // Simple dominant color extraction const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; let r = 0, g = 0, b = 0; for (let i = 0; i < data.length; i += 4) { r += data[i]; g += data[i + 1]; b += data[i + 2]; } const pixelCount = data.length / 4; r = Math.floor(r / pixelCount); g = Math.floor(g / pixelCount); b = Math.floor(b / pixelCount); setDominantColor(`rgb(${r}, ${g}, ${b})`); } } catch (error) { console.error('Failed to extract color:', error); } }; img.src = currentTrack.coverArt; }, [currentTrack]); const togglePlayPause = () => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (!mainAudio) return; if (isPlaying) { mainAudio.pause(); } else { mainAudio.play(); } }; const handleSeek = (e: React.MouseEvent) => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (!mainAudio || !duration) return; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = (x / rect.width) * 100; const newTime = (percentage / 100) * duration; mainAudio.currentTime = newTime; setCurrentTime(newTime); }; const handleVolumeChange = (e: React.ChangeEvent) => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (!mainAudio) return; const newVolume = parseInt(e.target.value) / 100; mainAudio.volume = newVolume; setVolume(newVolume); }; const handleLyricClick = (time: number) => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (!mainAudio) return; mainAudio.currentTime = time; setCurrentTime(time); // Update progress bar as well if (duration > 0) { const newProgress = (time / duration) * 100; setProgress(newProgress); } }; const formatTime = (seconds: number) => { if (!seconds || isNaN(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; if (!isOpen || !currentTrack) return null; return (
{/* Blurred background image */} {currentTrack.coverArt && (
)} {/* Overlay for better contrast */}
{/* Header */}

{onOpenQueue && ( )}
{/* Main Content */}
{/* Left Side - Album Art and Controls */}
0 ? 'justify-center lg:justify-start' : 'justify-center' }`}> {/* Album Art */}
{currentTrack.album}
{/* Track Info */}

{currentTrack.name}

{currentTrack.artist}

{/* Progress */}
{formatTime(currentTime)} {formatTime(duration)}
{/* Controls */}
{lyrics.length > 0 && ( )}
{/* Volume and Lyrics Toggle */}
{showVolumeSlider && (
setShowVolumeSlider(false)} >
)}
{/* Right Side - Lyrics */} {showLyrics && lyrics.length > 0 && (
{lyrics.map((line, index) => (
handleLyricClick(line.time)} className={`text-sm sm:text-base lg:text-base leading-relaxed transition-all duration-300 break-words cursor-pointer hover:text-foreground hover:scale-102 ${ index === currentLyricIndex ? 'text-foreground font-semibold text-base sm:text-lg lg:text-xl scale-105' : index < currentLyricIndex ? 'text-foreground/60' : 'text-foreground/40' }`} style={{ wordWrap: 'break-word', overflowWrap: 'break-word', hyphens: 'auto', paddingBottom: '6px', paddingLeft: '8px' }} title={`Click to jump to ${formatTime(line.time)}`} > {line.text || '♪'}
))} {/* Add extra padding at the bottom to allow last lyric to center */}
)}
); };