project-standalo-sonic-cloud/components/AudioPlayer.tsx

165 lines
5.7 KiB
TypeScript

'use client'
import { useState, useRef, useEffect } from 'react'
interface AudioPlayerProps {
songId: string
songTitle: string
artistName: string
coverUrl?: string
audioUrl: string
onPlayCountIncrement?: () => void
}
export function AudioPlayer({
songId,
songTitle,
artistName,
coverUrl,
audioUrl,
onPlayCountIncrement
}: AudioPlayerProps) {
const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0)
const [volume, setVolume] = useState(0.8)
const audioRef = useRef<HTMLAudioElement>(null)
useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = volume
}
}, [volume])
useEffect(() => {
const audio = audioRef.current
if (!audio) return
const updateTime = () => setCurrentTime(audio.currentTime)
const updateDuration = () => setDuration(audio.duration)
audio.addEventListener('timeupdate', updateTime)
audio.addEventListener('loadedmetadata', updateDuration)
return () => {
audio.removeEventListener('timeupdate', updateTime)
audio.removeEventListener('loadedmetadata', updateDuration)
}
}, [])
const togglePlay = () => {
if (audioRef.current) {
if (isPlaying) {
audioRef.current.pause()
} else {
audioRef.current.play()
if (onPlayCountIncrement && currentTime === 0) {
onPlayCountIncrement()
}
}
setIsPlaying(!isPlaying)
}
}
const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
const newTime = parseFloat(e.target.value)
setCurrentTime(newTime)
if (audioRef.current) {
audioRef.current.currentTime = newTime
}
}
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setVolume(parseFloat(e.target.value))
}
const formatTime = (time: number) => {
const minutes = Math.floor(time / 60)
const seconds = Math.floor(time % 60)
return `${minutes}:${seconds.toString().padStart(2, '0')}`
}
return (
<div className="fixed bottom-0 left-0 right-0 bg-zinc-900 border-t border-zinc-800 p-4 z-50">
<audio ref={audioRef} src={audioUrl} />
<div className="max-w-7xl mx-auto">
<div className="flex items-center gap-4">
{/* Song Info */}
<div className="flex items-center gap-3 flex-1 min-w-0">
{coverUrl && (
<img
src={coverUrl}
alt={songTitle}
className="w-12 h-12 rounded object-cover"
/>
)}
<div className="min-w-0">
<p className="text-white font-medium truncate">{songTitle}</p>
<p className="text-zinc-400 text-sm truncate">{artistName}</p>
</div>
</div>
{/* Controls */}
<div className="flex flex-col items-center flex-1 gap-2">
<button
onClick={togglePlay}
className="w-10 h-10 rounded-full bg-purple-500 hover:bg-purple-600 flex items-center justify-center transition"
>
{isPlaying ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M6 4h3v12H6V4zm5 0h3v12h-3V4z" />
</svg>
) : (
<svg className="w-5 h-5 text-white ml-0.5" fill="currentColor" viewBox="0 0 20 20">
<path d="M6.3 4.1c-.4-.2-.8 0-.8.4v11c0 .4.4.6.8.4l9-5.5c.3-.2.3-.6 0-.8l-9-5.5z" />
</svg>
)}
</button>
{/* Progress Bar */}
<div className="flex items-center gap-2 w-full max-w-md">
<span className="text-xs text-zinc-400 tabular-nums">
{formatTime(currentTime)}
</span>
<input
type="range"
min="0"
max={duration || 0}
value={currentTime}
onChange={handleSeek}
className="flex-1 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer"
style={{
background: `linear-gradient(to right, rgb(168 85 247) 0%, rgb(168 85 247) ${(currentTime / duration) * 100}%, rgb(63 63 70) ${(currentTime / duration) * 100}%, rgb(63 63 70) 100%)`
}}
/>
<span className="text-xs text-zinc-400 tabular-nums">
{formatTime(duration)}
</span>
</div>
</div>
{/* Volume */}
<div className="flex items-center gap-2 flex-1 justify-end">
<svg className="w-5 h-5 text-zinc-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" />
</svg>
<input
type="range"
min="0"
max="1"
step="0.01"
value={volume}
onChange={handleVolumeChange}
className="w-24 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer"
style={{
background: `linear-gradient(to right, rgb(168 85 247) 0%, rgb(168 85 247) ${volume * 100}%, rgb(63 63 70) ${volume * 100}%, rgb(63 63 70) 100%)`
}}
/>
</div>
</div>
</div>
</div>
)
}