From dbaaba82f960b4c3aebfbe6d7439afc359382b71 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 25 Feb 2026 19:13:42 +0530 Subject: [PATCH] feat(26-02): cleanup callbacks on unmount and defer controls rendering - Import and call cleanupJamTrackCallbacks in cleanup useEffect - Wrap controls section in condition: only render when downloadState is 'idle' or 'synchronized' and not in initial loading - Fixes memory leak from orphaned window.jamTrackDownload* globals - Fixes UX issue where controls appeared before track ready --- .../client/JKSessionJamTrackPlayer.js | 280 +++++++++--------- 1 file changed, 143 insertions(+), 137 deletions(-) diff --git a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js index 2c7d50b7d..b787bb098 100644 --- a/jam-ui/src/components/client/JKSessionJamTrackPlayer.js +++ b/jam-ui/src/components/client/JKSessionJamTrackPlayer.js @@ -5,7 +5,8 @@ import { checkJamTrackSync, closeJamTrack, setJamTrackState, - setDownloadState + setDownloadState, + cleanupJamTrackCallbacks } from '../../store/features/mediaSlice'; import { setOpenJamTrack, clearOpenJamTrack } from '../../store/features/sessionUISlice'; import { setAvailableMixdowns, setActiveMixdown } from '../../store/features/activeSessionSlice'; @@ -168,6 +169,9 @@ const JKSessionJamTrackPlayer = ({ dispatch(closeJamTrack({ fqId: fqIdRef.current, jamClient })); dispatch(clearOpenJamTrack()); } + + // Clean up download callbacks to prevent memory leaks + cleanupJamTrackCallbacks(); }; }, [dispatch, jamClient]); @@ -717,152 +721,154 @@ const JKSessionJamTrackPlayer = ({ )} - {/* Controls Section */} -
- {/* Circular Buttons and Seek Bar */} -
- {/* Play Button - Circular */} - - - {/* Stop Button - Circular */} - - - {/* Time and Seek Bar */} -
- - {formattedPosition} - - handleSeek(parseInt(e.target.value, 10))} - disabled={isOperating || !jamTrackState.durationMs} + {/* Controls Section - only show when track is ready (not in loading/download states) */} + {(downloadState.state === 'idle' || downloadState.state === 'synchronized') && !isLoadingSync && ( +
+ {/* Circular Buttons and Seek Bar */} +
+ {/* Play Button - Circular */} +
-
+ > + {jamTrackState.isPlaying && !jamTrackState.isPaused ? ( + + + + + ) : ( + + + + )} + - {/* Mix Selector */} - {availableMixdowns.length > 0 && ( -
-
- - Mix: + {/* Stop Button - Circular */} + + + {/* Time and Seek Bar */} +
+ + {formattedPosition} - handleSeek(parseInt(e.target.value, 10))} + disabled={isOperating || !jamTrackState.durationMs} style={{ flex: 1, - padding: '6px 12px', - fontSize: '13px', - border: '1px solid #d1d1d6', - borderRadius: '6px', - background: 'white', - color: '#1d1d1f', - cursor: 'pointer', - outline: 'none' + height: '4px', + borderRadius: '2px', + outline: 'none', + background: `linear-gradient(to right, #5b9bd5 0%, #5b9bd5 ${progressPercent}%, #ddd ${progressPercent}%, #ddd 100%)`, + WebkitAppearance: 'none', + cursor: (isOperating || !jamTrackState.durationMs) ? 'not-allowed' : 'pointer' }} - > - {availableMixdowns - .slice() - .sort((a, b) => { - // Sort order: master first, then custom mixes, then stems - if (a.type === 'master') return -1; - if (b.type === 'master') return 1; - if (a.type === 'custom' || a.type === 'custom-mix') return -1; - if (b.type === 'custom' || b.type === 'custom-mix') return 1; - return a.name.localeCompare(b.name); - }) - .map(mixdown => ( - - )) - } - -
-
- )} -
+ + {/* Mix Selector */} + {availableMixdowns.length > 0 && ( +
+
+ + Mix: + + +
+ +
+ )} +
+ )} {/* Close Button */}