fix: resolve recording level meter monitoring closure issue
Fixed the input level meter staying at 0% during recording by: Closure Issue Resolution: - Added isMonitoringRef to track monitoring state independent of React state - Removed state dependencies from monitorInputLevel callback - Animation loop now checks ref instead of stale closure state Changes: - Set isMonitoringRef.current = true when starting recording - Set isMonitoringRef.current = false when stopping/pausing recording - Animation frame continues while ref is true, stops when false - Proper cleanup in stopRecording, pauseRecording, and unmount effect This ensures the requestAnimationFrame loop continues properly and updates the RMS level calculation in real-time during recording. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
const startTimeRef = React.useRef<number>(0);
|
||||
const animationFrameRef = React.useRef<number>(0);
|
||||
const selectedDeviceIdRef = React.useRef<string>('');
|
||||
const isMonitoringRef = React.useRef<boolean>(false);
|
||||
|
||||
// Request microphone permission
|
||||
const requestPermission = React.useCallback(async (): Promise<boolean> => {
|
||||
@@ -73,6 +74,8 @@ export function useRecording(): UseRecordingReturn {
|
||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||
|
||||
const updateLevel = () => {
|
||||
if (!isMonitoringRef.current) return;
|
||||
|
||||
analyser.getByteTimeDomainData(dataArray);
|
||||
|
||||
// Calculate RMS level
|
||||
@@ -85,13 +88,11 @@ export function useRecording(): UseRecordingReturn {
|
||||
|
||||
setState((prev) => ({ ...prev, inputLevel: rms }));
|
||||
|
||||
if (state.isRecording && !state.isPaused) {
|
||||
animationFrameRef.current = requestAnimationFrame(updateLevel);
|
||||
}
|
||||
animationFrameRef.current = requestAnimationFrame(updateLevel);
|
||||
};
|
||||
|
||||
updateLevel();
|
||||
}, [state.isRecording, state.isPaused]);
|
||||
}, []);
|
||||
|
||||
// Start recording
|
||||
const startRecording = React.useCallback(async (): Promise<void> => {
|
||||
@@ -141,6 +142,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
});
|
||||
|
||||
// Start monitoring input level
|
||||
isMonitoringRef.current = true;
|
||||
monitorInputLevel();
|
||||
} catch (error) {
|
||||
console.error('Failed to start recording:', error);
|
||||
@@ -172,6 +174,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
||||
|
||||
// Clean up
|
||||
isMonitoringRef.current = false;
|
||||
if (audioContextRef.current) {
|
||||
await audioContextRef.current.close();
|
||||
}
|
||||
@@ -203,6 +206,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
mediaRecorderRef.current.pause();
|
||||
setState((prev) => ({ ...prev, isPaused: true }));
|
||||
|
||||
isMonitoringRef.current = false;
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
@@ -214,6 +218,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
if (mediaRecorderRef.current && state.isRecording && state.isPaused) {
|
||||
mediaRecorderRef.current.resume();
|
||||
setState((prev) => ({ ...prev, isPaused: false }));
|
||||
isMonitoringRef.current = true;
|
||||
monitorInputLevel();
|
||||
}
|
||||
}, [state.isRecording, state.isPaused, monitorInputLevel]);
|
||||
@@ -233,6 +238,7 @@ export function useRecording(): UseRecordingReturn {
|
||||
// Cleanup on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
isMonitoringRef.current = false;
|
||||
if (streamRef.current) {
|
||||
streamRef.current.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user