| 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; | |