import React, { useRef, useState, useEffect } from 'react'; import { Song } from '../types'; import { Play, Pause, SkipBack, SkipForward, Repeat, Shuffle, Download, Heart, MoreVertical, Volume2, VolumeX, Maximize2, Repeat1, ChevronDown, ChevronUp } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import { useResponsive } from '../context/ResponsiveContext'; import { useI18n } from '../context/I18nContext'; import { SongDropdownMenu } from './SongDropdownMenu'; import { ShareModal } from './ShareModal'; import { AlbumCover } from './AlbumCover'; interface PlayerProps { currentSong: Song | null; isPlaying: boolean; onTogglePlay: () => void; currentTime: number; duration: number; onSeek: (time: number) => void; onNext: () => void; onPrevious: () => void; volume: number; onVolumeChange: (val: number) => void; playbackRate: number; onPlaybackRateChange: (rate: number) => void; audioRef: React.RefObject; isShuffle: boolean; onToggleShuffle: () => void; repeatMode: 'none' | 'all' | 'one'; onToggleRepeat: () => void; isLiked: boolean; onToggleLike: () => void; onNavigateToSong?: (songId: string) => void; onOpenVideo?: () => void; onReusePrompt?: () => void; onAddToPlaylist?: () => void; onDelete?: () => void; onPlayFirst?: () => void; } export const Player: React.FC = ({ currentSong, isPlaying, onTogglePlay, currentTime, duration, onSeek, onNext, onPrevious, volume, onVolumeChange, playbackRate, onPlaybackRateChange, audioRef, isShuffle, onToggleShuffle, repeatMode, onToggleRepeat, isLiked, onToggleLike, onNavigateToSong, onOpenVideo, onReusePrompt, onAddToPlaylist, onDelete, onPlayFirst }) => { const { user } = useAuth(); const { isMobile } = useResponsive(); const { t } = useI18n(); const progressBarRef = useRef(null); const fullscreenProgressRef = useRef(null); const [isHoveringVolume, setIsHoveringVolume] = useState(false); const [showDropdown, setShowDropdown] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [shareModalOpen, setShareModalOpen] = useState(false); const [showSpeedMenu, setShowSpeedMenu] = useState(false); const speedMenuRef = useRef(null); // Close fullscreen on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isFullscreen) { setIsFullscreen(false); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isFullscreen]); // Close speed menu when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (speedMenuRef.current && !speedMenuRef.current.contains(event.target as Node)) { setShowSpeedMenu(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Show minimal player when no song is playing if (!currentSong) { return (
); } const formatTime = (time: number) => { if (isNaN(time)) return "0:00"; const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; const handleSeekInteraction = (e: React.MouseEvent, ref: React.RefObject) => { if (!ref.current || !duration) return; const rect = ref.current.getBoundingClientRect(); const x = e.clientX - rect.left; const width = rect.width; const percentage = Math.max(0, Math.min(1, x / width)); onSeek(percentage * duration); }; const progressPercent = duration ? (currentTime / duration) * 100 : 0; const handleDownload = async () => { if (!currentSong?.audioUrl) return; try { const response = await fetch(currentSong.audioUrl); const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${currentSong.title || 'song'}.mp3`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } catch (error) { console.error('Download failed:', error); } }; if (isMobile) { if (isFullscreen) { return (
{/* Header with close button */}
{t('nowPlaying')}
{/* Album Art */}
{currentSong.coverUrl ? ( cover { e.currentTarget.style.display = 'none'; e.currentTarget.nextElementSibling?.classList.remove('hidden'); }} /> ) : null}
{/* Song Info */}

{ setIsFullscreen(false); onNavigateToSong?.(currentSong.id); }} className="text-xl font-bold text-zinc-900 dark:text-white truncate" > {currentSong.title}

{currentSong.creator || 'Unknown Artist'}

{/* Progress Bar */}
handleSeekInteraction(e, fullscreenProgressRef)} >
{formatTime(currentTime)} {formatTime(duration || 0)}
{/* Main Controls */}
{/* Volume Control - Vertical */}
onVolumeChange(parseFloat(e.target.value))} className="w-32 h-8 -rotate-90 origin-center appearance-none bg-transparent cursor-pointer" style={{ WebkitAppearance: 'none', background: `linear-gradient(to right, rgb(236 72 153) 0%, rgb(236 72 153) ${volume * 100}%, rgb(228 228 231) ${volume * 100}%, rgb(228 228 231) 100%)` }} />
{/* Extra Actions */}
{onOpenVideo && ( )}
{showDropdown && (
setShowDropdown(false)} isOwner={user?.id === currentSong.userId} position="center" direction="up" onCreateVideo={onOpenVideo} onReusePrompt={onReusePrompt} onAddToPlaylist={onAddToPlaylist} onDelete={onDelete} onShare={() => setShareModalOpen(true)} />
)} setShareModalOpen(false)} song={currentSong} />
); } return (
{/* Progress Bar - taller for touch */}
handleSeekInteraction(e, progressBarRef)} >
{/* Main content: Song info left, controls right */}
{/* Song Info - takes available space, tap to expand */}
setIsFullscreen(true)} >
{currentSong.coverUrl ? ( cover { e.currentTarget.style.display = 'none'; }} /> ) : null} {!currentSong.coverUrl && }

{currentSong.title}

{currentSong.creator || 'Unknown Artist'}

{/* Mobile Controls - compact */}
); } // Desktop fullscreen mode if (isFullscreen) { return (
setIsFullscreen(false)} > {/* Header with close button */}
e.stopPropagation()}> {t('nowPlaying')}
{/* Main content area */}
e.stopPropagation()}>
{/* Album Art */}
{currentSong.coverUrl ? ( cover { e.currentTarget.style.display = 'none'; e.currentTarget.nextElementSibling?.classList.remove('hidden'); }} /> ) : null}
{/* Right side: Song info and controls */}
{/* Song Info */}

{ setIsFullscreen(false); onNavigateToSong?.(currentSong.id); }} className="text-2xl lg:text-3xl font-bold text-zinc-900 dark:text-white truncate cursor-pointer hover:underline" > {currentSong.title}

{currentSong.creator || 'Unknown Artist'}

{/* Progress Bar */}
handleSeekInteraction(e, fullscreenProgressRef)} >
{formatTime(currentTime)} {formatTime(duration || 0)}
{/* Main Controls */}
{/* Playback Speed Dropdown */}
{showSpeedMenu && (
{[0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0].map((rate) => ( ))}
)}
{/* Volume Control */}
onVolumeChange(parseFloat(e.target.value))} className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" />
{/* Extra Actions */}
{onOpenVideo && ( )}
{showDropdown && ( setShowDropdown(false)} isOwner={user?.id === currentSong.userId} position="center" direction="up" onCreateVideo={onOpenVideo} onReusePrompt={onReusePrompt} onAddToPlaylist={onAddToPlaylist} onDelete={onDelete} onShare={() => setShareModalOpen(true)} /> )}
setShareModalOpen(false)} song={currentSong} />
); } return (
{/* Progress Bar */}
handleSeekInteraction(e, progressBarRef)} >
{/* Hit area for easier clicking */}
{/* Song Info */}
{currentSong.coverUrl ? ( cover { e.currentTarget.style.display = 'none'; }} /> ) : null} {!currentSong.coverUrl && }

onNavigateToSong?.(currentSong.id)} className="text-xs sm:text-sm font-bold text-zinc-900 dark:text-white truncate cursor-pointer hover:underline" > {currentSong.title}

{currentSong.creator || 'Unknown Artist'}

{/* Controls */}
{/* Volume & Extras */}
{formatTime(currentTime)} / {formatTime(duration || 0)} {/* Playback Speed */}
{showSpeedMenu && (
{[0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0].map((rate) => ( ))}
)}
{/* Volume Control with Vertical Slider */}
setIsHoveringVolume(true)} onMouseLeave={() => setIsHoveringVolume(false)} > {/* Vertical Volume Slider */} {isHoveringVolume && (
onVolumeChange(parseFloat(e.target.value))} className="w-24 h-8 -rotate-90 origin-center appearance-none bg-transparent cursor-pointer" style={{ WebkitAppearance: 'none', background: `linear-gradient(to right, rgb(236 72 153) 0%, rgb(236 72 153) ${volume * 100}%, rgb(228 228 231) ${volume * 100}%, rgb(228 228 231) 100%)` }} />
{Math.round(volume * 100)}%
)}
setShowDropdown(false)} isOwner={user?.id === currentSong.userId} position="right" direction="up" onCreateVideo={onOpenVideo} onReusePrompt={onReusePrompt} onAddToPlaylist={onAddToPlaylist} onDelete={onDelete} onShare={() => setShareModalOpen(true)} />
setShareModalOpen(false)} song={currentSong} />
); };