| import React, { useState, useRef, useEffect } from 'react'; |
| import { Play, Pause } from 'lucide-react'; |
|
|
| type AudioPlayerProps = { |
| src: string; |
| className?: string; |
| autoPlay?: boolean; |
| }; |
|
|
| const AudioPlayer: React.FC<AudioPlayerProps> = ({ src, className = '', autoPlay = false }) => { |
| const [isPlaying, setIsPlaying] = useState(false); |
| const [duration, setDuration] = useState(0); |
| const [currentTime, setCurrentTime] = useState(0); |
| const audioRef = useRef<HTMLAudioElement>(null); |
| const progressRef = useRef<HTMLDivElement>(null); |
|
|
| const hasAutoPlayedRef = useRef(false); |
|
|
| useEffect(() => { |
| const audio = audioRef.current; |
| if (!audio) return; |
|
|
| const setAudioData = () => { |
| setDuration(audio.duration); |
| setCurrentTime(audio.currentTime); |
| }; |
|
|
| const setAudioTime = () => setCurrentTime(audio.currentTime); |
|
|
| if (audio.readyState >= 1) { |
| |
| setAudioData(); |
| } |
|
|
| audio.addEventListener('loadeddata', setAudioData); |
| audio.addEventListener('timeupdate', setAudioTime); |
|
|
| |
| if (autoPlay && !hasAutoPlayedRef.current) { |
| const playAudio = async () => { |
| try { |
| await audio.play(); |
| setIsPlaying(true); |
| hasAutoPlayedRef.current = true; |
| } catch (error) { |
| |
| console.warn('Auto-play failed:', error); |
| } |
| }; |
|
|
| playAudio(); |
| } |
|
|
| return () => { |
| audio.removeEventListener('loadeddata', setAudioData); |
| audio.removeEventListener('timeupdate', setAudioTime); |
| }; |
| }, [src, autoPlay]); |
|
|
| const togglePlayPause = () => { |
| const audio = audioRef.current; |
| if (!audio) return; |
|
|
| if (isPlaying) { |
| audio.pause(); |
| } else { |
| audio.play(); |
| } |
| setIsPlaying(!isPlaying); |
| }; |
|
|
| const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => { |
| const audio = audioRef.current; |
| if (!audio || !progressRef.current) return; |
|
|
| const progressBar = progressRef.current; |
| const clickX = e.nativeEvent.offsetX; |
| const width = progressBar.clientWidth; |
| const newTime = (clickX / width) * duration; |
|
|
| audio.currentTime = newTime; |
| setCurrentTime(newTime); |
| }; |
|
|
| 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 < 10 ? '0' : ''}${seconds}`; |
| }; |
|
|
| const progress = duration ? (currentTime / duration) * 100 : 0; |
|
|
| return ( |
| <div className={`bg-black/10 dark:bg-gray-800/50 backdrop-blur-sm rounded-xl p-3 ${className}`}> |
| <audio |
| ref={audioRef} |
| src={src} |
| onEnded={() => setIsPlaying(false)} |
| onPlay={() => setIsPlaying(true)} |
| onPause={() => setIsPlaying(false)} |
| /> |
| |
| <div className="flex items-center gap-3"> |
| <button |
| onClick={togglePlayPause} |
| className="flex items-center justify-center w-8 h-8 rounded-full bg-teal-500 hover:bg-teal-600 text-white transition-colors" |
| > |
| {isPlaying ? ( |
| <Pause className="w-4 h-4" /> |
| ) : ( |
| <Play className="w-4 h-4 ml-0.5" /> |
| )} |
| </button> |
| |
| <div className="flex-1"> |
| <div |
| ref={progressRef} |
| onClick={handleProgressClick} |
| className="h-1.5 bg-gray-300 dark:bg-gray-700 rounded-full cursor-pointer overflow-hidden" |
| > |
| <div |
| className="h-full bg-teal-500 rounded-full" |
| style={{ width: `${progress}%` }} |
| /> |
| </div> |
| <div className="flex justify-between text-xs text-gray-500 dark:text-white mt-1"> |
| <span>{formatTime(currentTime)}</span> |
| <span>{formatTime(duration)}</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default AudioPlayer; |