'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 } 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; } export const FullScreenPlayer: React.FC = ({ isOpen, onClose }) => { const { currentTrack, playPreviousTrack, playNextTrack } = 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 (throttled to prevent infinite loops) useEffect(() => { let lastUpdate = 0; const throttleMs = 200; // Update at most every 200ms const syncWithMainPlayer = () => { const now = Date.now(); if (now - lastUpdate < throttleMs) return; lastUpdate = now; const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (mainAudio && currentTrack) { const newCurrentTime = mainAudio.currentTime; const newDuration = mainAudio.duration || 0; const newIsPlaying = !mainAudio.paused; // Only update state if values have changed significantly if (Math.abs(newCurrentTime - currentTime) > 0.5) { setCurrentTime(newCurrentTime); } if (Math.abs(newDuration - duration) > 0.1) { setDuration(newDuration); } if (newDuration > 0) { const newProgress = (newCurrentTime / newDuration) * 100; if (Math.abs(newProgress - progress) > 0.1) { setProgress(newProgress); } } if (newIsPlaying !== isPlaying) { setIsPlaying(newIsPlaying); } if (Math.abs(mainAudio.volume - volume) > 0.01) { setVolume(mainAudio.volume); } } }; if (isOpen) { // Initial sync syncWithMainPlayer(); // Set up interval to keep syncing const interval = setInterval(syncWithMainPlayer, 100); return () => clearInterval(interval); } }, [isOpen, currentTrack]); // Removed currentTime from dependencies to prevent loop // 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 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; const backgroundStyle = { background: `linear-gradient(135deg, ${dominantColor}40 0%, ${dominantColor}20 50%, transparent 100%)` }; return (
{/* Header */}

Now Playing

{/* Main Content */}
{/* Left Side - Album Art and Controls */}
{/* Album Art */}
{currentTrack.album}
{/* Track Info */}

{currentTrack.name}

{currentTrack.artist}

{/* Progress */}
{formatTime(currentTime)} {formatTime(duration)}
{/* Controls */}
{/* Volume and Lyrics Toggle */}
{lyrics.length > 0 && ( )} {showVolumeSlider && (
setShowVolumeSlider(false)} >
)}
{/* Right Side - Lyrics */} {showLyrics && lyrics.length > 0 && (
{lyrics.map((line, index) => (
{line.text || '♪'}
))} {/* Add extra padding at the bottom to allow last lyric to center */}
)}
); };