'use client'; // AudioInput.tsx import React, { useRef, useState } from "react"; import { readAudio } from './audioUtils'; // Import the updated utility interface AudioInputProps { input: Blob | null; setInput: (v: Blob | null) => void; classify: (input: Float32Array) => void; // Still needs Float32Array ready: boolean | null; } export const AudioInput = ({ input, setInput, classify, ready }: AudioInputProps) => { const [recording, setRecording] = useState(false); const [audioUrl, setAudioUrl] = useState(null); const mediaRecorderRef = useRef(null); const chunks = useRef([]); const fileInputRef = useRef(null); const handleDrop = async (e: React.DragEvent) => { e.preventDefault(); if (e.dataTransfer.files.length > 0) { const file = e.dataTransfer.files[0]; if (file.type.startsWith("audio/")) { setInput(file); // Revoke previous URL to free memory if (audioUrl) URL.revokeObjectURL(audioUrl); setAudioUrl(URL.createObjectURL(file)); try { const audioData = await readAudio(file); // Now decodes AND resamples to Float32Array PCM classify(audioData); } catch (error) { console.error("Error reading or processing audio file:", error); // Handle error, e.g., show a message to the user } } } }; const handleFileChange = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const file = e.target.files[0]; if (file.type.startsWith("audio/")) { setInput(file); // Revoke previous URL to free memory if (audioUrl) URL.revokeObjectURL(audioUrl); setAudioUrl(URL.createObjectURL(file)); try { const audioData = await readAudio(file); // Now decodes AND resamples to Float32Array PCM classify(audioData); } catch (error) { console.error("Error reading or processing audio file:", error); // Handle error } } } }; const startRecording = async () => { try { setRecording(true); chunks.current = []; // Ensure audioUrl is cleared for new recording if (audioUrl) URL.revokeObjectURL(audioUrl); setAudioUrl(null); setInput(null); // Clear previous input blob const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new window.MediaRecorder(stream); mediaRecorderRef.current = mediaRecorder; mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) { chunks.current.push(e.data); } }; mediaRecorder.onstop = async () => { const blob = new Blob(chunks.current, { type: "audio/webm" }); // MediaRecorder outputs a Blob setInput(blob); // Set the Blob input if needed elsewhere // Revoke previous URL if (audioUrl) URL.revokeObjectURL(audioUrl); setAudioUrl(URL.createObjectURL(blob)); // Create URL for playback try { const audioData = await readAudio(blob); // Decode AND resample Blob to Float32Array PCM classify(audioData); // Pass the Float32Array PCM data } catch (error) { console.error("Error reading or processing recorded audio:", error); // Handle error } finally { // Always stop tracks after recording stops stream.getTracks().forEach(track => track.stop()); } }; mediaRecorder.start(); } catch (error) { console.error("Error starting recording:", error); setRecording(false); // Ensure recording state is reset on error // Handle error, e.g., show a message to the user that mic access failed } }; const stopRecording = async () => { if (!mediaRecorderRef.current) return; // The actual classification and setting of input/audioUrl happens in mediaRecorder.onstop mediaRecorderRef.current.stop(); setRecording(false); // Set recording state to false immediately }; // Added error handling and URL cleanup React.useEffect(() => { // Cleanup object URLs when component unmounts or audioUrl changes return () => { if (audioUrl) URL.revokeObjectURL(audioUrl); }; }, [audioUrl]); return (
e.preventDefault()} onClick={() => ready !== false && fileInputRef.current?.click()} // Prevent click if not ready style={{ minHeight: 120 }} > { ready === false ? "Loading models..." : "Drag & drop audio file here or click to select" }
{!recording ? ( ) : ( )} {/* Only show audio player if not recording and audioUrl exists */} {!recording && audioUrl && ( )} {ready === false && Loading...} {/* Optional loading indicator */}
); };