319

I'm using HTML5 to program games; the obstacle I've run into now is how to play sound effects.

The specific requirements are few in number:

  • Play and mix multiple sounds,
  • Play the same sample multiple times, possibly overlapping playbacks,
  • Interrupt playback of a sample at any point,
  • Preferably play WAV files containing (low quality) raw PCM, but I can convert these, of course.

My first approach was to use the HTML5 <audio> element and define all sound effects in my page. Firefox plays the WAV files just peachy, but calling #play multiple times doesn't really play the sample multiple times. From my understanding of the HTML5 spec, the <audio> element also tracks playback state, so that explains why.

My immediate thought was to clone the audio elements, so I created the following tiny JavaScript library to do that for me (depends on jQuery):

var Snd = {
  init: function() {
    $("audio").each(function() {
      var src = this.getAttribute('src');
      if (src.substring(0, 4) !== "snd/") { return; }
      // Cut out the basename (strip directory and extension)
      var name = src.substring(4, src.length - 4);
      // Create the helper function, which clones the audio object and plays it
      var Constructor = function() {};
      Constructor.prototype = this;
      Snd[name] = function() {
        var clone = new Constructor();
        clone.play();
        // Return the cloned element, so the caller can interrupt the sound effect
        return clone;
      };
    });
  }
};

So now I can do Snd.boom(); from the Firebug console and play snd/boom.wav, but I still can't play the same sample multiple times. It seems that the <audio> element is really more of a streaming feature rather than something to play sound effects with.

Is there a clever way to make this happen that I'm missing, preferably using only HTML5 and JavaScript?

I should also mention that, my test environment is Firefox 3.5 on Ubuntu 9.10. The other browsers I've tried - Opera, Midori, Chromium, Epiphany - produced varying results. Some don't play anything, and some throw exceptions.

Stéphan Kochen
  • 18,522
  • 9
  • 55
  • 48

18 Answers18

451

HTML5 Audio objects

You don't need to bother with <audio> elements. HTML 5 lets you access Audio objects directly:

var snd = new Audio("file.wav"); // buffers automatically when created
snd.play();

There's no support for mixing in current version of the spec.

To play same sound multiple times, create multiple instances of the Audio object. You could also set snd.currentTime=0 on the object after it finishes playing.


Since the JS constructor doesn't support fallback <source> elements, you should use

(new Audio()).canPlayType("audio/ogg; codecs=vorbis")

to test whether the browser supports Ogg Vorbis.


If you're writing a game or a music app (more than just a player), you'll want to use more advanced Web Audio API, which is now supported by most browsers.

Basil Bourque
  • 218,480
  • 72
  • 657
  • 915
Kornel
  • 91,239
  • 30
  • 200
  • 278
  • This is exactly what I'm doing now, thanks for the pointer! I still use the – Stéphan Kochen Dec 20 '09 at 15:56
  • 18
    `Audio` objects are autobuffered. They're designed analoguous to `Image` objects, so oldschool image preloading techniques work with audio too. – Kornel Dec 20 '09 at 21:15
  • 5
    This also works on iOS. Note though that you *need* to use some kind of user action (e.g., a click on a button) to initiate the sound. You can't just do a `snd.play()` on window.load(). – Husky May 24 '11 at 22:27
  • 1
    I am still using the ` – Derek 朕會功夫 Oct 21 '11 at 00:10
  • 2
    I would prefer this method if it weren't for the fact that – joelmdev Feb 09 '12 at 20:01
  • IE 9 doesn't support wav: http://www.w3schools.com/html5/html5_audio.asp. Using mp3 with a fallback to ogg (or the other way around) would probably be the best way to go – Cbas Aug 10 '12 at 21:29
  • 2
    On iOS 6 autoplay is supported: you can initiate sounds with a simple snd.play() on window.load(). – Pietro Polsinelli Feb 13 '13 at 16:00
  • 1
    Web audio api is alot more advanced now than the audio tag! – GriffLab May 28 '13 at 17:17
  • note that new Audio creates an audio tag and inserts it into the dom, which can be expensive and run you into problems if you and not reusing them wisely. – OJay Oct 03 '13 at 18:17
  • The "more advanced Web Audio API" link leads to a depecrated source, which advises visiting http://webaudio.github.io/web-audio-api/ – Stonetip Nov 19 '14 at 17:35
  • The link to the audio API is dead. Can you refresh it? (I couldn't find where it moved, or where is another good source) – radl Jun 18 '16 at 20:33
  • The new Web Audio API is _NOT_ supported by Internet Explorer 11 - a browser with still close to 8% market share, so, no, you cannot really use it yet. – Xharlie Sep 21 '16 at 14:24
  • Man thx a lot for playing same sound twice :) I was wondering how to achieve that ! – GLAND_PROPRE Mar 06 '17 at 19:01
  • Right what I wanted :) – A.R. Nirjhor Jun 01 '17 at 05:26
  • How two audios playing simultaneously in iOS Safari can be handled in iOS Control Center? – aareeph Oct 25 '18 at 11:53
  • 1
    @aareeph I can't help with that, please ask it as a new SO question – Kornel Oct 26 '18 at 22:45
37

WebAudio API by W3C

As of July 2012, the WebAudio API is now supported in Chrome, and at least partly supported in Firefox, and is slated to be added to IOS as of version 6.

Although it is robust enough to be used programatically for basic tasks, the Audio element was never meant to provide full audio support for games, etc. It was designed to allow a single piece of media to be embedded in a page, similar to an img tag. There are a lot of issues with trying to use the Audio tag for games:

  • Timing slips are common with Audio elements
  • You need an Audio element for each instance of a sound
  • Load events aren't totally reliable, yet
  • No common volume controls, no fading, no filters/effects

I used this Getting Started With WebAudio article to get started with the WebAudio API. The FieldRunners WebAudio Case Study is also a good read.

Basil Bourque
  • 218,480
  • 72
  • 657
  • 915
Chris Jaynes
  • 2,730
  • 27
  • 29
  • This is a better answer than the accepted one, as it focuses on the correct solution to the problem (WebAudio API), and the reasons Audio elements aren't a good solution, rather than trying to hack together a bad solution with Audio elements – Anomaly May 16 '19 at 12:17
31

howler.js

For game authoring, one of the best solutions is to use a library which solves the many problems we face when writing code for the web, such as howler.js. howler.js abstracts the great (but low-level) Web Audio API into an easy to use framework. It will attempt to fall back to HTML5 Audio Element if Web Audio API is unavailable.

var sound = new Howl({
  urls: ['sound.mp3', 'sound.ogg']
}).play();
// it also provides calls for spatial/3d audio effects (most browsers)
sound.pos3d(0.1,0.3,0.5);

wad.js

Another great library is wad.js, which is especially useful for producing synth audio, such as music and effects. For example:

var saw = new Wad({source : 'sawtooth'})
saw.play({
    volume  : 0.8,
    wait    : 0,     // Time in seconds between calling play() and actually triggering the note.
    loop    : false, // This overrides the value for loop on the constructor, if it was set. 
    pitch   : 'A4',  // A4 is 440 hertz.
    label   : 'A',   // A label that identifies this note.
    env     : {hold : 9001},
    panning : [1, -1, 10],
    filter  : {frequency : 900},
    delay   : {delayTime : .8}
})

Sound for Games

Another library similar to Wad.js is "Sound for Games", it has more focus on effects production, while providing a similar set of functionality through a relatively distinct (and perhaps more concise feeling) API:

function shootSound() {
  soundEffect(
    1046.5,           //frequency
    0,                //attack
    0.3,              //decay
    "sawtooth",       //waveform
    1,                //Volume
    -0.8,             //pan
    0,                //wait before playing
    1200,             //pitch bend amount
    false,            //reverse bend
    0,                //random pitch range
    25,               //dissonance
    [0.2, 0.2, 2000], //echo array: [delay, feedback, filter]
    undefined         //reverb array: [duration, decay, reverse?]
  );
}

Summary

Each of these libraries are worth a look, whether you need to play back a single sound file, or perhaps create your own html-based music editor, effects generator, or video game.

Shaun Wilson
  • 8,320
  • 3
  • 46
  • 46
d13
  • 9,421
  • 11
  • 33
  • 39
  • Can't comment on game-development, but I'm using this for some minor audio feedback in an interface and for that it works an absolute treat. Quick, easy & simple. – Rid Iculous Apr 08 '14 at 06:43
  • Really nice framework, works nice in mobile as well. You have to trigger it with touchstart... but once that's done it works out of the box :) – Philip May 27 '14 at 12:03
  • howler.js is awesome, so far. – Omiod Nov 18 '20 at 16:41
9

You may also want to use this to detect HTML 5 audio in some cases:

http://diveintohtml5.ep.io/everything.html

HTML 5 JS Detect function

function supportsAudio()
{
    var a = document.createElement('audio'); 
    return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
}
DanBeale
  • 316
  • 4
  • 15
Rob
  • 1,581
  • 3
  • 20
  • 34
  • Thanks for noting this. I learned this method from has.js, but I find it has some issues in Firefox, and apparently in Opera too according to the has.js source code. (refs: https://github.com/phiggins42/has.js/issues/48 https://github.com/phiggins42/has.js/blob/master/detect/audio.js#L19) – Stéphan Kochen Jan 01 '11 at 22:42
5

Here's one method for making it possible to play even same sound simultaneously. Combine with preloader, and you're all set. This works with Firefox 17.0.1 at least, haven't tested it with anything else yet.

// collection of sounds that are playing
var playing={};
// collection of sounds
var sounds={step:"knock.ogg",throw:"swing.ogg"};

// function that is used to play sounds
function player(x)
{
    var a,b;
    b=new Date();
    a=x+b.getTime();
    playing[a]=new Audio(sounds[x]);
    // with this we prevent playing-object from becoming a memory-monster:
    playing[a].onended=function(){delete playing[a]};
    playing[a].play();
}

Bind this to a keyboard key, and enjoy:

player("step");
F-3000
  • 805
  • 12
  • 13
  • No. I don't think that creating a new object for each individual sound playback is a solution at all. It's a quick and dirty workaround, but this can be done better. – Pawel May 26 '15 at 20:33
  • @Pawel True, that this should NOT be used as main method of playing sounds, but as far as I'm aware, the principle in the example is the only way of playing same sound more than once simultaneously, or as OP expressed it, "overlapping playbacks". (Without requiring browser-plugins.) My actual implement in my current project is much more sophisticated than example code I posted. – F-3000 May 27 '15 at 19:10
4

To play the same sample multiple times, wouldn't it be possible to do something like this:

e.pause(); // Perhaps optional
e.currentTime = 0;
e.play();

(e is the audio element)

Perhaps I completely misunderstood your problem, do you want the sound effect to play multiple times at the same time? Then this is completely wrong.

adamse
  • 11,732
  • 4
  • 32
  • 46
4

Have a look at the jai (-> mirror) (javascript audio interface) site. From looking at their source, they appear to be calling play() repeatedly, and they mention that their library might be appropriate for use in HTML5-based games.

You can fire multiple audio events simultaneously, which could be used for creating Javascript games, or having a voice speaking over some background music

toolsche
  • 25
  • 5
Raul Agrait
  • 5,692
  • 5
  • 46
  • 71
4

Sounds like what you want is multi-channel sounds. Let's suppose you have 4 channels (like on really old 16-bit games), I haven't got round to playing with the HTML5 audio feature yet, but don't you just need 4 <audio> elements, and cycle which is used to play the next sound effect? Have you tried that? What happens? If it works: To play more sounds simultaneously, just add more <audio> elements.

I have done this before without the HTML5 <audio> element, using a little Flash object from http://flash-mp3-player.net/ - I wrote a music quiz (http://webdeavour.appspot.com/) and used it to play clips of music when the user clicked the button for the question. Initially I had one player per question, and it was possible to play them over the top of each other, so I changed it so there was only one player, which I pointed at different music clips.

Lee Kowalkowski
  • 11,141
  • 3
  • 35
  • 44
  • That's an interesting thought, to use <audio> elements as channels. When I put two elements on a page, I *can* play them simultaneously. It looks like the cloning I'm doing doesn't actually do what I expect, because in my original code, playing two samples also works. Just not twice the same sample. I'll have to experiment with this some more, and will get back on the results! – Stéphan Kochen Dec 19 '09 at 21:34
  • Yeah, there's possibly a subtle reason why it's called clone and not copy. Perhaps the clone is still sharing something with the original that prevents them from both playing simultaneously. – Lee Kowalkowski Dec 19 '09 at 23:33
  • For reference, re-using the same – Stéphan Kochen Dec 20 '09 at 15:58
3

Here's an idea. Load all of your audio for a certain class of sounds into a single individual audio element where the src data is all of your samples in a contiguous audio file (probably want some silence between so you can catch and cut the samples with a timeout with less risk of bleeding to the next sample). Then, seek to the sample and play it when needed.

If you need more than one of these to play you can create an additional audio element with the same src so that it is cached. Now, you effectively have multiple "tracks". You can utilize groups of tracks with your favorite resource allocation scheme like Round Robin etc.

You could also specify other options like queuing sounds into a track to play when that resource becomes available or cutting a currently playing sample.

ЯegDwight
  • 23,615
  • 10
  • 43
  • 51
2

I would recommend using SoundJS, a library I've help develop. It allows you to write a single code base that works everywhere, with SoundJS picking web audio, html audio, or flash audio as appropriate.

It will allow you to do all of the thing you want:

  • Play and mix multiple sounds,
  • Play the same sample multiple times, possibly overlapping playbacks
  • Interrupt playback of a sample at any point
  • play WAV files containing (depending on browser support)

Hope that helps.

OJay
  • 1,239
  • 7
  • 13
  • that library is rather awesome, I really dig the sfx generator. time to make a web Saiko [download for anyone interested](http://1drv.ms/1iyzewr) – RozzA Sep 10 '15 at 07:45
2

http://robert.ocallahan.org/2011/11/latency-of-html5-sounds.html

http://people.mozilla.org/~roc/audio-latency-repeating.html

Works OK in Firefox and Chrome for me.

To stop a sound that you started, do var sound = document.getElementById("shot").cloneNode(true); sound.play(); and later sound.pause();

1

It's not possible to do multi-shot playing with a single <audio> element. You need to use multiple elements for this.

Eli Grey
  • 32,712
  • 13
  • 69
  • 92
1

I ran into this while programming a musicbox card generator. Started with different libraries but everytime there was a glitch somehow. The lag on normal audio implementation was bad, no multiple plays... eventually ended up using lowlag library + soundmanager:

http://lowlag.alienbill.com/ and http://www.schillmania.com/projects/soundmanager2/

You can check out the implementation here: http://musicbox.grit.it/

I generated wav + ogg files for multi browser plays. This musicbox player works responsive on ipad, iphone, Nexus, mac, pc,... works for me.

Grit
  • 61
  • 5
0

I know this is a total hack but thought I should add this sample open source audio library I put on github awhile ago...

https://github.com/run-time/jThump

After clicking the link below, type on the home row keys to play a blues riff (also type multiple keys at the same time etc.)

Sample using jThump library >> http://davealger.com/apps/jthump/

It basically works by making invisible <iframe> elements that load a page that plays a sound onReady().

This is certainly not ideal but you could +1 this solution based on creativity alone (and the fact that it is open source and works in any browser that I've tried it on) I hope this gives someone else searching some ideas at least.

:)

DaveAlger
  • 2,006
  • 22
  • 23
  • Why did someone down-vote this? Seems like a viable option to me. – jtrick Jan 13 '14 at 17:45
  • it is a pretty hacky solution but i have used this library to make a sample html 5 canvas game so it can work none the less... game using this library is here -- http://beepbox.net – DaveAlger Jan 27 '14 at 22:31
0

The selected answer will work in everything except IE. I wrote a tutorial on how to make it work cross browser = http://www.andy-howard.com/how-to-play-sounds-cross-browser-including-ie/index.html

Here is the function I wrote;

function playSomeSounds(soundPath)
 {

 var trident = !!navigator.userAgent.match(/Trident\/7.0/);
 var net = !!navigator.userAgent.match(/.NET4.0E/);
 var IE11 = trident && net
 var IEold = ( navigator.userAgent.match(/MSIE/i) ? true : false );
 if(IE11 || IEold){
 document.all.sound.src = soundPath;
 }
 else
 {
 var snd = new Audio(soundPath); // buffers automatically when created
 snd.play();
 }
 };

You also need to add the following tag to the html page:

<bgsound id="sound">

Finally you can call the function and simply pass through the path here:

playSomeSounds("sounds/welcome.wav");
Matt
  • 70,063
  • 26
  • 142
  • 172
Andrew Junior Howard
  • 2,125
  • 1
  • 24
  • 35
0

You can always try AudioContext it has limited support but it's a part of the web audio api working draft. It might be worth it if you are planing to release something in the future. And if you are only programing for chrome and Firefox you're golden.

haelmic
  • 427
  • 2
  • 14
0

var AudioContextFunc = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContextFunc();
var player=new WebAudioFontPlayer();
var instrumVox,instrumApplause;
var drumClap,drumLowTom,drumHighTom,drumSnare,drumKick,drumCrash;
loadDrum(21,function(s){drumClap=s;});
loadDrum(30,function(s){drumLowTom=s;});
loadDrum(50,function(s){drumHighTom=s;});
loadDrum(15,function(s){drumSnare=s;});
loadDrum(5,function(s){drumKick=s;});
loadDrum(70,function(s){drumCrash=s;});
loadInstrument(594,function(s){instrumVox=s;});
loadInstrument(1367,function(s){instrumApplause=s;});
function loadDrum(n,callback){
  var info=player.loader.drumInfo(n);
  player.loader.startLoad(audioContext, info.url, info.variable);
  player.loader.waitLoad(function () {callback(window[info.variable])});
}
function loadInstrument(n,callback){
  var info=player.loader.instrumentInfo(n);
  player.loader.startLoad(audioContext, info.url, info.variable);
  player.loader.waitLoad(function () {callback(window[info.variable])});
}
function uhoh(){
  var when=audioContext.currentTime;
  var b=0.1;
  player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*0, 60, b*1);
  player.queueWaveTable(audioContext, audioContext.destination, instrumVox, when+b*3, 56, b*4);
}
function applause(){
  player.queueWaveTable(audioContext, audioContext.destination, instrumApplause, audioContext.currentTime, 54, 3);
}
function badumtss(){
  var when=audioContext.currentTime;
  var b=0.11;
  player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*0, drumSnare.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumLowTom, when+b*0, drumLowTom.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*1, drumSnare.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumHighTom, when+b*1, drumHighTom.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumSnare, when+b*3, drumSnare.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumKick, when+b*3, drumKick.zones[0].keyRangeLow, 3.5);
  player.queueWaveTable(audioContext, audioContext.destination, drumCrash, when+b*3, drumCrash.zones[0].keyRangeLow, 3.5);
}
<script src='https://surikov.github.io/webaudiofont/npm/dist/WebAudioFontPlayer.js'></script>
<button onclick='badumtss();'>badumtss</button>
<button onclick='uhoh();'>uhoh</button>
<button onclick='applause();'>applause</button>
<br/><a href='https://github.com/surikov/webaudiofont'>More sounds</a>
user1024
  • 1,052
  • 1
  • 9
  • 16
0

Web Audio API is right tool for this job. There is little bit of work involved in loading sounds files and playing it. Luckily there are plenty of libraries out there that simplify the job. Being interested in sounds I also created a library called musquito you can check out that as well.

Currently it supports only fading sound effect and I'm working on other things like 3D spatialization.

VJAI
  • 29,899
  • 20
  • 93
  • 155