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);
}
}
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
}
};