fix(audio): robust async buffer loading with state merging + API fallback

- Rewrite loadAudioBuffersForTracks to use functional setTracks
  preventing loss of intermediate state updates
- Add API download endpoint as fallback when static fetch fails
- Also update sessionTabs tracks with loaded buffers
- return decodeAudioFile result (audioBuffer+channelInfo) instead
  of just audioBuffer
This commit is contained in:
2026-07-30 22:30:56 +07:00
parent be3205645f
commit b1aec24392
2 changed files with 57 additions and 41 deletions
+56 -40
View File
@@ -9860,65 +9860,81 @@ const App = () => {
const loadAudioBuffersForTracks = async (tracksList) => { const loadAudioBuffersForTracks = async (tracksList) => {
let hasLoadedAny = false; let hasLoadedAny = false;
const loadBuffer = async (url) => {
const res = await fetch(url);
if (!res.ok) return null;
const blob = await res.blob();
return await window.SonicAudio.decodeAudioFile(blob);
};
const tryLoad = async (fileId) => {
if (!fileId) return null;
try {
const result = await loadBuffer('/static/audio/uploads/' + fileId);
if (result) return result;
} catch (_) {}
try {
const result = await loadBuffer(`${API_AUDIO}/download/${fileId}`);
if (result) return result;
} catch (_) {}
return null;
};
const updatedTracks = await Promise.all(tracksList.map(async t => { const updatedTracks = await Promise.all(tracksList.map(async t => {
let trackBuffer = t.buffer; let trackBuffer = t.buffer;
let trackChannelInfo = t.channelInfo; let trackChannelInfo = t.channelInfo;
if (t.serverFileId && !trackBuffer) { if (t.serverFileId && !trackBuffer) {
try { const result = await tryLoad(t.serverFileId);
const res = await fetch('/static/audio/uploads/' + t.serverFileId); if (result) {
if (res.ok) { trackBuffer = result.audioBuffer;
const blob = await res.blob(); trackChannelInfo = result.channelInfo;
const { audioBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(blob); hasLoadedAny = true;
trackBuffer = audioBuffer;
trackChannelInfo = channelInfo;
hasLoadedAny = true;
}
} catch (e) {
console.warn("Failed to autoload track buffer for " + t.serverFileId, e);
} }
} }
let clipsUpdated = false;
const updatedClips = await Promise.all((t.clips || []).map(async c => { const updatedClips = await Promise.all((t.clips || []).map(async c => {
let clipBuffer = c.buffer; let clipBuffer = c.buffer;
const targetFileId = c.serverFileId || t.serverFileId; const targetFileId = c.serverFileId || t.serverFileId;
if (targetFileId && !clipBuffer) { if (targetFileId && !clipBuffer) {
try { const result = await tryLoad(targetFileId);
const res = await fetch('/static/audio/uploads/' + targetFileId); if (result) {
if (res.ok) { clipBuffer = result.audioBuffer;
const blob = await res.blob(); hasLoadedAny = true;
const { audioBuffer } = await window.SonicAudio.decodeAudioFile(blob);
clipBuffer = audioBuffer;
clipsUpdated = true;
hasLoadedAny = true;
}
} catch (e) {
console.warn("Failed to autoload clip buffer for " + targetFileId, e);
} }
} }
return { ...c, buffer: clipBuffer }; return { ...c, buffer: clipBuffer };
})); }));
if (trackBuffer && updatedClips.length === 0) { if (trackBuffer && updatedClips.length === 0) {
const clipId = `default_${t.id}`; const clipId = `default_${t.id}`;
return { return { ...t, buffer: trackBuffer, channelInfo: trackChannelInfo,
...t, clips: [{ id: clipId, buffer: trackBuffer, startTime: t.startTime || 0, name: t.name, speed: 1.0 }] };
buffer: trackBuffer,
channelInfo: trackChannelInfo,
clips: [{ id: clipId, buffer: trackBuffer, startTime: t.startTime || 0, name: t.name, speed: 1.0 }]
};
} }
return { ...t, buffer: trackBuffer, channelInfo: trackChannelInfo, clips: updatedClips };
return {
...t,
buffer: trackBuffer,
channelInfo: trackChannelInfo,
clips: updatedClips
};
})); }));
if (hasLoadedAny) { if (hasLoadedAny) {
setTracks(updatedTracks); setTracks(prev => {
const merged = [...updatedTracks];
(prev || []).forEach((pt, i) => {
if (!merged[i]) merged[i] = pt;
else {
merged[i] = { ...merged[i] };
merged[i].clips = (pt.clips || []).map((pc, j) => {
if (merged[i].clips && merged[i].clips[j] && (merged[i].clips[j].buffer || pc.buffer)) {
return { ...pc, buffer: pc.buffer || merged[i].clips[j].buffer };
}
if ((pc.buffer || (merged[i].clips && merged[i].clips[j] && merged[i].clips[j].buffer))) {
return pc;
}
return merged[i].clips && merged[i].clips[j] ? merged[i].clips[j] : pc;
});
}
});
return merged;
});
setSessionTabs(prev => prev.map(st => ({
...st,
tracks: (st.tracks || []).map(t => {
const found = updatedTracks.find(u => u.id === t.id);
return found || t;
})
})));
} }
}; };
File diff suppressed because one or more lines are too long