fix(export): progress bar hien thi duoc - footer ngoai vung scroll + encode chunked yield + yield frame dau

This commit is contained in:
2026-08-24 18:31:28 +07:00
parent fefe95d0a9
commit 14f8340fdf
3 changed files with 39 additions and 23 deletions
+31 -19
View File
@@ -12626,6 +12626,8 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
<span className="text-xs text-zinc-300">Realtime (bounce — chạy lại project thật, giữ nguyên MIDI + FX Rack + Mastering Chain)</span>
</label>
</div>
</div>
<div className="p-4 pt-3 border-t border-slate-700 space-y-2">
{isExporting && (
<div className="bg-[#141414] border border-zinc-800 rounded px-2 py-1.5">
<div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
@@ -12636,7 +12638,7 @@ const ExportModal = ({ open, onClose, exportSettings, setExportSettings, isExpor
</div>
</div>
)}
<div className="grid grid-cols-2 gap-2 pt-1">
<div className="grid grid-cols-2 gap-2">
<button onClick={onExport} disabled={isExporting}
className="w-full py-1 bg-cyan-700 hover:bg-cyan-600 text-zinc-100 font-bold rounded text-xs flex items-center justify-center gap-1">
<i data-lucide="download-cloud" className="w-3 h-3" /> {isExporting ? '...' : 'Export'}
@@ -28481,6 +28483,9 @@ const App = () => {
setIsExporting(true);
setExportProgress(5);
showToast("Đang trộn âm thanh đa kênh (Offline Mixdown)...", "info");
// Yield 1 frame → React vẽ thanh "Đang xử lý..." trước khi graph build +
// OfflineAudioContext render chặn main thread.
await new Promise(res => setTimeout(res, 30));
try {
const targetRate = parseInt(exportSettings.sampleRate);
const isFloatWav = exportSettings.bitDepth === 'float';
@@ -28673,26 +28678,33 @@ const App = () => {
view.setUint32(40, exportLength * bytesPerSample * numExportCh, true);
let offset = 44;
let peak = 0;
for (let i = 0; i < exportLength; i++) {
if ((i & 0x3FFF) === 0) setExportProgress(60 + Math.round((i / Math.max(1, exportLength)) * 32));
for (let ch = 0; ch < numExportCh; ch++) {
const chData = renderedBuffer.getChannelData(ch);
const sample = Math.max(-1, Math.min(1, chData[i]));
if (Math.abs(sample) > peak) peak = Math.abs(sample);
if (bitDepth === 8) {
view.setUint8(offset, Math.floor((sample + 1.0) * 127.5));
} else if (bitDepth === 16) {
view.setInt16(offset, Math.floor(sample < 0 ? sample * 0x8000 : sample * 0x7FFF), true);
} else if (bitDepth === 24) {
const val24 = Math.floor(sample < 0 ? sample * 0x800000 : sample * 0x7FFFFF);
view.setUint8(offset, val24 & 0xFF);
view.setUint8(offset + 1, val24 >> 8 & 0xFF);
view.setUint8(offset + 2, val24 >> 16 & 0xFF);
} else if (isFloatWav) {
view.setFloat32(offset, sample, true);
// Chunk encode + yield giữa các chunk → React vẽ thanh tiến độ thật sự
// (vòng lặp đồng bộ dài sẽ chặn main thread, bar không bao giờ hiện).
const ENC_CHUNK = 65536;
for (let base = 0; base < exportLength; base += ENC_CHUNK) {
const encEnd = Math.min(exportLength, base + ENC_CHUNK);
for (let i = base; i < encEnd; i++) {
for (let ch = 0; ch < numExportCh; ch++) {
const chData = renderedBuffer.getChannelData(ch);
const sample = Math.max(-1, Math.min(1, chData[i]));
if (Math.abs(sample) > peak) peak = Math.abs(sample);
if (bitDepth === 8) {
view.setUint8(offset, Math.floor((sample + 1.0) * 127.5));
} else if (bitDepth === 16) {
view.setInt16(offset, Math.floor(sample < 0 ? sample * 0x8000 : sample * 0x7FFF), true);
} else if (bitDepth === 24) {
const val24 = Math.floor(sample < 0 ? sample * 0x800000 : sample * 0x7FFFFF);
view.setUint8(offset, val24 & 0xFF);
view.setUint8(offset + 1, val24 >> 8 & 0xFF);
view.setUint8(offset + 2, val24 >> 16 & 0xFF);
} else if (isFloatWav) {
view.setFloat32(offset, sample, true);
}
offset += bytesPerSample;
}
offset += bytesPerSample;
}
setExportProgress(60 + Math.round((encEnd / Math.max(1, exportLength)) * 32));
await new Promise(res => setTimeout(res, 0));
}
if (peak < 0.001) {
showToast("Cảnh báo: file export gần như CÂM (không có tín hiệu âm) — kiểm tra nguồn track / MIDI cache preview.", "warning");
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -51,7 +51,7 @@
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/services/chordTheory.js?v=202608211300"></script>
<script src="/static/js/app.precompiled.js?v=202608240010" defer></script>
<script src="/static/js/app.precompiled.js?v=202608240040" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {