websocketsafarivuejs3audio-streamingweb-audio-api

Decoding failed for Safari browser while using audioContext.decodeAudioData


I have a Vue JS application in which I am playing an audio stream coming from a Websocket connection. The audio stream is coming continuously in chunks so I have implemented a logic to play the incoming chunks in a queue. Everything works great in Chrome browser but unfortunately it breaks in Safari. I did a lot of research on this but I am not able to find any solution to this problem.

Here is my Code

vm.socket = new WebSocket('wss://my_website.com');

//required to receive data in array buffer type. If not mentioned I get Blob's
vm.socket.binaryType = 'arraybuffer'


vm.socket.addEventListener("message", (event) => {


    const audioData = new Uint8Array(event.data);

    this.audioQueue.push(audioData);
    if (!this.isPlaying) {
        this.playNextChunk();
    }

});

//Main method which is responsible to process incoming audio chunks
async playNextChunk() {

    if (this.audioQueue.length === 0) {
        this.isPlaying = false;
        return;
    }

    this.isPlaying = true;
    const audioData = this.audioQueue.shift();

    try {
        //decode audio data (This fails randomly in between in Safari browser)
        const audioBuffer = this.audioContext.decodeAudioData(audioData.buffer);
        const source = this.audioContext.createBufferSource();
        source.buffer = audioBuffer;
        source.connect(this.audioContext.destination);
        source.onended = this.playNextChunk;
        source.start();
    } catch (error) {
        console.error('Error decoding audio data', error);
    }

}


Here is a screenshot of console logs enter image description here


Solution

  • I was able to decode the audio data on Safari without any issue after using audio-decode library https://www.npmjs.com/package/audio-decode

    const playNextChunk = async () => {
        // Get the current value of the audioQueue from a ref
        const audioQueue = audioQueueRef.current;
    
        // Log the current value of audioQueue
        console.log("audioQueue in pnc", audioQueue);
    
        // Check if there are items in the audioQueue
        if (audioQueue.length > 0) {
          console.log("Audio queue length is: " + audioQueue.length)
          // If there are items, remove the first item from the queue
          const audioBuffer = audioQueue.shift();
    
          // Check if the audioBuffer exists
          if (audioBuffer) {
            // If it exists, play the audio buffer asynchronously
            await playAudio(audioBuffer);
          }
        } else {
          // If there are no chunks remaining in the audio queue:
          console.log("NO CHUNKS REMAINING IN THE AUDIO QUEUE")
    
          // Clear the audioQueue by assigning an empty array to it
          audioQueueRef.current = [];
        }
      };
    
      const playAudio = async (
        audio: Uint8Array,
        isForWelcomeMsg: boolean = false
      ) => {
        if (audio && audioContextRef?.current) {
          try {
            console.log("decoders.mp3()");
            await decoders.mp3(); // load & compile decoder []
            console.log("decoders.mp3(audioData)");
    
            const audioData = await decoders.mp3(audio); // decode
            console.log("DECODING COMPLETE");
    
            console.log("playing started");
    
            if (audioSourceRef.current) {
              audioSourceRef.current.disconnect(audioContextRef.current.destination);
              audioSourceRef.current.stop()
              audioSourceRef.current = null
            }
    
            audioSourceRef.current = audioContextRef.current.createBufferSource();
            audioSourceRef.current.buffer = audioData;
            audioSourceRef.current.connect(audioContextRef.current.destination);
            audioSourceRef.current.start(0);
    
            audioSourceRef.current.onended = async () => {
              console.log("Completed playing a CHUNK"); 
              await playNextChunk();
            };
    
          } catch (error) {
            console.log("error while audio decode");
          }
        } else {
          //do cleanup
        }
      };