Skip to content

Commit 5c085d2

Browse files
authored
Enhance audio download with fallback mechanism
Refactor download process to use File System Access API and add fallback to blob download. Introduce a new function for downloading audio as a blob.
1 parent e9be501 commit 5c085d2

1 file changed

Lines changed: 83 additions & 2 deletions

File tree

main.js

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ window.downloadQueueJob = async function(jobId) {
352352
const audioData = await queueManager.getAudioData(jobId);
353353

354354
if (audioData && audioData.chunks) {
355-
// Combine chunks and save as WAV
355+
// Try File System Access API first
356356
try {
357357
await audioDiskSaver.initSave();
358358

@@ -364,13 +364,94 @@ window.downloadQueueJob = async function(jobId) {
364364
updateProgress(100, "Download complete!");
365365
} catch (error) {
366366
console.error('Download error:', error);
367-
alert('Download failed: ' + error.message);
367+
368+
// Handle user cancellation more gracefully
369+
if (error.message && (error.message.includes('user aborted') || error.message.includes('abort'))) {
370+
console.log('User cancelled the file save dialog, trying alternative download...');
371+
372+
// Fallback: create a blob and download it
373+
await downloadAsBlob(jobId, audioData.chunks);
374+
} else {
375+
// For other errors, try the blob method as fallback
376+
console.log('File save failed, trying alternative download...');
377+
await downloadAsBlob(jobId, audioData.chunks);
378+
}
368379
}
369380
} else {
370381
alert('No audio data found for this job');
371382
}
372383
};
373384

385+
// Alternative download method using blob URLs
386+
async function downloadAsBlob(jobId, chunks) {
387+
try {
388+
updateProgress(0, "Creating audio file...");
389+
390+
// Combine chunks into a single array
391+
const samples = [];
392+
for (const chunk of chunks) {
393+
const chunkArray = new Float32Array(chunk);
394+
for (let i = 0; i < chunkArray.length; i++) {
395+
samples.push(chunkArray[i]);
396+
}
397+
}
398+
const allSamples = new Float32Array(samples);
399+
400+
// Create WAV header
401+
const numChannels = 1;
402+
const sampleRate = 24000;
403+
const bytesPerSample = 2;
404+
const blockAlign = numChannels * bytesPerSample;
405+
const byteRate = sampleRate * blockAlign;
406+
const dataSize = allSamples.length * bytesPerSample;
407+
const buffer = new ArrayBuffer(44 + dataSize);
408+
const view = new DataView(buffer);
409+
410+
// WAV header
411+
const writeString = (offset, string) => {
412+
for (let i = 0; i < string.length; i++) {
413+
view.setUint8(offset + i, string.charCodeAt(i));
414+
}
415+
};
416+
417+
writeString(0, 'RIFF');
418+
view.setUint32(4, 36 + dataSize, true);
419+
writeString(8, 'WAVE');
420+
writeString(12, 'fmt ');
421+
view.setUint32(16, 16, true);
422+
view.setUint16(20, 1, true);
423+
view.setUint16(22, numChannels, true);
424+
view.setUint32(24, sampleRate, true);
425+
view.setUint32(28, byteRate, true);
426+
view.setUint16(32, blockAlign, true);
427+
view.setUint16(34, 16, true);
428+
writeString(36, 'data');
429+
view.setUint32(40, dataSize, true);
430+
431+
// Write audio data
432+
for (let i = 0; i < allSamples.length; i++) {
433+
const sample = Math.max(-1, Math.min(1, allSamples[i]));
434+
view.setInt16(44 + i * 2, sample * 0x7FFF, true);
435+
}
436+
437+
// Create blob and download
438+
const blob = new Blob([buffer], { type: 'audio/wav' });
439+
const url = URL.createObjectURL(blob);
440+
const a = document.createElement('a');
441+
a.href = url;
442+
a.download = `tts_audio_${jobId}.wav`;
443+
document.body.appendChild(a);
444+
a.click();
445+
document.body.removeChild(a);
446+
URL.revokeObjectURL(url);
447+
448+
updateProgress(100, "Download complete!");
449+
} catch (error) {
450+
console.error('Blob download error:', error);
451+
alert('Download failed: ' + error.message);
452+
}
453+
}
454+
374455
window.deleteQueueJob = async function(jobId) {
375456
if (confirm(`Delete job #${jobId}?`)) {
376457
await queueManager.deleteJob(jobId);

0 commit comments

Comments
 (0)