I was really excited to see iOS 6 supports the Web Audio API, since we make HTML5 games. However, I cannot get iOS 6 to play any sound at all using the Web Audio API with e
You can try to debug it using the Web Inspector on Safari 6 on a mac.
It doesn't work out of the box for me, but with a few tries it can help narrow down the problem.
Apparently there is also the thing that audio can only be triggered by a user action. I'm not sure this is true 'cos some code that works on iOS6 on iPhone4 doesn't play any sound on an iPad (also iOS6).
Update: Some success with web audio on iPhone4+iOS6. Found that the "currentTime" remains stuck at 0 for a while as soon as you create a new audio context on iOS6. In order to get it moving, you first need to perform a dummy API call (like createGainNode()
and discard the result). Sounds play only when currentTime starts to run, but scheduling sounds exactly at currentTime doesn't seem to work. They need to be a little bit into the future (ex: 10ms). You can use the following createAudioContext
function to wait until the context is ready to make noise. User action doesn't seem to be required on iPhone, but no such success on iPad just yet.
function createAudioContext(callback, errback) {
var ac = new webkitAudioContext();
ac.createGainNode(); // .. and discard it. This gets
// the clock running at some point.
var count = 0;
function wait() {
if (ac.currentTime === 0) {
// Not ready yet.
++count;
if (count > 600) {
errback('timeout');
} else {
setTimeout(wait, 100);
}
} else {
// Ready. Pass on the valid audio context.
callback(ac);
}
}
wait();
}
Subsequently, when playing a note, don't call .noteOn(ac.currentTime)
, but do .noteOn(ac.currentTime + 0.01)
instead.
Please don't ask me why you have to do all that. That's just the way it is at the moment - i.e. crazy.
updated for 2015 solution: hey all, if you are here working on a web audio problem with ios6+ I've found these links as help.
-this is a good article with code solution: http://matt-harrison.com/perfect-web-audio-on-ios-devices-with-the-web-audio-api/
-here is an update to the api after the above ^ solution article was written https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
-below is my updated solution to the first article, using the changes from the second article. The issue I was having was iOS 7 safari throwing a strange not-enough-args error. this fixed it:
define(function() {
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new window.AudioContext();
} catch (e) {
console.log("No Web Audio API support");
}
/*
* WebAudioAPISoundManager Constructor
*/
var WebAudioAPISoundManager = function (context) {
this.context = context;
this.bufferList = {};
this.playingSounds = {};
};
/*
* WebAudioAPISoundManager Prototype
*/
WebAudioAPISoundManager.prototype = {
addSound: function (url) {
// Load buffer asynchronously
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
var self = this;
request.onload = function () {
// Asynchronously decode the audio file data in request.response
self.context.decodeAudioData(
request.response,
function (buffer) {
if (!buffer) {
alert('error decoding file data: ' + url);
return;
}
self.bufferList[url] = buffer;
});
};
request.onerror = function () {
alert('BufferLoader: XHR error');
};
request.send();
},
stopSoundWithUrl: function(url) {
if(this.playingSounds.hasOwnProperty(url)){
for(var i in this.playingSounds[url]){
if(this.playingSounds[url].hasOwnProperty(i)) {
this.playingSounds[url][i].stop(0);
}
}
}
}
};
/*
* WebAudioAPISound Constructor
*/
var WebAudioAPISound = function (url, options) {
this.settings = {
loop: false
};
for(var i in options){
if(options.hasOwnProperty(i)) {
this.settings[i] = options[i];
}
}
this.url = '/src/www/assets/audio/' + url + '.mp3';
this.volume = 1;
window.webAudioAPISoundManager = window.webAudioAPISoundManager || new WebAudioAPISoundManager(window.audioContext);
this.manager = window.webAudioAPISoundManager;
this.manager.addSound(this.url);
// this.buffer = this.manager.bufferList[this.url];
};
/*
* WebAudioAPISound Prototype
*/
WebAudioAPISound.prototype = {
play: function () {
var buffer = this.manager.bufferList[this.url];
//Only play if it's loaded yet
if (typeof buffer !== "undefined") {
var source = this.makeSource(buffer);
source.loop = this.settings.loop;
source.start(0);
if(!this.manager.playingSounds.hasOwnProperty(this.url)) {
this.manager.playingSounds[this.url] = [];
}
this.manager.playingSounds[this.url].push(source);
}
},
stop: function () {
this.manager.stopSoundWithUrl(this.url);
},
getVolume: function () {
return this.translateVolume(this.volume, true);
},
//Expect to receive in range 0-100
setVolume: function (volume) {
this.volume = this.translateVolume(volume);
},
translateVolume: function(volume, inverse){
return inverse ? volume * 100 : volume / 100;
},
makeSource: function (buffer) {
var source = this.manager.context.createBufferSource();
var gainNode = this.manager.context.createGain();
source.connect(gainNode);
gainNode.gain.value = this.volume;
source.buffer = buffer;
// source.connect(gainNode);
gainNode.connect(this.manager.context.destination);
return source;
}
};
return WebAudioAPISound;
});
Update: iOS still requires a user input to play sound (No sound on iOS 6 Web Audio API)
I was previously stuck with web audio on iOS web. And to make things worse, it needs to work on android and other desktop platform. This post is one of those posts that I read and found no immediate answers.
Until I found howler.js.
This is the solution for cross-platform web audio solution:
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.0.3/howler.min.js"></script>
<script>
var sound = new Howl({
src: ['yay3.mp3']
});
sound.play();
</script>
Edit (November 2015): iOS 9 no longer allows audio to start in a touchstart
event, which breaks the solution below. However it works in a touchend
event. The original answer for iOS 6 is left intact below, but for iOS 9 support make sure you use touchend
.
Well, sorry to answer my own bounty question, but after hours of debugging I finally found the answer. Safari on iOS 6 effectively starts with the Web Audio API muted. It will not unmute until you attempt to play a sound in a user input event (create a buffer source, connect it to destination, and call noteOn()
). After this, it unmutes and audio plays unrestricted and as it ought to. This is an undocumented aspect of how the Web Audio API works on iOS 6 (Apple's doc is here, hopefully they update it with a mention of this soon!)
The user can be touching the screen a lot, engaged in the game. But it will remain muted. You have to play inside a user input event like touchstart
[edit: touchend
for iOS 9+], once, then all audio unmutes. After that you can play audio at any time (doesn't have to be in a user input event).
Note this is different to the restrictions on HTML5 audio: typically you can only start audio at all in a user input event, and only play one sound at a time; the Web Audio API fully unmutes after the first play-in-user-input, so that you can play sounds at any time, and then you can mix them polyphonically, process cool effects, etc.
This means many games already on the web using the Web Audio API will never play audio, because they do not happen to issue a noteOn in a touch event. You have to adjust it to wait for the first user input event.
There are a few ways to work around this: don't play your title music until the user touches the screen; have an initial 'touch to enable audio' screen and play a sound then begin the game when they touch; etc. Hopefully this will help anyone else having the same problem save some time trying to debug it!
Answering to the original question, I can confirm some troubles with file formats on iPhone 4S/iOS 6 and MacOSX. If an MP3 file is "not good" for Safari, the decoding goes bad and calling AudioContext.createBuffer(array, bool) gives you and error.
The strange thing is about the error: "SYNTAX_ERR, DOM Exception 12", as pointed out by others. This makes me think it is a bug....
Same behavior also on MacOS, with Safari 6.0 (7536.25).
Okay I like AshleysBrain answer, it helped me to solve the problem. But I found a bit more general solution.
Before you had to initiate the play sound from a user event, now they force you to do it via a user input event, (sounds strange) What I did was just read a input field before I played the sound.
So
$('#start-lesson').click(function() {
return startThisLesson();
});
startThisLesson = function() {
var value;
value = $('#key-pad-value')[0].value;
playSoundFile(yourBuffer);
}
playSoundFile is whatever you use to create the buffer source.