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 startTimeRef = React.useRef<number>(0);
|
||||||
const animationFrameRef = React.useRef<number>(0);
|
const animationFrameRef = React.useRef<number>(0);
|
||||||
const selectedDeviceIdRef = React.useRef<string>('');
|
const selectedDeviceIdRef = React.useRef<string>('');
|
||||||
|
const isMonitoringRef = React.useRef<boolean>(false);
|
||||||
|
|
||||||
// Request microphone permission
|
// Request microphone permission
|
||||||
const requestPermission = React.useCallback(async (): Promise<boolean> => {
|
const requestPermission = React.useCallback(async (): Promise<boolean> => {
|
||||||
@@ -73,6 +74,8 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
||||||
|
|
||||||
const updateLevel = () => {
|
const updateLevel = () => {
|
||||||
|
if (!isMonitoringRef.current) return;
|
||||||
|
|
||||||
analyser.getByteTimeDomainData(dataArray);
|
analyser.getByteTimeDomainData(dataArray);
|
||||||
|
|
||||||
// Calculate RMS level
|
// Calculate RMS level
|
||||||
@@ -85,13 +88,11 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
|
|
||||||
setState((prev) => ({ ...prev, inputLevel: rms }));
|
setState((prev) => ({ ...prev, inputLevel: rms }));
|
||||||
|
|
||||||
if (state.isRecording && !state.isPaused) {
|
animationFrameRef.current = requestAnimationFrame(updateLevel);
|
||||||
animationFrameRef.current = requestAnimationFrame(updateLevel);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLevel();
|
updateLevel();
|
||||||
}, [state.isRecording, state.isPaused]);
|
}, []);
|
||||||
|
|
||||||
// Start recording
|
// Start recording
|
||||||
const startRecording = React.useCallback(async (): Promise<void> => {
|
const startRecording = React.useCallback(async (): Promise<void> => {
|
||||||
@@ -141,6 +142,7 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Start monitoring input level
|
// Start monitoring input level
|
||||||
|
isMonitoringRef.current = true;
|
||||||
monitorInputLevel();
|
monitorInputLevel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start recording:', error);
|
console.error('Failed to start recording:', error);
|
||||||
@@ -172,6 +174,7 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
|
isMonitoringRef.current = false;
|
||||||
if (audioContextRef.current) {
|
if (audioContextRef.current) {
|
||||||
await audioContextRef.current.close();
|
await audioContextRef.current.close();
|
||||||
}
|
}
|
||||||
@@ -203,6 +206,7 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
mediaRecorderRef.current.pause();
|
mediaRecorderRef.current.pause();
|
||||||
setState((prev) => ({ ...prev, isPaused: true }));
|
setState((prev) => ({ ...prev, isPaused: true }));
|
||||||
|
|
||||||
|
isMonitoringRef.current = false;
|
||||||
if (animationFrameRef.current) {
|
if (animationFrameRef.current) {
|
||||||
cancelAnimationFrame(animationFrameRef.current);
|
cancelAnimationFrame(animationFrameRef.current);
|
||||||
}
|
}
|
||||||
@@ -214,6 +218,7 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
if (mediaRecorderRef.current && state.isRecording && state.isPaused) {
|
if (mediaRecorderRef.current && state.isRecording && state.isPaused) {
|
||||||
mediaRecorderRef.current.resume();
|
mediaRecorderRef.current.resume();
|
||||||
setState((prev) => ({ ...prev, isPaused: false }));
|
setState((prev) => ({ ...prev, isPaused: false }));
|
||||||
|
isMonitoringRef.current = true;
|
||||||
monitorInputLevel();
|
monitorInputLevel();
|
||||||
}
|
}
|
||||||
}, [state.isRecording, state.isPaused, monitorInputLevel]);
|
}, [state.isRecording, state.isPaused, monitorInputLevel]);
|
||||||
@@ -233,6 +238,7 @@ export function useRecording(): UseRecordingReturn {
|
|||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
isMonitoringRef.current = false;
|
||||||
if (streamRef.current) {
|
if (streamRef.current) {
|
||||||
streamRef.current.getTracks().forEach((track) => track.stop());
|
streamRef.current.getTracks().forEach((track) => track.stop());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user