jefflix-website/components/music/mini-player.tsx

129 lines
4.7 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useMusicPlayer, isRadioTrack } from './music-provider'
import { FullScreenPlayer } from './full-screen-player'
import { QueueView } from './queue-view'
import { Slider } from '@/components/ui/slider'
import { Play, Pause, SkipBack, SkipForward, ListMusic, Shuffle, Radio } from 'lucide-react'
function formatTime(secs: number) {
if (!secs || !isFinite(secs)) return '0:00'
const m = Math.floor(secs / 60)
const s = Math.floor(secs % 60)
return `${m}:${s.toString().padStart(2, '0')}`
}
export function MiniPlayer() {
const { state, togglePlay, seek, nextTrack, prevTrack, setFullScreen, toggleShuffle } = useMusicPlayer()
const [queueOpen, setQueueOpen] = useState(false)
if (!state.currentTrack) return null
const track = state.currentTrack
const isRadio = isRadioTrack(track)
return (
<>
<div className="fixed bottom-0 inset-x-0 z-50 bg-card border-t border-border shadow-lg">
{/* Progress bar (thin, above the player) — disabled for radio */}
<div className="px-2">
{isRadio ? (
<div className="h-1 w-full bg-rose-500/40 rounded-full overflow-hidden">
<div className="h-full w-full bg-rose-500 animate-pulse" />
</div>
) : (
<Slider
value={[state.progress]}
max={state.duration || 1}
step={1}
onValueChange={([v]) => seek(v)}
className="h-1 [&_[data-slot=slider]]:h-1 [&_span[data-slot=scroll-bar]]:hidden"
/>
)}
</div>
<div className="flex items-center gap-3 px-4 py-2 h-14">
{/* Cover art - clickable to open fullscreen */}
<button
onClick={() => setFullScreen(true)}
className="flex-shrink-0 w-10 h-10 rounded-md overflow-hidden bg-muted"
>
{track.coverArt ? (
<img
src={`/api/music/cover/${track.coverArt}?size=80`}
alt={track.album}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-muted">
{isRadio && <Radio className="h-5 w-5 text-rose-400" />}
</div>
)}
</button>
{/* Track info - clickable to open fullscreen */}
<button
onClick={() => setFullScreen(true)}
className="flex-1 min-w-0 text-left"
>
<div className="text-sm font-medium truncate">{track.title}</div>
<div className="text-xs text-muted-foreground truncate">{track.artist}</div>
</button>
{/* Time / LIVE badge */}
{isRadio ? (
<span className="text-[10px] font-bold px-1.5 py-0.5 rounded bg-rose-500/20 text-rose-400 animate-pulse hidden sm:block">
LIVE
</span>
) : (
<span className="text-xs text-muted-foreground tabular-nums hidden sm:block">
{formatTime(state.progress)} / {formatTime(state.duration)}
</span>
)}
{/* Controls */}
<div className="flex items-center gap-1">
{!isRadio && (
<button
onClick={toggleShuffle}
className={`p-1.5 rounded-full transition-colors ${state.shuffleEnabled ? 'text-purple-400' : 'text-muted-foreground hover:bg-muted/50'}`}
title={state.shuffleEnabled ? 'Disable shuffle' : 'Enable shuffle'}
>
<Shuffle className="h-3.5 w-3.5" />
</button>
)}
<button
onClick={prevTrack}
className="p-1.5 hover:bg-muted/50 rounded-full transition-colors"
>
<SkipBack className="h-4 w-4" />
</button>
<button
onClick={togglePlay}
className="p-2 bg-primary text-primary-foreground rounded-full hover:bg-primary/90 transition-colors"
>
{state.isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4 ml-0.5" />}
</button>
<button
onClick={nextTrack}
className="p-1.5 hover:bg-muted/50 rounded-full transition-colors"
>
<SkipForward className="h-4 w-4" />
</button>
<button
onClick={() => setQueueOpen(true)}
className="p-1.5 hover:bg-muted/50 rounded-full transition-colors text-muted-foreground"
>
<ListMusic className="h-4 w-4" />
</button>
</div>
</div>
</div>
<FullScreenPlayer />
<QueueView open={queueOpen} onClose={() => setQueueOpen(false)} />
</>
)
}