Spaces:
Running
Running
import React, { useState, useEffect, useRef } from 'react'; | |
import MicIcon from '@mui/icons-material/Mic'; | |
import StopIcon from '@mui/icons-material/Stop'; | |
import styles from './page.module.css'; | |
import useSpeechRecognition from './hooks/useSpeechRecognition'; | |
import useAudioManager from './hooks/useAudioManager'; | |
const getMimeType = (): string | null => { | |
const types = ["audio/webm", "audio/mp4", "audio/ogg", "audio/wav", "audio/aac"]; | |
for (let type of types) { | |
if (MediaRecorder.isTypeSupported(type)) { | |
return type; | |
} | |
} | |
return null; | |
}; | |
interface VoiceInputFormProps { | |
handleSubmit: any; | |
input: string; | |
setInput: React.Dispatch<React.SetStateAction<string>>; | |
} | |
const VoiceInputForm: React.FC<VoiceInputFormProps> = ({ handleSubmit, input, setInput }) => { | |
const [isRecording, setIsRecording] = useState(false); | |
const { startListening, stopListening, recognizedText } = useSpeechRecognition(); | |
const { setAudioFromRecording } = useAudioManager(); | |
const mediaRecorderRef = useRef<MediaRecorder | null>(null); | |
const audioChunksRef = useRef<BlobPart[]>([]); | |
useEffect(() => { | |
if (recognizedText) { | |
setInput(recognizedText); | |
} | |
}, [recognizedText, setInput]); | |
const startRecording = async () => { | |
try { | |
const mimeType = getMimeType(); | |
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
mediaRecorderRef.current = new MediaRecorder(stream, { mimeType: mimeType ?? undefined }); | |
audioChunksRef.current = []; | |
mediaRecorderRef.current.ondataavailable = (event: BlobEvent) => { | |
audioChunksRef.current.push(event.data); | |
}; | |
mediaRecorderRef.current.start(); | |
} catch (err) { | |
console.error("Error accessing media devices:", err); | |
} | |
}; | |
const stopRecording = async (): Promise<Blob> => { | |
return new Promise((resolve, reject) => { | |
const recorder = mediaRecorderRef.current; | |
if (recorder && recorder.state === "recording") { | |
recorder.onstop = () => { | |
const audioBlob = new Blob(audioChunksRef.current, { 'type': recorder.mimeType }); | |
audioChunksRef.current = []; | |
resolve(audioBlob); | |
}; | |
recorder.stop(); | |
} else { | |
reject(new Error("MediaRecorder is not recording")); | |
} | |
}); | |
}; | |
const handleRecording = async () => { | |
if (isRecording) { | |
stopListening(); | |
const recordedBlob = await stopRecording(); | |
setAudioFromRecording(recordedBlob); | |
const audioBuffer = await convertBlobToAudioBuffer(recordedBlob); | |
startListening(audioBuffer); | |
} else { | |
startRecording(); | |
} | |
setIsRecording(!isRecording); | |
}; | |
return ( | |
<div> | |
<form onSubmit={handleSubmit} className={styles.form}> | |
<input | |
type="text" | |
value={input} | |
className={styles.input} | |
onChange={(e) => setInput(e.target.value)} | |
placeholder="Speak or type..." | |
/> | |
</form> | |
<button onClick={handleRecording} className={styles.button}> | |
{isRecording ? <StopIcon /> : <MicIcon />} | |
</button> | |
</div> | |
); | |
}; | |
const convertBlobToAudioBuffer = async (blob: Blob): Promise<AudioBuffer> => { | |
const audioContext = new AudioContext(); | |
const arrayBuffer = await blob.arrayBuffer(); | |
return await audioContext.decodeAudioData(arrayBuffer); | |
}; | |
export default VoiceInputForm; | |