97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import type { AudioPlayerProps } from '@/types/component-props';
|
|
|
|
export default function AudioPlayer({
|
|
audioUrl,
|
|
duration,
|
|
onPlayPause,
|
|
onSeek,
|
|
}: AudioPlayerProps) {
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
const [currentTime, setCurrentTime] = useState(0);
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
|
|
useEffect(() => {
|
|
const audio = audioRef.current;
|
|
if (!audio) return;
|
|
|
|
const handleTimeUpdate = () => {
|
|
setCurrentTime(audio.currentTime);
|
|
};
|
|
|
|
const handleEnded = () => {
|
|
setIsPlaying(false);
|
|
onPlayPause?.(false);
|
|
};
|
|
|
|
audio.addEventListener('timeupdate', handleTimeUpdate);
|
|
audio.addEventListener('ended', handleEnded);
|
|
|
|
return () => {
|
|
audio.removeEventListener('timeupdate', handleTimeUpdate);
|
|
audio.removeEventListener('ended', handleEnded);
|
|
};
|
|
}, [onPlayPause]);
|
|
|
|
const handlePlayPause = () => {
|
|
const audio = audioRef.current;
|
|
if (!audio) return;
|
|
|
|
if (isPlaying) {
|
|
audio.pause();
|
|
} else {
|
|
audio.play();
|
|
}
|
|
setIsPlaying(!isPlaying);
|
|
onPlayPause?.(!isPlaying);
|
|
};
|
|
|
|
const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const audio = audioRef.current;
|
|
if (!audio) return;
|
|
|
|
const newTime = parseFloat(e.target.value);
|
|
audio.currentTime = newTime;
|
|
setCurrentTime(newTime);
|
|
onSeek?.(newTime);
|
|
};
|
|
|
|
const formatTime = (seconds: number) => {
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white border rounded-lg p-4">
|
|
<audio ref={audioRef} src={audioUrl} />
|
|
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={handlePlayPause}
|
|
className="w-12 h-12 rounded-full bg-blue-500 text-white flex items-center justify-center hover:bg-blue-600 transition-colors"
|
|
>
|
|
{isPlaying ? '⏸️' : '▶️'}
|
|
</button>
|
|
|
|
<div className="flex-1">
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max={duration}
|
|
value={currentTime}
|
|
onChange={handleSeek}
|
|
className="w-full"
|
|
/>
|
|
<div className="flex justify-between text-xs text-gray-600 mt-1">
|
|
<span>{formatTime(currentTime)}</span>
|
|
<span>{formatTime(duration)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|