Reputation: 5897
I'm create a very basic HTML5 game and I've hit a little wall, basically I have an array, this array is populated with one sound file (it is an array so I can assign more sounds if needed)
this.Sounds["L_Explosion1"] = new Audio("Assets/Sounds/Explosion_1.wav");
this.Sounds["L_Explosion1"].volume = 1;
this.Sounds["L_Explosion1"].load();
When the player presses the space bar I want to play the sound so I run this code
this.Sounds["L_Explosion1"].play();
however if I press space bar again before the sound played it will not play it, what I am trying to do is play the same sound multiple times event if the same has not yet finished, how would I achieve this?
Upvotes: 11
Views: 9248
Reputation: 21161
The HTML5 audio element has many limitations and it's not well suited for a complex audio use-case like what you are describing, which seems to be a game.
Instead, consider using the WebAudio API. You'll have to:
Use a XMLHttpRequest
to load your audio file in a buffer
Assign that buffer to a AudioBufferSourceNode
instance.
Connect that node to the appropriate destination or intermediate nodes (e.g. GainNode
).
If you also need to stop sounds, you'll need to keep track of each source node that is created and that hasn't finished playing yet, which can be done by listen to the source node's onended
.
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();
})
Upvotes: 3
Reputation: 1458
In order to play multiple instances of the sound at the same time, you will need to create a new copy of that Audio
element. One way to do this is to use the .cloneNode()
method and play the cloned audio every time the spacebar is pressed.
this.Sounds["L_Explosion1"].cloneNode(true).play();
Upvotes: 19