fix(05-01): fix loadJamTrack fqId bug and extend mediaSlice state

- Fix critical bug: JamTrackPlay now uses fqId instead of jamTrack.id
- Move fqId construction outside conditional for consistent availability
- Extend jamTrackState with 7 fields (isPlaying, isPaused, position, duration, etc)
- Replace downloadingJamTrack with full downloadState (8 fields, 6-state machine)
- Add 3 new reducers: setJamTrackState, setDownloadState, clearDownloadState
- Update clearJamTrackState and clearAllMedia to reset to proper structures
- Update loadJamTrack extraReducers to use downloadState
- Update selectors: remove selectDownloadingJamTrack, add selectDownloadState

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-14 23:57:12 +05:30
parent cc32abcaba
commit bb74c50462
1 changed files with 104 additions and 14 deletions

View File

@ -17,16 +17,18 @@ export const loadJamTrack = createAsyncThunk(
'media/loadJamTrack',
async ({ jamTrack, jamClient }, { rejectWithValue }) => {
try {
// Build fqId for all JamTrack calls (format: {jamTrackId}-{sampleRate})
const sampleRate = await jamClient.GetSampleRate();
const sampleRateForFilename = sampleRate === 48 ? '48' : '44';
const fqId = `${jamTrack.id}-${sampleRateForFilename}`;
// Load JMep data if available (matches MediaContext:163-170 logic)
if (jamTrack.jmep) {
const sampleRate = await jamClient.GetSampleRate();
const sampleRateForFilename = sampleRate === 48 ? '48' : '44';
const fqId = `${jamTrack.id}-${sampleRateForFilename}`;
await jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep);
}
// Play/load the jamtrack
const result = await jamClient.JamTrackPlay(jamTrack.id);
const result = await jamClient.JamTrackPlay(fqId);
if (!result) {
throw new Error('Unable to open JamTrack');
@ -59,8 +61,27 @@ const initialState = {
recordedTracks: [], // [{ recordingName, isOpener, userName, instrumentIcon, track, mixers }]
// JamTrack state (real-time playback state from native client)
jamTrackState: {}, // Real-time jamTrack playback state from WebSocket JAM_TRACK_CHANGES
downloadingJamTrack: false,
jamTrackState: {
isPlaying: false,
isPaused: false,
currentPositionMs: 0,
durationMs: 0,
selectedMixdownId: null,
playbackMode: null, // 'master' | 'custom-mix' | 'stem'
lastUpdate: null
},
// Download/sync state machine
downloadState: {
jamTrackId: null,
mixdownId: null,
fqId: null,
state: 'idle', // 'idle' | 'checking' | 'downloading' | 'keying' | 'synchronized' | 'error'
progress: 0,
currentStep: 0,
totalSteps: 0,
error: null
},
// Loading states for async operations
loading: {
@ -94,8 +115,38 @@ export const mediaSlice = createSlice({
state.jamTrackState = { ...state.jamTrackState, ...action.payload };
},
setJamTrackState: (state, action) => {
state.jamTrackState = action.payload;
},
clearJamTrackState: (state) => {
state.jamTrackState = {};
state.jamTrackState = {
isPlaying: false,
isPaused: false,
currentPositionMs: 0,
durationMs: 0,
selectedMixdownId: null,
playbackMode: null,
lastUpdate: null
};
},
// Download state management
setDownloadState: (state, action) => {
state.downloadState = { ...state.downloadState, ...action.payload };
},
clearDownloadState: (state) => {
state.downloadState = {
jamTrackId: null,
mixdownId: null,
fqId: null,
state: 'idle',
progress: 0,
currentStep: 0,
totalSteps: 0,
error: null
};
},
// Clear all media (called on session end or media close)
@ -103,8 +154,25 @@ export const mediaSlice = createSlice({
state.backingTracks = [];
state.jamTracks = [];
state.recordedTracks = [];
state.jamTrackState = {};
state.downloadingJamTrack = false;
state.jamTrackState = {
isPlaying: false,
isPaused: false,
currentPositionMs: 0,
durationMs: 0,
selectedMixdownId: null,
playbackMode: null,
lastUpdate: null
};
state.downloadState = {
jamTrackId: null,
mixdownId: null,
fqId: null,
state: 'idle',
progress: 0,
currentStep: 0,
totalSteps: 0,
error: null
};
state.error = null;
}
},
@ -129,17 +197,18 @@ export const mediaSlice = createSlice({
// Load jam track
.addCase(loadJamTrack.pending, (state) => {
state.loading.jamTrack = true;
state.downloadingJamTrack = true;
state.downloadState.state = 'checking';
state.error = null;
})
.addCase(loadJamTrack.fulfilled, (state) => {
state.loading.jamTrack = false;
state.downloadingJamTrack = false;
state.downloadState.state = 'synchronized';
// Note: jamTracks array updated by MIXER_CHANGES WebSocket message
})
.addCase(loadJamTrack.rejected, (state, action) => {
state.loading.jamTrack = false;
state.downloadingJamTrack = false;
state.downloadState.state = 'error';
state.downloadState.error = { message: action.payload };
state.error = action.payload;
})
@ -153,7 +222,25 @@ export const mediaSlice = createSlice({
state.backingTracks = [];
state.jamTracks = [];
state.recordedTracks = [];
state.jamTrackState = {};
state.jamTrackState = {
isPlaying: false,
isPaused: false,
currentPositionMs: 0,
durationMs: 0,
selectedMixdownId: null,
playbackMode: null,
lastUpdate: null
};
state.downloadState = {
jamTrackId: null,
mixdownId: null,
fqId: null,
state: 'idle',
progress: 0,
currentStep: 0,
totalSteps: 0,
error: null
};
})
.addCase(closeMedia.rejected, (state, action) => {
state.loading.closing = false;
@ -167,7 +254,10 @@ export const {
setJamTracks,
setRecordedTracks,
updateJamTrackState,
setJamTrackState,
clearJamTrackState,
setDownloadState,
clearDownloadState,
clearAllMedia
} = mediaSlice.actions;
@ -178,6 +268,6 @@ export const selectBackingTracks = (state) => state.media.backingTracks;
export const selectJamTracks = (state) => state.media.jamTracks;
export const selectRecordedTracks = (state) => state.media.recordedTracks;
export const selectJamTrackState = (state) => state.media.jamTrackState;
export const selectDownloadingJamTrack = (state) => state.media.downloadingJamTrack;
export const selectDownloadState = (state) => state.media.downloadState;
export const selectMediaLoading = (state) => state.media.loading;
export const selectMediaError = (state) => state.media.error;