"use client"; import { Play, Pause, SkipBack, SkipForward, Repeat, Shuffle, Volume2, VolumeX, Download, Disc, PlusCircle, Mic2, Heart, Loader2, ListMusic, MonitorSpeaker, Maximize2 } from 'lucide-react'; import { usePlayer } from "@/context/PlayerContext"; import { useEffect, useRef, useState } from "react"; import TechSpecs from './TechSpecs'; import AddToPlaylistModal from "@/components/AddToPlaylistModal"; import LyricsDetail from './LyricsDetail'; export default function PlayerBar() { const { currentTrack, isPlaying, isBuffering, togglePlay, setBuffering, likedTracks, toggleLike, nextTrack, prevTrack, shuffle, toggleShuffle, repeatMode, toggleRepeat, audioQuality, isLyricsOpen, toggleLyrics } = usePlayer(); const audioRef = useRef(null); const wakeLockRef = useRef(null); const [progress, setProgress] = useState(0); const [duration, setDuration] = useState(0); const [volume, setVolume] = useState(1); // Modal State const [isAddToPlaylistOpen, setIsAddToPlaylistOpen] = useState(false); // isLyricsOpen is now in context const [isTechSpecsOpen, setIsTechSpecsOpen] = useState(false); const [isFullScreenPlayerOpen, setIsFullScreenPlayerOpen] = useState(false); const [isCoverModalOpen, setIsCoverModalOpen] = useState(false); // Wake Lock API - Keeps device awake during playback (for FiiO/Android) useEffect(() => { const requestWakeLock = async () => { if ('wakeLock' in navigator && isPlaying) { try { wakeLockRef.current = await navigator.wakeLock.request('screen'); console.log('Wake Lock acquired for background playback'); wakeLockRef.current.addEventListener('release', () => { console.log('Wake Lock released'); }); } catch (err) { console.log('Wake Lock not available:', err); } } }; const releaseWakeLock = async () => { if (wakeLockRef.current) { await wakeLockRef.current.release(); wakeLockRef.current = null; } }; if (isPlaying) { requestWakeLock(); } else { releaseWakeLock(); } // Re-acquire wake lock when page becomes visible again const handleVisibilityChange = async () => { if (document.visibilityState === 'visible' && isPlaying) { await requestWakeLock(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => { releaseWakeLock(); document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [isPlaying]); // Prevent audio pause on visibility change (screen off) - Critical for FiiO useEffect(() => { const handleVisibilityChange = () => { // When screen turns off, Android might pause audio // We explicitly resume if we should be playing if (document.visibilityState === 'hidden' && isPlaying && audioRef.current) { // Use setTimeout to ensure audio continues after visibility change setTimeout(() => { if (audioRef.current && audioRef.current.paused && isPlaying) { audioRef.current.play().catch(e => console.log('Resume on hidden:', e)); } }, 100); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => document.removeEventListener('visibilitychange', handleVisibilityChange); }, [isPlaying]); useEffect(() => { if (currentTrack && audioRef.current && currentTrack.url) { // Prevent reloading if URL hasn't changed const isSameUrl = audioRef.current.src === currentTrack.url || (currentTrack.url.startsWith('/') && audioRef.current.src.endsWith(currentTrack.url)) || (audioRef.current.src.includes(currentTrack.id)); // Fallback for stream IDs if (isSameUrl) return; audioRef.current.src = currentTrack.url; if (isPlaying) { audioRef.current.play().catch(e => console.error("Play error:", e)); } } }, [currentTrack?.url]); // Media Session API (Lock Screen Controls) useEffect(() => { if (!currentTrack || !('mediaSession' in navigator)) return; navigator.mediaSession.metadata = new MediaMetadata({ title: currentTrack.title, artist: currentTrack.artist, album: currentTrack.album, artwork: [ { src: currentTrack.cover_url, sizes: '96x96', type: 'image/jpeg' }, { src: currentTrack.cover_url, sizes: '128x128', type: 'image/jpeg' }, { src: currentTrack.cover_url, sizes: '192x192', type: 'image/jpeg' }, { src: currentTrack.cover_url, sizes: '256x256', type: 'image/jpeg' }, { src: currentTrack.cover_url, sizes: '384x384', type: 'image/jpeg' }, { src: currentTrack.cover_url, sizes: '512x512', type: 'image/jpeg' }, ] }); // Action Handlers navigator.mediaSession.setActionHandler('play', () => { togglePlay(); navigator.mediaSession.playbackState = "playing"; }); navigator.mediaSession.setActionHandler('pause', () => { togglePlay(); navigator.mediaSession.playbackState = "paused"; }); navigator.mediaSession.setActionHandler('previoustrack', () => prevTrack()); navigator.mediaSession.setActionHandler('nexttrack', () => nextTrack()); navigator.mediaSession.setActionHandler('seekto', (details) => { if (details.seekTime !== undefined && audioRef.current) { audioRef.current.currentTime = details.seekTime; setProgress(details.seekTime); } }); }, [currentTrack]); // access to togglePlay etc. via closure is safe from context useEffect(() => { if (audioRef.current) { if (isPlaying) { audioRef.current.play().catch(e => console.error("Play error:", e)); if ('mediaSession' in navigator) navigator.mediaSession.playbackState = "playing"; } else { audioRef.current.pause(); if ('mediaSession' in navigator) navigator.mediaSession.playbackState = "paused"; } } }, [isPlaying]); // Volume Effect useEffect(() => { if (audioRef.current) { audioRef.current.volume = volume; } }, [volume]); const handleTimeUpdate = () => { if (audioRef.current) { setProgress(audioRef.current.currentTime); if (!isNaN(audioRef.current.duration)) { setDuration(audioRef.current.duration); } // Update Position State for standard progress bar on lock screen // Throttle this in real apps, but for simplicity: if ('mediaSession' in navigator && !isNaN(audioRef.current.duration)) { try { navigator.mediaSession.setPositionState({ duration: audioRef.current.duration, playbackRate: audioRef.current.playbackRate, position: audioRef.current.currentTime }); } catch (e) { // Ignore errors (often due to duration being infinite/NaN at start) } } } }; const handleSeek = (e: React.ChangeEvent) => { const time = parseFloat(e.target.value); if (audioRef.current) { audioRef.current.currentTime = time; setProgress(time); } }; const handleVolume = (e: React.ChangeEvent) => { const vol = parseFloat(e.target.value); setVolume(vol); }; const handleDownload = () => { if (!currentTrack) return; const apiUrl = process.env.NEXT_PUBLIC_API_URL || ''; const url = `${apiUrl}/api/download?id=${currentTrack.id}&title=${encodeURIComponent(currentTrack.title)}`; window.open(url, '_blank'); }; 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')}`; }; if (!currentTrack) return null; return ( ); }