3v324v23's picture
feat: initial release of Multiverse AI Studio
4aed5f4
Raw
History Blame Contribute Delete
5.02 kB
import React, { useState, useRef, useEffect } from 'react';
import { Play, Pause, Volume2, VolumeX } from 'lucide-react';
interface AudioPlayerProps {
src: string;
}
export default function AudioPlayer({ src }: AudioPlayerProps) {
const [isPlaying, setIsPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [isMuted, setIsMuted] = useState(false);
const audioRef = useRef<HTMLAudioElement>(null);
const progressBarRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setIsPlaying(false);
setCurrentTime(0);
}, [src]);
const togglePlay = () => {
if (!audioRef.current) return;
if (isPlaying) {
audioRef.current.pause();
} else {
audioRef.current.play();
}
setIsPlaying(!isPlaying);
};
const handleTimeUpdate = () => {
if (!audioRef.current) return;
setCurrentTime(audioRef.current.currentTime);
};
const handleLoadedMetadata = () => {
if (!audioRef.current) return;
setDuration(audioRef.current.duration);
};
const handleProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!audioRef.current) return;
const newTime = parseFloat(e.target.value);
audioRef.current.currentTime = newTime;
setCurrentTime(newTime);
};
const toggleMute = () => {
if (!audioRef.current) return;
audioRef.current.muted = !isMuted;
setIsMuted(!isMuted);
};
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}`;
};
return (
<div className="bg-[#12121a] border border-white/10 rounded-2xl p-6 shadow-2xl backdrop-blur-xl animate-in fade-in slide-in-from-bottom-4 duration-500">
<audio
ref={audioRef}
src={src}
onTimeUpdate={handleTimeUpdate}
onLoadedMetadata={handleLoadedMetadata}
onEnded={() => setIsPlaying(false)}
/>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
{/* Left Side: Playback Control & Waveform Mock */}
<div className="flex items-center space-x-6">
<button
onClick={togglePlay}
className="w-14 h-14 rounded-full bg-gradient-to-r from-fuchsia-600 to-pink-600 text-white flex items-center justify-center hover:scale-105 active:scale-95 transition-transform shadow-[0_0_15px_rgba(217,70,239,0.4)]"
>
{isPlaying ? <Pause className="w-6 h-6 fill-white" /> : <Play className="w-6 h-6 fill-white ml-1" />}
</button>
<div>
<h4 className="font-semibold text-gray-200 text-sm">Ambient Soundscape</h4>
<p className="text-xs text-gray-500 mt-0.5">Synthesized environment background track</p>
</div>
</div>
{/* Center: Waveform Animation Visualizer */}
<div className="flex-1 flex items-center justify-center space-x-1.5 h-10 px-4">
{[...Array(18)].map((_, i) => {
// Random heights for visual variance
const height = [16, 28, 12, 34, 18, 40, 24, 30, 14, 38, 20, 32, 10, 26, 16, 36, 12, 22][i];
return (
<span
key={i}
className="w-[3px] rounded-full bg-gradient-to-t from-fuchsia-500 to-pink-500"
style={{
height: isPlaying ? `${height}px` : '4px',
opacity: isPlaying ? 0.8 : 0.2,
transition: 'height 0.3s ease, opacity 0.3s ease',
animation: isPlaying ? `pulse-bar 1.2s ease-in-out infinite alternate` : 'none',
animationDelay: `${i * 0.08}s`
}}
/>
);
})}
</div>
{/* Right Side: Timeline & Volume Mute */}
<div className="flex items-center space-x-4 min-w-[200px] justify-between">
<span className="text-xs font-mono text-gray-400 w-10 text-right">{formatTime(currentTime)}</span>
<input
ref={progressBarRef}
type="range"
min={0}
max={duration || 100}
value={currentTime}
onChange={handleProgressChange}
className="flex-1 h-1 bg-gray-800 rounded-lg appearance-none cursor-pointer accent-fuchsia-500"
/>
<span className="text-xs font-mono text-gray-400 w-10">{formatTime(duration)}</span>
<button onClick={toggleMute} className="text-gray-400 hover:text-white transition-colors">
{isMuted ? <VolumeX className="w-5 h-5 text-red-400" /> : <Volume2 className="w-5 h-5" />}
</button>
</div>
</div>
{/* Styled animation definition inside JSX */}
<style>{`
@keyframes pulse-bar {
0% { transform: scaleY(0.4); }
100% { transform: scaleY(1.1); }
}
`}</style>
</div>
);
}