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:
2025-11-18 14:56:30 +01:00
parent c0ca4d7913
commit cf0c37caa6

View File

@@ -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());
}