307 lines
7.9 KiB
TypeScript
307 lines
7.9 KiB
TypeScript
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Slider } from '@/components/ui/slider'
|
|
import type { PlayerMode, PlayerState } from '@/lib/api/services/player'
|
|
import {
|
|
ArrowRight,
|
|
ArrowRightToLine,
|
|
List,
|
|
Pause,
|
|
Play,
|
|
Repeat,
|
|
Repeat1,
|
|
Shuffle,
|
|
SkipBack,
|
|
SkipForward,
|
|
Square,
|
|
Volume2,
|
|
VolumeX,
|
|
} from 'lucide-react'
|
|
import { memo, useMemo } from 'react'
|
|
import { useRenderFlash } from '@/hooks/useRenderFlash'
|
|
|
|
interface PlayerControlsProps {
|
|
status: PlayerState['status']
|
|
mode: PlayerMode
|
|
isLoading: boolean
|
|
showPlaylistButton?: boolean
|
|
volume?: number
|
|
onPlayPause: () => void
|
|
onStop: () => void
|
|
onPrevious: () => void
|
|
onNext: () => void
|
|
onModeChange: () => void
|
|
onTogglePlaylist?: () => void
|
|
onVolumeChange?: (volume: number[]) => void
|
|
onMute?: () => void
|
|
variant?: 'normal' | 'maximized' | 'minimized'
|
|
}
|
|
|
|
export const PlayerControls = memo(function PlayerControls({
|
|
status,
|
|
mode,
|
|
isLoading,
|
|
showPlaylistButton = false,
|
|
volume,
|
|
onPlayPause,
|
|
onStop,
|
|
onPrevious,
|
|
onNext,
|
|
onModeChange,
|
|
onTogglePlaylist,
|
|
onVolumeChange,
|
|
onMute,
|
|
variant = 'normal',
|
|
}: PlayerControlsProps) {
|
|
const isMinimized = variant === 'minimized'
|
|
const isMaximized = variant === 'maximized'
|
|
const flashClass = useRenderFlash([status, mode, volume], 'green')
|
|
|
|
const modeIcon = useMemo(() => {
|
|
switch (mode) {
|
|
case 'continuous':
|
|
return <ArrowRight className="h-4 w-4" />
|
|
case 'loop':
|
|
return <Repeat className="h-4 w-4" />
|
|
case 'loop_one':
|
|
return <Repeat1 className="h-4 w-4" />
|
|
case 'random':
|
|
return <Shuffle className="h-4 w-4" />
|
|
default:
|
|
return <ArrowRightToLine className="h-4 w-4" />
|
|
}
|
|
}, [mode])
|
|
|
|
const modeLabel = useMemo(() =>
|
|
mode.replace('_', ' '),
|
|
[mode]
|
|
)
|
|
|
|
if (isMinimized) {
|
|
return (
|
|
<div className={`${flashClass} flex items-center gap-1`}>
|
|
{/* DEBUG: PlayerControls Minimized - GREEN FLASH */}
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onPrevious}
|
|
disabled={isLoading}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
<SkipBack className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onPlayPause}
|
|
disabled={isLoading}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
{status === 'playing' ? (
|
|
<Pause className="h-4 w-4" />
|
|
) : (
|
|
<Play className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onStop}
|
|
disabled={isLoading}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
<Square className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onNext}
|
|
disabled={isLoading}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
<SkipForward className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (isMaximized) {
|
|
return (
|
|
<div className={flashClass}>
|
|
{/* DEBUG: PlayerControls Maximized - GREEN FLASH */}
|
|
{/* Large Controls */}
|
|
<div className="flex items-center gap-4 mb-8">
|
|
<Button
|
|
size="lg"
|
|
variant="ghost"
|
|
onClick={onPrevious}
|
|
disabled={isLoading}
|
|
>
|
|
<SkipBack className="h-6 w-6" />
|
|
</Button>
|
|
<Button
|
|
size="lg"
|
|
onClick={onPlayPause}
|
|
disabled={isLoading}
|
|
className="h-16 w-16 rounded-full"
|
|
>
|
|
{status === 'playing' ? (
|
|
<Pause className="h-8 w-8" />
|
|
) : (
|
|
<Play className="h-8 w-8" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
size="lg"
|
|
variant="ghost"
|
|
onClick={onStop}
|
|
disabled={isLoading}
|
|
>
|
|
<Square className="h-6 w-6" />
|
|
</Button>
|
|
<Button
|
|
size="lg"
|
|
variant="ghost"
|
|
onClick={onNext}
|
|
disabled={isLoading}
|
|
>
|
|
<SkipForward className="h-6 w-6" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Secondary Controls */}
|
|
<div className="flex items-center gap-6">
|
|
<div className="flex items-center gap-2">
|
|
<Button size="sm" variant="ghost" onClick={onModeChange}>
|
|
{modeIcon}
|
|
</Button>
|
|
<Badge variant="secondary">{modeLabel}</Badge>
|
|
</div>
|
|
|
|
{volume !== undefined && onVolumeChange && onMute && (
|
|
<div className="flex items-center gap-2">
|
|
<Button size="sm" variant="ghost" onClick={onMute}>
|
|
{volume === 0 ? (
|
|
<VolumeX className="h-4 w-4" />
|
|
) : (
|
|
<Volume2 className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
<div className="w-24">
|
|
<Slider
|
|
value={[volume]}
|
|
max={100}
|
|
step={1}
|
|
onValueChange={onVolumeChange}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
<span className="text-sm text-muted-foreground w-8">
|
|
{Math.round(volume)}%
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Normal variant
|
|
return (
|
|
<div className={flashClass}>
|
|
{/* DEBUG: PlayerControls Normal - GREEN FLASH */}
|
|
{/* Main Controls */}
|
|
<div className="flex items-center justify-center gap-2 mb-4">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onModeChange}
|
|
className="h-8 w-8 p-0"
|
|
title={`Mode: ${modeLabel}`}
|
|
>
|
|
{modeIcon}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onPrevious}
|
|
disabled={isLoading}
|
|
>
|
|
<SkipBack className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
onClick={onPlayPause}
|
|
disabled={isLoading}
|
|
className="h-10 w-10 rounded-full"
|
|
>
|
|
{status === 'playing' ? (
|
|
<Pause className="h-5 w-5" />
|
|
) : (
|
|
<Play className="h-5 w-5" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onStop}
|
|
disabled={isLoading}
|
|
>
|
|
<Square className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onNext}
|
|
disabled={isLoading}
|
|
>
|
|
<SkipForward className="h-4 w-4" />
|
|
</Button>
|
|
{showPlaylistButton && onTogglePlaylist && (
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onTogglePlaylist}
|
|
className="h-8 w-8 p-0"
|
|
title="Toggle Playlist"
|
|
>
|
|
<List className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Secondary Controls */}
|
|
<div className="flex items-center justify-between">
|
|
<Badge variant="secondary" className="text-xs">
|
|
{modeLabel}
|
|
</Badge>
|
|
|
|
{volume !== undefined && onVolumeChange && onMute && (
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={onMute}
|
|
className="h-8 w-8 p-0"
|
|
>
|
|
{volume === 0 ? (
|
|
<VolumeX className="h-4 w-4" />
|
|
) : (
|
|
<Volume2 className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
<div className="w-16">
|
|
<Slider
|
|
value={[volume]}
|
|
max={100}
|
|
step={1}
|
|
onValueChange={onVolumeChange}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}) |