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) => {
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 => {
let trackBuffer = t.buffer;
let trackChannelInfo = t.channelInfo;
if (t.serverFileId && !trackBuffer) {
try {
const res = await fetch('/static/audio/uploads/' + t.serverFileId);
if (res.ok) {
const blob = await res.blob();
const { audioBuffer, channelInfo } = await window.SonicAudio.decodeAudioFile(blob);
trackBuffer = audioBuffer;
trackChannelInfo = channelInfo;
hasLoadedAny = true;
}
} catch (e) {
console.warn("Failed to autoload track buffer for " + t.serverFileId, e);
const result = await tryLoad(t.serverFileId);
if (result) {
trackBuffer = result.audioBuffer;
trackChannelInfo = result.channelInfo;
hasLoadedAny = true;
}
}
let clipsUpdated = false;
const updatedClips = await Promise.all((t.clips || []).map(async c => {
let clipBuffer = c.buffer;
const targetFileId = c.serverFileId || t.serverFileId;
if (targetFileId && !clipBuffer) {
try {
const res = await fetch('/static/audio/uploads/' + targetFileId);
if (res.ok) {
const blob = await res.blob();
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);
const result = await tryLoad(targetFileId);
if (result) {
clipBuffer = result.audioBuffer;
hasLoadedAny = true;
}
}
return { ...c, buffer: clipBuffer };
}));
if (trackBuffer && updatedClips.length === 0) {
const clipId = `default_${t.id}`;
return {
...t,
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: [{ 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) {
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;
})
})));
}
};