Issue with Merging Two MP3 Audio Files in Bubble.io using Toolbox Plugin and JavaScript

Hello everyone,

I am having difficulties mixing two MP3 audio files in my Bubble.io application using the Toolbox plugin and JavaScript. Here is what I have done so far:

  1. Configuration of “File Uploader” Elements:
  • I have added two “File Uploader” elements with IDs audio1 and audio2 to upload the audio files (voice and music).
  • The files are correctly loaded into these elements.
  1. Added a Mixing Button:
  • I added a button that starts the mixing process when clicked.
  1. Added an Audio Player and a Download Link:
  • I added HTML elements to display the audio player and the download link:
  1. Using “JavascriptToBubble”:
  • I added the “JavascriptToBubble” element with the suffix mixedAudioUrl.
  1. JavaScript Script to Mix Audio Files:
  • I configured a “Run JavaScript” action in the Toolbox plugin with the following script:
async function mixAudioFiles() {
    try {
        const voiceFileInput = document.getElementById('audio1').querySelector('input[type="file"]');
        const musicFileInput = document.getElementById('audio2').querySelector('input[type="file"]');

        if (!voiceFileInput || !musicFileInput) {
            alert('Audio file upload elements not found.');
            return;
        }

        const voiceFile = voiceFileInput.files[0];
        const musicFile = musicFileInput.files[0];

        if (!voiceFile || !musicFile) {
            alert('Please upload both audio files.');
            return;
        }

        const voiceArrayBuffer = await voiceFile.arrayBuffer();
        const musicArrayBuffer = await musicFile.arrayBuffer();

        const audioContext = new (window.AudioContext || window.webkitAudioContext)();

        const voiceBuffer = await audioContext.decodeAudioData(voiceArrayBuffer);
        const musicBuffer = await audioContext.decodeAudioData(musicArrayBuffer);

        const outputBuffer = audioContext.createBuffer(
            2,
            voiceBuffer.length,
            audioContext.sampleRate
        );

        for (let channel = 0; channel < 2; channel++) {
            const outputData = outputBuffer.getChannelData(channel);
            const voiceData = voiceBuffer.getChannelData(channel % voiceBuffer.numberOfChannels);
            const musicData = musicBuffer.getChannelData(channel % musicBuffer.numberOfChannels);

            for (let i = 0; i < voiceBuffer.length; i++) {
                const musicIndex = Math.floor(i * (musicBuffer.length / voiceBuffer.length));
                outputData[i] = voiceData[i] + musicData[musicIndex];
            }
        }

        const mixedAudioBlob = await bufferToWaveBlob(outputBuffer, audioContext.sampleRate);
        const mixedAudioUrl = URL.createObjectURL(mixedAudioBlob);

        // Pass the mixed audio URL to Bubble via JavascriptToBubble
        bubble_fn_mixedAudioUrl(mixedAudioUrl);
    } catch (error) {
        console.error('Error mixing audio files:', error);
        alert('An error occurred while mixing the audio files. Please try again.');
    }
}

function bufferToWaveBlob(buffer, sampleRate) {
    return new Promise(resolve => {
        const worker = new Worker(URL.createObjectURL(new Blob([`
            self.onmessage = function(e) {
                const buffer = e.data.buffer;
                const sampleRate = e.data.sampleRate;
                const length = buffer.length;
                const numOfChan = buffer.numberOfChannels;
                const bufferLength = length * numOfChan * 2 + 44;
                const result = new ArrayBuffer(bufferLength);
                const view = new DataView(result);
                let offset = 0;

                const writeString = function(view, offset, string) {
                    for (let i = 0; i < string.length; i++) {
                        view.setUint8(offset + i, string.charCodeAt(i));
                    }
                };

                writeString(view, offset, 'RIFF'); offset += 4;
                view.setUint32(offset, 36 + length * 2 * numOfChan, true); offset += 4;
                writeString(view, offset, 'WAVE'); offset += 4;
                writeString(view, offset, 'fmt '); offset += 4;
                view.setUint32(offset, 16, true); offset += 4;
                view.setUint16(offset, 1, true); offset += 2;
                view.setUint16(offset, numOfChan, true); offset += 2;
                view.setUint32(offset, sampleRate, true); offset += 4;
                view.setUint32(offset, sampleRate * 2 * numOfChan, true); offset += 4;
                view.setUint16(offset, numOfChan * 2, true); offset += 2;
                view.setUint16(offset, 16, true); offset += 2;
                writeString(view, offset, 'data'); offset += 4;
                view.setUint32(offset, length * 2 * numOfChan, true); offset += 4;

                for (let i = 0; i < length; i++) {
                    for (let channel = 0; channel < numOfChan; channel++) {
                        const sample = buffer.getChannelData(channel)[i];
                        view.setInt16(offset, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
                        offset += 2;
                    }
                }

                self.postMessage(result, [result]);
            }
        `])));

        worker.onmessage = function(e) {
            resolve(new Blob([e.data], { type: 'audio/wav' }));
        };

        worker.postMessage({ buffer, sampleRate });
    });
}

// Call the mixAudioFiles function
mixAudioFiles();
  1. Workflow to Update the Audio Player URL and Download Link:
  • I configured a workflow for the event “When a JavascriptToBubble mixedAudioUrl is fired” with the following action:
const mixedAudioUrl = bubble_fn_mixedAudioUrl;

document.getElementById('mixedAudio').src = mixedAudioUrl;
document.getElementById('downloadLink').href = mixedAudioUrl;
document.getElementById('downloadLink').style.display = 'inline';

Issue Encountered

When I try to mix the audio files, Bubble keeps showing “Please upload both audio files” even though the files are correctly selected in the “File Uploader” elements.

I am not sure why the files are not being correctly retrieved. Do you have any suggestions or advice to resolve this issue?

Thank you in advance for your help !