import { useState, useRef, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Mic, MicOff, Play, Pause, Download, Trash2 } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import AudioWaveform from './AudioWaveform'; import MicrophoneScene from '../three/MicrophoneScene'; interface AudioRecorderProps { onRecordingComplete?: (audioBlob: Blob, audioUrl: string) => void; className?: string; } export default function AudioRecorder({ onRecordingComplete, className = "" }: AudioRecorderProps) { const [isRecording, setIsRecording] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [audioUrl, setAudioUrl] = useState(''); const [recordingTime, setRecordingTime] = useState(0); const mediaRecorderRef = useRef(null); const audioRef = useRef(null); const chunksRef = useRef([]); const intervalRef = useRef(null); const { toast } = useToast(); const startRecording = useCallback(async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, sampleRate: 44100 } }); const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' }); mediaRecorderRef.current = mediaRecorder; chunksRef.current = []; mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { chunksRef.current.push(event.data); } }; mediaRecorder.onstop = () => { const audioBlob = new Blob(chunksRef.current, { type: 'audio/webm' }); const url = URL.createObjectURL(audioBlob); setAudioUrl(url); onRecordingComplete?.(audioBlob, url); // Stop all tracks stream.getTracks().forEach(track => track.stop()); }; mediaRecorder.start(); setIsRecording(true); setRecordingTime(0); intervalRef.current = setInterval(() => { setRecordingTime(prev => prev + 1); }, 1000); toast({ title: "Recording started", description: "Speak clearly into your microphone" }); } catch (error) { console.error('Error starting recording:', error); toast({ title: "Recording failed", description: "Could not access microphone. Please check permissions.", variant: "destructive" }); } }, [onRecordingComplete, toast]); const stopRecording = useCallback(() => { if (mediaRecorderRef.current && isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } toast({ title: "Recording complete", description: `Recorded ${recordingTime} seconds of audio` }); } }, [isRecording, recordingTime, toast]); const playAudio = useCallback(() => { if (audioUrl && audioRef.current) { if (isPlaying) { audioRef.current.pause(); setIsPlaying(false); } else { audioRef.current.play(); setIsPlaying(true); } } }, [audioUrl, isPlaying]); const downloadAudio = useCallback(() => { if (audioUrl) { const a = document.createElement('a'); a.href = audioUrl; a.download = `voice-sample-${Date.now()}.webm`; document.body.appendChild(a); a.click(); document.body.removeChild(a); } }, [audioUrl]); const clearRecording = useCallback(() => { if (audioUrl) { URL.revokeObjectURL(audioUrl); setAudioUrl(''); } setIsPlaying(false); setRecordingTime(0); }, [audioUrl]); const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; return (
{/* 3D Microphone */}
{/* Waveform Visualization */}
{/* Recording Time */} {(isRecording || recordingTime > 0) && (
{formatTime(recordingTime)}
{isRecording && (
Recording...
)}
)} {/* Control Buttons */}
{!isRecording ? ( ) : ( )} {audioUrl && !isRecording && ( <> )}
{/* Hidden audio element for playback */} {audioUrl && (
); }