Search code examples
javascriptaudiostreamingaudio-streamingweb-audio-api

Why AudioBufferSourceNodes stacks on play?


Basicly, I'm trying to build and play audio data from bytes, that comes from WS sockets.

Detailed:


I have simple WS server written in Django-Channels, that on connect returns me splitted audio file in blob object with 6144 bytes of each chunk. Next, I want to decode this blob data and turn it into sound:

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source;
var play = document.querySelector('#play');
var audioQueue = []

const chatSocket = new WebSocket(
    'ws://'
    + window.location.host
    + 'audio-stream-test'
    + '/'
);

chatSocket.onmessage = function(e) {
    e.data.arrayBuffer().then(buffer => {
        audioCtx.decodeAudioData(buffer, (x)=>{
            source = audioCtx.createBufferSource();
            source.buffer = x;
            source.connect(audioCtx.destination);
            source.loop = false;
            audioQueue.push(source)
        })
    })
}

After WS sent all the data, it closes on server side. The last thing is to play queued buffers from audioQueue array:

play.onclick = function() {
    var playOffset;
    for (let [bufferCount, buffer] of audioQueue.entries()) {
        if (bufferCount == 0) {
            playOffset = 0
        } else {
            playOffset = audioQueue[bufferCount-1].buffer.duration
        }
        buffer.start(when=playOffset)
    }
}

Want to clarify about this line: playOffset = audioQueue[bufferCount-1].buffer.duration. I think, i'm writed it right because I want to play new buffer at the end of old (already played) one.

For me, as server-side developer, it seems like it should work fine.


But, the main problem is: all buffers from audioQueue array is played at once. IDK what I'm doing wrong. Hoping for youre help :)


The song


Solution

  • You need to start each AudioBufferSourceNode in relation to the currentTime of the AudioContext.

    play.onclick = function() {
        audioQueue.reduce((startTime, audioBufferSourceNode) => {
            audioBufferSourceNode.start(startTime);
    
            return startTime + audioBufferSourceNode.buffer.duration;
        }, audioContext.currentTime);
    };
    

    The code above will loop through all nodes in the audioQueue. It computes the startTime for each AudioBufferSourceNode by accumulating the durations of the previous nodes based on the currentTime of the AudioContext.