import { useState } from 'react'; import styles from './styles.module.css'; import toast from 'react-hot-toast'; import RecorderControl from './record-controller'; interface VoiceRecorder { setInputMsg: (msg: string) => void; tapToSpeak: boolean; includeDiv?: boolean; showVoiceRecorder?: boolean; delayBetweenDialogs?: number; handleVoiceRecorder?: () => void; styles?: object; primaryColor: string; primaryDarkColor: string; } const VoiceRecorder: React.FC = ({ setInputMsg, tapToSpeak, includeDiv = false, showVoiceRecorder = true, delayBetweenDialogs = 3000, handleVoiceRecorder, styles: customStyles, primaryColor, primaryDarkColor, }) => { const [mediaRecorder, setMediaRecorder] = useState(null); const [isErrorClicked, setIsErrorClicked] = useState(false); const [recorderStatus, setRecorderStatus] = useState('idle'); const [isRecording, setIsRecording] = useState(false); const voiceMinDecibels: number = -50; const dialogMaxLength: number = 60000; // 1 minute const startRecording = () => { if (!isRecording) { setIsRecording(true); record(); if (handleVoiceRecorder) handleVoiceRecorder(); } }; const stopRecording = () => { if (isRecording) { if (mediaRecorder !== null) { mediaRecorder.stop(); setIsRecording(false); setMediaRecorder(null); } } }; function record() { navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => { const recorder = new MediaRecorder(stream); recorder.start(); setMediaRecorder(recorder); const audioChunks: BlobPart[] = []; recorder.addEventListener('dataavailable', (event) => { audioChunks.push(event.data); }); const audioContext = new AudioContext(); const audioStreamSource = audioContext.createMediaStreamSource(stream); const analyser = audioContext.createAnalyser(); analyser.minDecibels = voiceMinDecibels; audioStreamSource.connect(analyser); const bufferLength = analyser.frequencyBinCount; const domainData = new Uint8Array(bufferLength); let time: Date = new Date(); let startTime: number = time.getTime(); let lastDetectedTime: number = time.getTime(); let anySoundDetected: boolean = false; const detectSound = () => { if (!isRecording) return; time = new Date(); const currentTime = time.getTime(); if (currentTime > startTime + dialogMaxLength) { recorder.stop(); return; } if (anySoundDetected && currentTime > lastDetectedTime + delayBetweenDialogs) { recorder.stop(); return; } analyser.getByteFrequencyData(domainData); for (let i = 0; i < bufferLength; i++) { if (domainData[i] > 0) { anySoundDetected = true; lastDetectedTime = currentTime; } } window.requestAnimationFrame(detectSound); }; window.requestAnimationFrame(detectSound); recorder.addEventListener('stop', () => { stream.getTracks().forEach((track) => track.stop()); if (!anySoundDetected) return; const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' }); makeComputeAPICall(audioBlob); }); }); } const makeComputeAPICall = async (blob: Blob) => { try { setRecorderStatus('processing'); toast.success('Processing your recording...'); if (blob) { // Simulate API call result setTimeout(() => { setInputMsg('Processed message'); setRecorderStatus('idle'); }, 2000); } } catch (error) { console.error(error); setRecorderStatus('error'); toast.error('An error occurred while processing your recording.'); setIsErrorClicked(false); setTimeout(() => { if (!isErrorClicked) { setRecorderStatus('idle'); } }, 2500); } }; if (!showVoiceRecorder) return null; return (
{mediaRecorder && mediaRecorder.state === 'recording' ? (
) : (
{recorderStatus === 'processing' ? (
{}} primaryColor={primaryColor} primaryDarkColor={primaryDarkColor} />
) : recorderStatus === 'error' ? (
{ setIsErrorClicked(true); startRecording(); }} includeDiv={includeDiv} primaryColor={primaryColor} primaryDarkColor={primaryDarkColor} />
) : (
{ setIsErrorClicked(true); startRecording(); }} includeDiv={includeDiv} tapToSpeak={tapToSpeak} primaryColor={primaryColor} primaryDarkColor={primaryDarkColor} />
)}
)}
); }; export default VoiceRecorder;