@@ -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+
374455window . deleteQueueJob = async function ( jobId ) {
375456 if ( confirm ( `Delete job #${ jobId } ?` ) ) {
376457 await queueManager . deleteJob ( jobId ) ;
0 commit comments