{"id":1437,"date":"2024-04-03T18:56:40","date_gmt":"2024-04-03T13:26:40","guid":{"rendered":"http:\/\/codetheory.in\/?p=1437---bb7be891-ea9a-4765-8a9b-cc093cb71242"},"modified":"2024-04-03T18:56:40","modified_gmt":"2024-04-03T13:26:40","slug":"solve-your-game-audio-problems-on-ios-and-android-with-web-audio-api","status":"publish","type":"post","link":"https:\/\/codetheory.in\/solve-your-game-audio-problems-on-ios-and-android-with-web-audio-api\/","title":{"rendered":"Solve Your Game Audio Problems on iOS and Android with Web Audio API"},"content":{"rendered":"

So you made your game or some other app that has multiple sound effects produced by several audio files. It works great on your desktop, laptop, etc. browsers but then you realize that you have some major issues<\/a> with multiple sounds (or even single) on mobile and tablet platforms like iOS and android browsers. Sad!<\/p>\n

<\/p>\n

You try various methods<\/a> to fix it but no luck! Don’t worry, there’s a really good way to solve it with – as the title says – Web Audio API.<\/p>\n

Web Audio API<\/h2>\n

The HTML5 audio<\/code> element has some serious limitations making it pretty much unusable in various environments, especially mobile browsers. We discussed them in one of the previous posts<\/a>. This is one of the reasons why the Web Audio API has been introduced, to work around those restrictions and do low level processing and synthesizing of single or multiple audio streams.<\/p>\n

HTML5 Rocks<\/a> has a very solid introduction on this very topic along with some helpful FAQs<\/a>. Do read them to get acquainted with this API.<\/p>\n

Implementation<\/h2>\n

If you’ve read that html5rocks tutorial, then you probably already know what to do next, although it would be nice to share what I tried in my recent game that worked like a charm on iOS 6<\/strong> browsers.<\/p>\n

When the game loads, we create an audio context and load all the audio data.<\/p>\n

\r\nif (typeof webkitAudioContext !== 'undefined') {\r\n  var audio_ctx = new webkitAudioContext();\r\n}\r\n  \r\nfunction loadMusic(url, cb) {\r\n  var req = new XMLHttpRequest();\r\n  req.open('GET', url, true);\r\n  \/\/ XHR2\r\n  req.responseType = 'arraybuffer';\r\n\r\n  req.onload = function() {\r\n    audio_ctx.decodeAudioData(req.response, cb);\r\n  };\r\n\r\n  req.send();\r\n}\r\n<\/pre>\n

loadMusic<\/code> is just a utility function that loads the audio via XHR. Let’s look at how we invoke it.<\/p>\n

\r\nGAME.audio = {};\r\n\r\nvar audio = {\r\n  enemy: 'sfx\/enemy.mp3',\r\n  normal_jump: 'sfx\/normal_jump.mp3',\r\n  super_jump: 'sfx\/super_jump.mp3',\r\n  space_belt: 'sfx\/space_belt.mp3',\r\n  game_over: 'sfx\/game_over.mp3'\r\n}\r\n\r\nvar loadAudioData = function(name, url) {\r\n\r\n  \/\/ Async\r\n  loadMusic(url, function(buffer) {\r\n    GAME.audio[name] = buffer;\r\n  });\r\n\r\n};\r\n\r\nfor (var name in audio) {\r\n  var url = audio[name];\r\n  loadAudioData(name, url);\r\n}\r\n<\/pre>\n

We just loaded all our audio data and stored them in an object available on a global variable called GAME<\/code> that acts as a namespace. So how do we play the sound exactly ? Let’s write a few helper functions that can help us achieve this.<\/p>\n

\r\nfunction playSound (buffer, opt, cb) {\r\n  if (!opt) cb = opt;\r\n  opt = opt || {};\r\n\r\n  var src = audio_ctx.createBufferSource();\r\n  src.buffer = buffer;\r\n\r\n  gain_node = audio_ctx.createGainNode();\r\n  src.connect(gain_node);\r\n  \r\n  gain_node.connect(audio_ctx.destination);\r\n  \/\/console.log(gain_node);\r\n\r\n  if (typeof opt.sound !== 'undefined')\r\n    gain_node.gain.value = opt.sound;\r\n  else\r\n    gain_node.gain.value = 1;\r\n\r\n  \/\/ Options\r\n  if (opt.loop)\r\n    src.loop = true;\r\n\r\n  src.noteOn(0);\r\n\r\n  cb(src);\r\n}\r\n\r\nfunction stopSound (src) {\r\n  src.noteOff(0);\r\n}\r\n<\/pre>\n

Do you notice the playSound<\/code> method has a callback parameter cb<\/code> which is called with the buffer source object ? I’ll show you why I did that in a bit (basically so that we can stop\/pause the source later). So how do we go about calling playSound<\/code> to play our sound at different actions in the game ?<\/p>\n

\r\nfunction playGameSound(name, opt) {\r\n  opt = opt || {};\r\n\r\n  var cb = function(src) {\r\n    GAME.audio_src[name] = src;\r\n  };\r\n\r\n  playSound( GAME.audio[name], opt, cb );\r\n}\r\n\r\n\/\/ This his how we call it\r\nplayGameSound('enemy', {loop: true, sound: 0.5})\r\n<\/pre>\n

Take a close look at this piece of code GAME.audio_src[name] = src;<\/code> – we’re doing this so that we can stop the sound, whenever we want to, pretty easily. We store the source buffer object in the audio_src<\/code> object by the name<\/code> and later when we want to stop it, we just call stopSound<\/code> with the same object.<\/p>\n

\r\nstopSound(GAME.audio_src[name]);\r\n<\/pre>\n

Hopefully, you get the entire idea of using the web audio API to play multiple audio in Webkit browsers now! This really works well in iOS safari\/chrome. But remember, it is only supported in iOS 6 (with safari 6), but not iOS 5. Supporting flawless audio playback in iOS 5 is still a pain. We simply decided to give up on it.<\/p>\n

Falling Back to HTML5 Audio<\/h2>\n

At the moment, Web Audio API is only supported in webkit browsers. So we need to gracefully fallback to html5 audio elements for others like Firefox, Opera and Internet Explorer 9+. Doing this is pretty straightforward, just check for Web Audio API support – something like typeof webkitAudioContext !== 'undefined'<\/code> should be good enough for now – and when there’s no support start creating audio<\/code> elements and append them to the DOM.<\/p>\n

\r\nfor (var name in audio) {\r\n  var url = audio[name];\r\n\r\n  \/\/ Create an audio node\r\n  var audio_el = document.createElement('audio');\r\n\r\n  \/\/ Source nodes for mp3 and ogg\r\n  var src1_el = document.createElement('source');\r\n  var src2_el = document.createElement('source');\r\n\r\n  audio_el.id = name;\r\n\r\n  src1_el.src = url;\r\n  src1_el.type = 'audio\/mp3';\r\n\r\n  src2_el.src = url.replace('.mp3', '.ogg');\r\n  src2_el.type = 'audio\/ogg';\r\n  \r\n  \/\/ Append OGG first (else firefox cries)\r\n  audio_el.appendChild(src2_el);\r\n  \/\/ Append MP3 second\/next\r\n  audio_el.appendChild(src1_el);\r\n\r\n  $$('body').appendChild(audio_el);\r\n\r\n  GAME.audio[name] = audio_el;\r\n}\r\n<\/pre>\n

When you need to play the sound, check for web audio API support again and when there’s no support, call play()<\/code> to start and pause()<\/code> to stop the audio files (on GAME.audio[name]<\/code>).<\/p>\n

That’s pretty much it. Now you’ll be able to start playing audio flawlessly in iOS 6 mobiles and ipads atleast. Let’s hope that the audio support across all platforms gets rock solid soon!<\/p>\n","protected":false},"excerpt":{"rendered":"

So you made your game or some other app that has multiple sound effects produced by several audio files. It works great on your desktop, laptop, etc. browsers but then you realize that you have some major issues with multiple sounds (or even single) on mobile and tablet platforms like iOS and android browsers. Sad!<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,12],"tags":[105,43,15,104,45],"_links":{"self":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/1437"}],"collection":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/comments?post=1437"}],"version-history":[{"count":10,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/1437\/revisions"}],"predecessor-version":[{"id":184788,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/posts\/1437\/revisions\/184788"}],"wp:attachment":[{"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/media?parent=1437"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/categories?post=1437"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codetheory.in\/wp-json\/wp\/v2\/tags?post=1437"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}