'use client'; import React, { useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useAudioPlayer } from '@/app/components/AudioPlayerContext'; import { Progress } from '@/components/ui/progress'; import { lrcLibClient } from '@/lib/lrclib'; import Link from 'next/link'; import { useIsMobile } from '@/hooks/use-mobile'; import { FaPlay, FaPause, FaVolumeHigh, FaForward, FaBackward, FaVolumeXmark, FaShuffle, FaRepeat, FaXmark, FaQuoteLeft, FaListUl } from "react-icons/fa6"; import { Heart } from 'lucide-react'; import { ScrollArea } from '@/components/ui/scroll-area'; interface LyricLine { time: number; text: string; } interface FullScreenPlayerProps { isOpen: boolean; onClose: () => void; onOpenQueue?: () => void; } type MobileTab = 'player' | 'lyrics' | 'queue'; export const FullScreenPlayer: React.FC = ({ isOpen, onClose, onOpenQueue }) => { const { currentTrack, playPreviousTrack, playNextTrack, shuffle, toggleShuffle, toggleCurrentTrackStar, queue } = useAudioPlayer(); const isMobile = useIsMobile(); const router = useRouter(); 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 [activeTab, setActiveTab] = useState('player'); const lyricsRef = useRef(null); // Debug logging for component changes useEffect(() => { console.log('πŸ” FullScreenPlayer state changed:', { isOpen, currentTrack, currentTrackKeys: currentTrack ? Object.keys(currentTrack) : 'null', queueLength: queue?.length || 0 }); }, [isOpen, currentTrack, queue?.length]); // 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.log('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 - Disabled on mobile to prevent iOS audio issues useEffect(() => { // Only auto-scroll on desktop to avoid iOS audio interference const shouldScroll = !isMobile && showLyrics && lyrics.length > 0; if (currentLyricIndex >= 0 && shouldScroll && lyricsRef.current) { const scrollTimeout = setTimeout(() => { try { const scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement; const currentLyricElement = lyricsRef.current?.querySelector(`[data-lyric-index="${currentLyricIndex}"]`) as HTMLElement; if (scrollContainer && currentLyricElement) { const containerHeight = scrollContainer.clientHeight; const elementTop = currentLyricElement.offsetTop; const elementHeight = currentLyricElement.offsetHeight; const targetScrollTop = elementTop - (containerHeight / 2) + (elementHeight / 2); scrollContainer.scrollTo({ top: Math.max(0, targetScrollTop), behavior: 'smooth' }); } } catch (error) { console.warn('Lyrics scroll failed:', error); } }, 200); return () => clearTimeout(scrollTimeout); } }, [currentLyricIndex, showLyrics, lyrics.length, isMobile]); // Reset lyrics to top when song changes - Disabled on mobile to prevent iOS audio issues useEffect(() => { // Only reset scroll on desktop to avoid iOS audio interference const shouldReset = !isMobile && showLyrics && lyrics.length > 0; if (currentTrack && shouldReset && lyricsRef.current) { const resetTimeout = setTimeout(() => { try { const scrollContainer = lyricsRef.current?.querySelector('[data-radix-scroll-area-viewport]') as HTMLElement; if (scrollContainer) { scrollContainer.scrollTo({ top: 0, behavior: 'instant' }); } } catch (error) { console.warn('Lyrics reset scroll failed:', error); } setCurrentLyricIndex(-1); }, 50); return () => clearTimeout(resetTimeout); } }, [currentTrack?.id, showLyrics, isMobile, lyrics.length]); // Sync with main audio player (improved responsiveness) useEffect(() => { const syncWithMainPlayer = () => { const mainAudio = document.querySelector('audio') as HTMLAudioElement; console.log('=== FULLSCREEN PLAYER AUDIO DEBUG ==='); console.log('currentTrack from context:', currentTrack); console.log('currentTrack keys:', currentTrack ? Object.keys(currentTrack) : 'null'); if (currentTrack) { console.log('currentTrack.url:', currentTrack.url); console.log('currentTrack.id:', currentTrack.id); console.log('currentTrack.name:', currentTrack.name); console.log('currentTrack.artist:', currentTrack.artist); } console.log('Audio element found:', !!mainAudio); if (mainAudio) { console.log('Audio element src:', mainAudio.src); console.log('Audio element currentSrc:', mainAudio.currentSrc); console.log('Audio state:', { currentTime: mainAudio.currentTime, duration: mainAudio.duration, paused: mainAudio.paused, ended: mainAudio.ended, readyState: mainAudio.readyState, networkState: mainAudio.networkState, error: mainAudio.error }); // Check if audio source matches current track if (currentTrack) { const audioSourceMatches = mainAudio.src === currentTrack.url || mainAudio.currentSrc === currentTrack.url; console.log('Audio source matches current track URL:', audioSourceMatches); if (!audioSourceMatches) { console.log('⚠️ Audio source mismatch!'); console.log('Expected:', currentTrack.url); console.log('Audio src:', mainAudio.src); console.log('Audio currentSrc:', mainAudio.currentSrc); } } } console.log('=========================================='); 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]); // 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.log('Failed to extract color:', error); } }; img.src = currentTrack.coverArt; }, [currentTrack]); const togglePlayPause = () => { console.log('🎡 FullScreenPlayer Toggle Play/Pause clicked'); // Find the main audio player's play/pause button and click it // This ensures we use the same logic as the main player const mainPlayButton = document.querySelector('[data-testid="play-pause-button"]') as HTMLButtonElement; if (mainPlayButton) { console.log('βœ… Found main play button, triggering click'); mainPlayButton.click(); } else { console.log('❌ Main play button not found, falling back to direct audio control'); // Fallback to direct audio control if button not found const mainAudio = document.querySelector('audio') as HTMLAudioElement; if (!mainAudio) { console.log('❌ No audio element found'); // Try to find ALL audio elements for debugging const allAudio = document.querySelectorAll('audio'); console.log('πŸ” Found audio elements:', allAudio.length); allAudio.forEach((audio, index) => { console.log(`Audio ${index}:`, { src: audio.src, currentSrc: audio.currentSrc, paused: audio.paused, hidden: audio.hidden, style: audio.style.display }); }); return; } console.log('πŸ” Detailed audio element state:'); console.log('- Audio src:', mainAudio.src); console.log('- Audio currentSrc:', mainAudio.currentSrc); console.log('- Audio paused:', mainAudio.paused); console.log('- Audio currentTime:', mainAudio.currentTime); console.log('- Audio duration:', mainAudio.duration); console.log('- Audio readyState:', mainAudio.readyState, '(0=HAVE_NOTHING, 1=HAVE_METADATA, 2=HAVE_CURRENT_DATA, 3=HAVE_FUTURE_DATA, 4=HAVE_ENOUGH_DATA)'); console.log('- Audio networkState:', mainAudio.networkState, '(0=EMPTY, 1=IDLE, 2=LOADING, 3=NO_SOURCE)'); console.log('- Audio error:', mainAudio.error); console.log('- Audio ended:', mainAudio.ended); console.log('- Audio seeking:', mainAudio.seeking); console.log('- Audio volume:', mainAudio.volume); console.log('- Audio muted:', mainAudio.muted); console.log('- Audio autoplay:', mainAudio.autoplay); console.log('- Audio loop:', mainAudio.loop); console.log('- Audio preload:', mainAudio.preload); console.log('- Audio crossOrigin:', mainAudio.crossOrigin); if (isPlaying) { console.log('⏸️ Attempting to pause audio'); try { mainAudio.pause(); console.log('βœ… Audio pause() succeeded'); } catch (error) { console.log('❌ Audio pause() failed:', error); } } else { console.log('▢️ Attempting to play audio'); // Check if audio has a valid source if (!mainAudio.src && !mainAudio.currentSrc) { console.log('❌ Audio has no source set!'); console.log('currentTrack:', currentTrack); if (currentTrack) { console.log('Setting audio source to:', currentTrack.url); mainAudio.src = currentTrack.url; mainAudio.load(); } } mainAudio.play().then(() => { console.log('βœ… Audio play() succeeded'); }).catch((error) => { console.log('❌ Audio play() failed:', error); console.log('Error details:', { name: error.name, message: error.message, code: error.code }); }); } } }; 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 (
{/* Enhanced Blurred background image */} {currentTrack.coverArt && (
{/* Main background */}
{/* Top gradient blur for mobile */}
{/* Bottom gradient blur for mobile */}
)} {/* Overlay for better contrast */}
{/* Mobile Close Handle */} {isMobile && (
)} {/* Desktop Header */} {!isMobile && (
{onOpenQueue && ( )}
)} {/* Main Content */}
{isMobile ? ( /* Mobile Tab Content */
{activeTab === 'player' && (
{/* Mobile Album Art */}
{currentTrack.album}
{/* Track Info - Left Aligned and Heart on Same Line */}

{currentTrack.name}

{currentTrack.artist} {currentTrack.album}
{/* Progress */}
{/* Time below progress on mobile */}
{formatTime(currentTime)} {formatTime(duration)}
{/* Controls */}
{/* Volume Slider */} {showVolumeSlider && (
setShowVolumeSlider(false)} >
)}
)} {activeTab === 'lyrics' && lyrics.length > 0 && (
{lyrics.map((line, index) => (
handleLyricClick(line.time)} className={`text-base leading-relaxed transition-all duration-300 break-words cursor-pointer hover:text-foreground px-2 ${ index === currentLyricIndex ? 'text-foreground font-bold text-xl' : index < currentLyricIndex ? 'text-foreground/60' : 'text-foreground/40' }`} style={{ wordWrap: 'break-word', overflowWrap: 'break-word', hyphens: 'auto', paddingBottom: '4px' }} title={`Click to jump to ${formatTime(line.time)}`} > {line.text || 'β™ͺ'}
))}
)} {activeTab === 'queue' && (
{queue.map((track, index) => (
{track.album}

{track.name}

{track.artist}

))}
)}
{/* Mobile Tab Bar */}
{lyrics.length > 0 && ( )}
) : ( /* Desktop Layout */
{/* Left Side - Album Art and Controls */}
{/* Album Art */}
{currentTrack.album}
{/* Track Info */}

{currentTrack.name}

{currentTrack.artist} {currentTrack.album}
{/* Progress */}
{formatTime(currentTime)} {formatTime(duration)}
{/* Controls */}
{/* Volume and Lyrics Toggle - Desktop Only */}
{lyrics.length > 0 && ( )} {showVolumeSlider && (
setShowVolumeSlider(false)} >
)}
{/* Right Side - Lyrics (Desktop Only) */} {showLyrics && lyrics.length > 0 && (
{lyrics.map((line, index) => (
handleLyricClick(line.time)} className={`text-base leading-relaxed transition-all duration-300 break-words cursor-pointer hover:text-foreground ${ index === currentLyricIndex ? 'text-foreground font-bold text-2xl' : index < currentLyricIndex ? 'text-foreground/60' : 'text-foreground/40' }`} style={{ wordWrap: 'break-word', overflowWrap: 'break-word', hyphens: 'auto', paddingBottom: '4px', paddingLeft: '8px' }} title={`Click to jump to ${formatTime(line.time)}`} > {line.text || 'β™ͺ'}
))}
)}
)}
); };