fix: resolve playback level monitoring closure issue
Fixed playback level meters staying at 0% by resolving React closure issue in the monitoring loop - same pattern as the recording fix. The Problem: - monitorPlaybackLevels callback checked stale `isPlaying` state - Animation loop would run once and never continue - Dependency on isPlaying caused callback recreation on every state change The Solution: - Added isMonitoringLevelsRef to track state independent of React - Removed isPlaying dependency from callback (now has empty deps []) - Set ref to true when starting playback - Set ref to false when pausing, stopping, or ending playback - Animation loop checks ref instead of stale closure state Monitoring State Management: - Start: play() sets isMonitoringLevelsRef.current = true - Stop: pause(), stop(), onended, and cleanup set it to false - Loop: continues while ref is true, stops when false This ensures the requestAnimationFrame loop runs continuously during playback and calculates real-time RMS levels for all tracks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
const pausedAtRef = useRef<number>(0);
|
||||
const animationFrameRef = useRef<number | null>(null);
|
||||
const levelMonitorFrameRef = useRef<number | null>(null);
|
||||
const isMonitoringLevelsRef = useRef<boolean>(false);
|
||||
const tracksRef = useRef<Track[]>(tracks); // Always keep latest tracks
|
||||
|
||||
// Keep tracksRef in sync with tracks prop
|
||||
@@ -52,7 +53,7 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
|
||||
// Monitor playback levels for all tracks
|
||||
const monitorPlaybackLevels = useCallback(() => {
|
||||
if (!isPlaying || analyserNodesRef.current.length === 0) return;
|
||||
if (!isMonitoringLevelsRef.current || analyserNodesRef.current.length === 0) return;
|
||||
|
||||
const levels: Record<string, number> = {};
|
||||
|
||||
@@ -75,10 +76,8 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
|
||||
setTrackLevels(levels);
|
||||
|
||||
if (isPlaying) {
|
||||
levelMonitorFrameRef.current = requestAnimationFrame(monitorPlaybackLevels);
|
||||
}
|
||||
}, [isPlaying]);
|
||||
levelMonitorFrameRef.current = requestAnimationFrame(monitorPlaybackLevels);
|
||||
}, []);
|
||||
|
||||
const updatePlaybackPosition = useCallback(() => {
|
||||
if (!audioContextRef.current) return;
|
||||
@@ -88,12 +87,18 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
|
||||
if (newTime >= duration) {
|
||||
setIsPlaying(false);
|
||||
isMonitoringLevelsRef.current = false;
|
||||
setCurrentTime(0);
|
||||
pausedAtRef.current = 0;
|
||||
setTrackLevels({});
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = null;
|
||||
}
|
||||
if (levelMonitorFrameRef.current) {
|
||||
cancelAnimationFrame(levelMonitorFrameRef.current);
|
||||
levelMonitorFrameRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,8 +190,10 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
source.onended = () => {
|
||||
if (pausedAtRef.current + (audioContext.currentTime - startTimeRef.current) >= duration) {
|
||||
setIsPlaying(false);
|
||||
isMonitoringLevelsRef.current = false;
|
||||
setCurrentTime(0);
|
||||
pausedAtRef.current = 0;
|
||||
setTrackLevels({});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -194,6 +201,9 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
startTimeRef.current = audioContext.currentTime;
|
||||
setIsPlaying(true);
|
||||
updatePlaybackPosition();
|
||||
|
||||
// Start level monitoring
|
||||
isMonitoringLevelsRef.current = true;
|
||||
monitorPlaybackLevels();
|
||||
}, [tracks, duration, masterVolume, updatePlaybackPosition, monitorPlaybackLevels]);
|
||||
|
||||
@@ -217,6 +227,9 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
|
||||
setIsPlaying(false);
|
||||
|
||||
// Stop level monitoring
|
||||
isMonitoringLevelsRef.current = false;
|
||||
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = null;
|
||||
@@ -421,8 +434,10 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
source.onended = () => {
|
||||
if (pausedAtRef.current + (audioContext.currentTime - startTimeRef.current) >= duration) {
|
||||
setIsPlaying(false);
|
||||
isMonitoringLevelsRef.current = false;
|
||||
setCurrentTime(0);
|
||||
pausedAtRef.current = 0;
|
||||
setTrackLevels({});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -430,6 +445,9 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
startTimeRef.current = audioContext.currentTime;
|
||||
setIsPlaying(true);
|
||||
|
||||
// Start level monitoring
|
||||
isMonitoringLevelsRef.current = true;
|
||||
|
||||
// Start animation frame for position updates
|
||||
const updatePosition = () => {
|
||||
if (!audioContextRef.current) return;
|
||||
@@ -439,12 +457,17 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
|
||||
if (newTime >= duration) {
|
||||
setIsPlaying(false);
|
||||
isMonitoringLevelsRef.current = false;
|
||||
setCurrentTime(0);
|
||||
pausedAtRef.current = 0;
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
animationFrameRef.current = null;
|
||||
}
|
||||
if (levelMonitorFrameRef.current) {
|
||||
cancelAnimationFrame(levelMonitorFrameRef.current);
|
||||
levelMonitorFrameRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -513,6 +536,7 @@ export function useMultiTrackPlayer(tracks: Track[], masterVolume: number = 1) {
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
isMonitoringLevelsRef.current = false;
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user