Voicechat / src /VoiceBot.jsx
Durga-7780's picture
Update src/VoiceBot.jsx
ecf5c25 verified
Raw
History Blame Contribute Delete
4.45 kB
import React, { useRef, useState } from "react";
import Loader from "./Loader";
const SILENCE_THRESHOLD = 0.01;
const SILENCE_DURATION = 1500;
export default function VoiceBot() {
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const analyserRef = useRef(null);
const audioContextRef = useRef(null);
const silenceTimerRef = useRef(null);
const streamRef = useRef(null);
const listeningRef = useRef(false);
const [status, setStatus] = useState("idle");
// idle | listening | thinking | speaking
// =========================
// START RECORDING
// =========================
const startRecording = async () => {
setStatus("listening");
listeningRef.current = true;
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
streamRef.current = stream;
const recorder = new MediaRecorder(stream);
mediaRecorderRef.current = recorder;
audioChunksRef.current = [];
recorder.ondataavailable = (e) => {
audioChunksRef.current.push(e.data);
};
recorder.onstop = handleStop;
recorder.start();
setupSilenceDetection(stream);
};
// =========================
// SILENCE DETECTION
// =========================
const setupSilenceDetection = (stream) => {
audioContextRef.current = new AudioContext();
const source = audioContextRef.current.createMediaStreamSource(stream);
analyserRef.current = audioContextRef.current.createAnalyser();
analyserRef.current.fftSize = 2048;
source.connect(analyserRef.current);
const data = new Uint8Array(analyserRef.current.fftSize);
const checkSilence = () => {
analyserRef.current.getByteTimeDomainData(data);
let sum = 0;
for (let i = 0; i < data.length; i++) {
const v = (data[i] - 128) / 128;
sum += v * v;
}
const volume = Math.sqrt(sum / data.length);
if (volume < SILENCE_THRESHOLD) {
if (!silenceTimerRef.current) {
silenceTimerRef.current = setTimeout(() => {
stopRecording();
}, SILENCE_DURATION);
}
} else {
clearTimeout(silenceTimerRef.current);
silenceTimerRef.current = null;
}
// ✅ FIX: use ref, not React state
if (listeningRef.current) {
requestAnimationFrame(checkSilence);
}
};
checkSilence();
};
// =========================
// STOP RECORDING
// =========================
const stopRecording = () => {
listeningRef.current = false;
if (silenceTimerRef.current) {
clearTimeout(silenceTimerRef.current);
silenceTimerRef.current = null;
}
if (mediaRecorderRef.current?.state !== "inactive") {
mediaRecorderRef.current.stop();
}
streamRef.current?.getTracks().forEach(t => t.stop());
if (
audioContextRef.current &&
audioContextRef.current.state !== "closed"
) {
audioContextRef.current.close();
}
};
// =========================
// SEND TO BACKEND
// =========================
const handleStop = async () => {
setStatus("thinking");
const blob = new Blob(audioChunksRef.current, {
type: "audio/wav",
});
const formData = new FormData();
formData.append("audio", blob);
const response = await fetch(
"https://durga-7780-voicebot.hf.space/api/process-audio",
{
method: "POST",
body: formData,
}
);
const data = await response.json();
if (!response.ok || !data.audio_url) {
console.error("Backend error:", data);
setStatus("idle");
return;
}
playBotAudio(data.audio_url);
};
// =========================
// PLAY BOT AUDIO
// =========================
const playBotAudio = (url) => {
setStatus("speaking");
const audio = new Audio(url + "?t=" + Date.now());
audio.onended = () => setStatus("idle");
audio.play();
};
// =========================
// UI
// =========================
return (
<div style={styles.container}>
{status === "idle" ? (
<button onClick={startRecording} style={styles.mic}>
🎤
</button>
) : (
<Loader />
)}
</div>
);
}
const styles = {
container: {
height: "100vh",
background: "#111",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
mic: {
width: 120,
height: 120,
borderRadius: "50%",
fontSize: 40,
background: "#4CAF50",
color: "#fff",
border: "none",
cursor: "pointer",
},
};