feat(05-04): implement mixdown picker UI with hierarchy sorting

- Add handleMixdownChange callback to switch between mixdowns
- Stop and restart playback when changing mixdown (if playing)
- Add mixdown dropdown selector with sorted display
- Sort order: master → custom mixes → stems (alphabetical within types)
- Visual indicators: 🎵 master, 🎨 custom, 🎸 stem
- Disable picker during operations and loading
- Update selectedMixdownId local state and activeMixdown Redux state

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-15 01:00:03 +05:30
parent 7014107152
commit 845b44a319
1 changed files with 71 additions and 0 deletions

View File

@ -241,6 +241,46 @@ const JKSessionJamTrackPlayer = ({
}
}, [isOperating, jamClient, jamTrackState.isPaused, dispatch]);
// Mixdown change handler
const handleMixdownChange = useCallback(async (mixdownId) => {
if (isOperating || !jamClient || !fqIdRef.current) return;
try {
setIsOperating(true);
setError(null);
// Find the new mixdown
const mixdown = availableMixdowns.find(m => m.id === mixdownId);
if (!mixdown) {
throw new Error('Mixdown not found');
}
// Update local state
setSelectedMixdownId(mixdownId);
dispatch(setActiveMixdown(mixdown));
// If currently playing, stop and restart with new mixdown
if (jamTrackState.isPlaying || jamTrackState.isPaused) {
await jamClient.JamTrackStop(fqIdRef.current);
await dispatch(loadJamTrack({
jamTrack,
mixdownId,
autoPlay: true,
jamClient
})).unwrap();
}
} catch (err) {
console.error('[JamTrack] Mixdown change error:', err);
setError({ type: 'playback', message: 'Failed to change mixdown' });
} finally {
if (mountedRef.current) {
setIsOperating(false);
}
}
}, [isOperating, jamClient, jamTrack, jamTrackState, availableMixdowns, dispatch]);
// Helper: Format milliseconds to MM:SS
const formatTime = (ms) => {
if (!ms || isNaN(ms)) return '00:00';
@ -354,6 +394,37 @@ const JKSessionJamTrackPlayer = ({
{formatTime(jamTrackState.currentPositionMs)} / {formatTime(jamTrackState.durationMs)}
</p>
</div>
{availableMixdowns.length > 0 && (
<div style={{ marginTop: '10px' }}>
<label>Mixdown:</label>
<select
value={selectedMixdownId || ''}
onChange={(e) => handleMixdownChange(parseInt(e.target.value, 10))}
disabled={isOperating || isLoadingSync}
>
{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 => (
<option key={mixdown.id} value={mixdown.id}>
{mixdown.type === 'master' && '🎵 '}
{(mixdown.type === 'custom' || mixdown.type === 'custom-mix') && '🎨 '}
{mixdown.type === 'stem' && '🎸 '}
{mixdown.name}
</option>
))
}
</select>
</div>
)}
</div>
);
};