So i have a bunch of loaded audio samples that I am calling the schedule function with in the code below:
let audio;
function playChannel() {
let audioStart = context.currentTime;
let next = 0;
for(let i = 0; i < 8; i++) {
scheduler(audioStart, next);
next++;
}
}
Here is the audio scheduler function:
function scheduler(audioStart, index) {
audio = context.createBufferSource();
audio.buffer = audioSamples[index]; //array with all the loaded audio
audio.connect(context.destination);
audio.start(audioStart + (audio.buffer.duration * index));
}
And it's working fine and plays the scheduled sounds as it should.
How am I supposed to stop/cancel all the scheduled sounds from playing?
Because right now when I try to call the stop()
method it will only stop the last scheduled sound from playing.
So i have a bunch of loaded audio samples that I am calling the schedule function with in the code below:
let audio;
function playChannel() {
let audioStart = context.currentTime;
let next = 0;
for(let i = 0; i < 8; i++) {
scheduler(audioStart, next);
next++;
}
}
Here is the audio scheduler function:
function scheduler(audioStart, index) {
audio = context.createBufferSource();
audio.buffer = audioSamples[index]; //array with all the loaded audio
audio.connect(context.destination);
audio.start(audioStart + (audio.buffer.duration * index));
}
And it's working fine and plays the scheduled sounds as it should.
How am I supposed to stop/cancel all the scheduled sounds from playing?
Because right now when I try to call the stop()
method it will only stop the last scheduled sound from playing.
2 Answers
Reset to default 6You'll need to keep track of the BufferSource nodes you're creating inside scheduler, referenced by index, and then run through all of them. E.g.:
var sources = [];
function scheduler(audioStart, index) {
audio = context.createBufferSource();
sources[index] = audio;
audio.buffer = audioSamples[index]; //array with all the loaded audio
audio.connect(context.destination);
audio.start(audioStart + (audio.buffer.duration * index));
}
function stopAll() {
for(let i = 0; i < 8; i++)
if (sources[i])
sources[i].stop(0);
}
While the accepted answer is correct, the sources
array will keep growing indefinitely.
To fix that, you should listen to the source node's onended
and remove them from sources
once they are done playing (or stopped).
Here's a class that encapsulates the logic to load a sound from a URL and play/stop it as many times as you want, keeping track of all currently playing sources and cleaning them up as needed:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
export class Sound {
url = '';
buffer = null;
sources = [];
constructor(url) {
this.url = url;
}
load() {
if (!this.url) return Promise.reject(new Error('Missing or invalid URL: ', this.url));
if (this.buffer) return Promise.resolve(this.buffer);
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', this.url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously:
request.onload = () => {
context.decodeAudioData(request.response, (buffer) => {
if (!buffer) {
console.log(`Sound decoding error: ${ this.url }`);
reject(new Error(`Sound decoding error: ${ this.url }`));
return;
}
this.buffer = buffer;
resolve(buffer);
});
};
request.onerror = (err) => {
console.log('Sound XMLHttpRequest error:', err);
reject(err);
};
request.send();
});
}
play(volume = 1, time = 0) {
if (!this.buffer) return;
// Create a new sound source and assign it the loaded sound's buffer:
const source = context.createBufferSource();
source.buffer = this.buffer;
// Keep track of all sources created, and stop tracking them once they finish playing:
const insertedAt = this.sources.push(source) - 1;
source.onended = () => {
source.stop(0);
this.sources.splice(insertedAt, 1);
};
// Create a gain node with the desired volume:
const gainNode = context.createGain();
gainNode.gain.value = volume;
// Connect nodes:
source.connect(gainNode).connect(context.destination);
// Start playing at the desired time:
source.start(time);
}
stop() {
// Stop any sources still playing:
this.sources.forEach((source) => {
source.stop(0);
});
this.sources = [];
}
}
You can then do something like this:
const soundOne = new Sound('./sounds/sound-one.mp3')
const soundTwo = new Sound('./sounds/sound-two.mp3')
Promises.all([
soundOne.load(),
soundTwo.load(),
]).then(() => {
buttonOne.onclick = () => soundOne.play();
buttonTwo.onclick = () => soundOne.play();
})