From cf0c37caa6753654ae54103a269ee2960d63b415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Tue, 18 Nov 2025 14:56:30 +0100 Subject: [PATCH] fix: resolve recording level meter monitoring closure issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/hooks/useRecording.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/hooks/useRecording.ts b/lib/hooks/useRecording.ts index a5115e1..b94f11d 100644 --- a/lib/hooks/useRecording.ts +++ b/lib/hooks/useRecording.ts @@ -36,6 +36,7 @@ export function useRecording(): UseRecordingReturn { const startTimeRef = React.useRef(0); const animationFrameRef = React.useRef(0); const selectedDeviceIdRef = React.useRef(''); + const isMonitoringRef = React.useRef(false); // Request microphone permission const requestPermission = React.useCallback(async (): Promise => { @@ -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 => { @@ -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()); }