import React, { useState, useRef, useEffect } from 'react';
import { Box, IconButton, Typography, Tooltip, CircularProgress } from '@mui/material';
import {
Mic as MicIcon,
Stop as StopIcon,
Close as CloseIcon,
Check as CheckIcon
} from '@mui/icons-material';
import { transcribeAudio } from '../api/client';
/**
* AudioRecorder Component
* Provides live audio recording with real-time visualization and transcription
*/
function AudioRecorder({ onTranscript, onCancel }) {
const [isRecording, setIsRecording] = useState(false);
const [recordingTime, setRecordingTime] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const [audioLevel, setAudioLevel] = useState(0);
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const timerRef = useRef(null);
const analyserRef = useRef(null);
const animationRef = useRef(null);
useEffect(() => {
return () => {
// Cleanup on unmount
stopRecording();
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, []);
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// Setup audio analyzer for visualization
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
source.connect(analyser);
analyserRef.current = analyser;
// Start visualization
visualizeAudio();
// Setup media recorder
const mediaRecorder = new MediaRecorder(stream);
mediaRecorderRef.current = mediaRecorder;
audioChunksRef.current = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
audioChunksRef.current.push(event.data);
}
};
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
await handleTranscription(audioBlob);
// Stop all tracks
stream.getTracks().forEach(track => track.stop());
};
mediaRecorder.start();
setIsRecording(true);
// Start timer
timerRef.current = setInterval(() => {
setRecordingTime(prev => prev + 1);
}, 1000);
} catch (error) {
console.error('Failed to start recording:', error);
alert('Microphone access denied or not available');
}
};
const stopRecording = () => {
if (mediaRecorderRef.current && isRecording) {
mediaRecorderRef.current.stop();
setIsRecording(false);
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
}
};
const visualizeAudio = () => {
if (!analyserRef.current) return;
const analyser = analyserRef.current;
const dataArray = new Uint8Array(analyser.frequencyBinCount);
const updateLevel = () => {
analyser.getByteFrequencyData(dataArray);
const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
setAudioLevel(Math.min(100, (average / 255) * 100));
if (isRecording) {
animationRef.current = requestAnimationFrame(updateLevel);
}
};
updateLevel();
};
const handleTranscription = async (audioBlob) => {
setIsProcessing(true);
try {
const result = await transcribeAudio(audioBlob);
if (result.success && result.transcript) {
onTranscript(result.transcript);
} else {
alert('Transcription failed. Please try again.');
}
} catch (error) {
console.error('Transcription error:', error);
alert('Failed to transcribe audio: ' + error.message);
} finally {
setIsProcessing(false);
setRecordingTime(0);
setAudioLevel(0);
}
};
const handleCancel = () => {
if (isRecording) {
mediaRecorderRef.current.stop();
setIsRecording(false);
if (timerRef.current) {
clearInterval(timerRef.current);
}
}
setRecordingTime(0);
setAudioLevel(0);
onCancel();
};
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
if (isProcessing) {
return (
Transcribing audio...
);
}
return (
{/* Record/Stop Button */}
{isRecording ? : }
{/* Recording Info */}
{isRecording ? 'Recording...' : 'Ready to record'}
{isRecording && (
{formatTime(recordingTime)}
)}
{/* Audio Level Indicator */}
{isRecording && (
)}
{/* Cancel Button */}
);
}
export default AudioRecorder;