iOS 6 Web Audio API没有声音

我很高兴看到iOS 6支持Web Audio API,因为我们制作了HTML5游戏。 但是,我无法让iOS 6使用Web Audio API播放任何声音,并且可以在桌面版Chrome中正常工作。

这里是一个带有触摸控件的HTML5游戏,并通过Web Audio API播放audio(如果存在的话,如果不存在的话,它将回落到HTML5audio):

http://www.scirra.com/labs/sbios6b/

编辑:@Srikumarbuild议一些解决方法。 我在下面的版本中应用了它们。 它仍然不起作用!

http://www.scirra.com/labs/sbios6f/

在桌面版Chrome上一切都很顺利,但iOS 6完全没有声音。 我在debugging时遇到了麻烦,因为我只进行Windows开发,iOS 6用远程Web检查器取代了debugging模式,这显然在Safari的Windows上不可用。 使用一些警报,我发现它正确地识别Web Audio API,使用它,检测到没有Vorbis支持,所以退回到AACaudio,解码缓冲区,然后播放它,并没有错误抛出,但我什么都没听到。 而且,当然,我试着把音量调高到最大:)

不应该有编解码器问题,因为iOS 6可以播放AAC就好 – 您可以浏览到.m4a的其中一个游戏,并直接从Safari浏览器中直接播放。

在iOS 6上查看Web Audio API示例: http : //chromium.googlecode.com/svn/trunk/samples/audio/samples.html – 其中一些工作,而另一些则不工作。 例如, Chromeaudio可视化工具 ,但Javascript无人机不。

iOS 6上的Web Audio与桌面版Chrome之间必须存在一些细微的不兼容性。 我错过了什么?

编辑(2015年11月): iOS 9不再允许audio在touchstart事件中启动,这打破了下面的解决scheme。 然而它在一个touchend事件中touchend 。 iOS 6的原始答案在下面完整保留,但对于iOS 9支持,请确保使用touchend

那么,很抱歉回答我自己的赏金问题,但经过几个小时的debugging,我终于find了答案。 iOS 6上的Safari有效地从Web Audio API静音开始。 除非您尝试在用户input事件中播放声音,否则不会取消静音(创build缓冲区源,将其连接到目标位置并调用noteOn() )。 在此之后,它将取消静音,并且audio播放不受限制,并且应该如此。 这是Web Audio API如何在iOS 6上工作的一个没有logging的方面( 苹果公司的文档在这里 ,希望他们更新,提及这个很快!)

用户可以很多地触摸屏幕,参与游戏。 但它将保持静音。 您必须touchstart [编辑: touchend for iOS 9+]之类的用户input事件中播放一次,然后再播放所有audiotouchend静音。 之后,您可以随时播放audio(不必在用户input事件中)。

请注意,这与HTML5audio的限制不同:通常您只能在用户input事件中启动audio,并且一次只播放一个声音; Web Audio API在第一次播放用户input后完全取消静音,以便您可以随时播放声音,然后您可以将它们混合在一起,处理很酷的效果等等。

这意味着使用Web Audio API已经在networking上的许多游戏将不会播放audio,因为它们不会在触摸事件中发出音符。 您必须调整它以等待第一个用户input事件。

解决这个问题的方法有几种:在用户触摸屏幕前不要播放标题音乐, 有一个初步的“触摸启用audio”屏幕并播放声音,然后开始游戏,当他们触摸; 等希望这将帮助其他人有相同的问题节省一些时间试图debugging!

您可以尝试在Mac上使用Safari 6上的Web Inspector进行debugging。

  1. 在Mobile Safari设置/高级中启用“Webkit Inspector”。
  2. 使用USB电缆将设备连接到运行Safari 6的Mac。
  3. 加载你的网页/游戏
  4. 进入菜单Develop – > [devicename] – > [pageurl]

这对我来说并不是很有用,但是通过一些尝试,它可以帮助缩小问题的范围。

显然也有audio只能由用户操作触发的事情。 我不确定这是真的吗?因为iPhone4上的iOS6上的某些代码不能在iPad(也是iOS6)上播放任何声音。

更新 :iPhone4 + iOS6上的networkingaudio取得了一些成功。 发现在iOS6上创build一个新的audio上下文后,“currentTime”一段时间内会一直保持为0。 为了让它移动,首先需要执行一个虚拟API调用(如createGainNode()并放弃结果)。 只有当currentTime开始运行时,声音才会播放,但在currentTime处精确调度声音似乎不起作用。 他们需要一点点未来(例如:10毫秒)。 您可以使用以下createAudioContext函数等待上下文准备好发出噪音。 用户行为似乎并不需要在iPhone上,但在iPad上还没有这样的成功。

 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(); } 

随后,在播放音符时,不要调用.noteOn(ac.currentTime) ,而是使用.noteOn(ac.currentTime + 0.01)来代替。

请不要问我为什么你必须这样做。 这就是现在的方式 – 即疯狂。

我设法找出了一个简单的解决scheme,我肯定必须在其他地方logging – 但有时我们不得不花费数小时来为自己找出这些东西。

所以看起来很多教程(比如html5rocks上的这个教程)都会指导您执行以下步骤:

  • 创build一个window.AudioContext的实例,如果它不存在(它不在iOS上),然后创buildwindow.webkitAudioContext

  • 创build一个XMLHttpRequest来加载你的声音文件

  • load事件上运行context.decodeAudioData(....)然后createBufferSource() ,用解码数据填充它,最后用source.start(0)播放声音。

正如其他人已经指出,作为用户交互(点击或触摸开始)的结果,您必须创buildAudioContext (顺便说一下,您必须存储和使用的页面的生命周期)。

不过:为了让iOS“解锁”它的audiofunction,你必须在创buildAudioContext时有audio数据可用。 如果您asynchronous加载数据,则无法播放。 仅仅在click事件中创buildAudioContext是不够的。

这里有两个可靠的iOS回放解决scheme:

  • 1)在初始化AudioContext之前,您必须至less加载一个声音文件,然后在单个用户交互(例如单击)中立即运行该声音文件的所有上述步骤。

  • 或者2)在内存中dynamic创build声音并播放。

这就是我做第二个select的方式:

记住 – 必须在iOS的click / touch事件中:

  window.AudioContext = window.AudioContext || window.webkitAudioContext; var context = new window.AudioContext(); // you should null check window.AudioContext for old browsers to not blow up // create a dummy sound - and play it immediately in same 'thread' var oscillator = context.createOscillator(); oscillator.frequency.value = 400; oscillator.connect(context.destination); oscillator.start(0); oscillator.stop(.5); // you can set this to zero, but I left it here for testing. // audio context is now 'unlocked' and ready to load and play sounds asynchronously // YOU MUST STORE 'context' for future usage. DON'T recreate more AudioContexts 

我想这是一个常见的错误 – 三年后我感到很惊讶,似乎没有人指出或发现它:

所以,我想我已经明白了。

在允许播放声音之前,这是Apple需要用户操作的问题。 事实certificate,至less对我来说,除了用户要求的时候,你不应该创buildaudio上下文。 在页面加载时创build上下文是不够的,然后使用createGainNode或类似的用户操作。

在你的情况下,我会创build上下文时,用户点击“触摸开始”button。

回答原来的问题,我可以确认在iPhone 4S / iOS 6和MacOSX上的文件格式的一些麻烦。 如果一个MP3文件对于Safari来说是“不好的”,那么解码会变得很糟糕,并且调用AudioContext.createBuffer(array,bool)会给出错误信息。

奇怪的是关于错误:“SYNTAX_ERR,DOMexception12”,正如别人指出的。 这让我觉得这是一个错误….

Safari6.0(7536.25)在MacOS上也有同样的行为。

我遇到了iOS5上的HTML5 Audio的audio限制,并通过以下方式解决了这个问题:

1)使用无声audio文件创build一个audio元素,并最初使用触摸事件播放(例如,“开始游戏”button),然后立即暂停。

2)build立一个声音切换器function,切换Audio src,然后在短暂超时后播放Audio元素。

3)在任何事件上调用声音切换器function(不需要是触摸事件)。

这是有效的,因为audio元素在第一次触摸时没有静音,具有静音的audio文件,并且保持未被静音,所以源可以被即时切换。

 switchSound: (id) -> @soundSwitch.pause() @soundSwitch.src = @sounds[id]._src clearTimeout @switchSoundDelay @switchSoundDelay = setTimeout => # @soundSwitch.volume = 1 @soundSwitch.play() ,50 

更新为2015解决scheme:嘿所有,如果你在这里与ios6 +的webaudio问题工作,我发现这些链接作为帮助。

– 这是一个很好的代码解决scheme的文章: http : //matt-harrison.com/perfect-web-audio-on-ios-devices-with-the-web-audio-api/

– 在上面的^解决scheme文章被写入后,有一个更新的API https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext

以下是我第一篇文章的更新解决scheme,使用第二篇文章中的更改。 我遇到的问题是iOS 7 Safari浏览器抛出一个奇怪的不足够的参数错误。 这固定它:

 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; }); 

更新:iOS仍然需要用户input来播放声音( iOS 6 Web Audio API没有声音 )

我以前坚持在iOSnetworking上的networkingaudio。 而更糟的是,它需要在android和其他桌面平台上工作。 这篇文章是我阅读的那些post之一,没有find直接的答案。

直到我find了howler.js 。

这是跨平台networkingaudio解决scheme的解决scheme:

 <script src="ajax/libs/howler/2.0.3/howler.min.js"></script> <script> var sound = new Howl({ src: ['yay3.mp3'] }); sound.play(); </script> 

这不是一个真正的答案,只是一个方向来看,如果事情还没有工作。 iOS6在某些设备上有audio问题(特别是在某个特定时期制造的64GB 4s,尽pipe我已经看到其他设备,所以它可能实际上并不涉及硬件),并会神秘地停止播放某些种类的声音(不是铃声或声音,对于一些原因,但许多其他的声音),它的音量滑块将消失。 我发现debugging非常困难,因为通常情况下(并不总是,有时候你可以捕捉它)只有在没有连接电源线时才会发生。

在控制台中查看来自VirtualAudio_Device和各种编解码器的ASSERTION FAILURE消息。 这可能与您的特定问题没有任何关系,但是再次,声音设备的一个区域中的错误可能与另一个区域有关。 至less,这是一个调查区域,如果没有别的帮助。

API似乎在iOS 6.1上被破坏,或者至less有一个突破的变化,意味着目前没有网站使用它。

我遇到了使用所有简单解决scheme的麻烦。 尤其是当我想多次播放声音的时候。

所以我使用这个js库: http : //pupunzi.open-lab.com/2013/03/13/making-html5-audio-actually-work-on-mobile

好吧,我喜欢AshleysBrain的答案,它帮助我解决了这个问题。 但是我find了一个更通用的解决scheme。

在用户事件开始播放声音之前,现在他们强迫你通过用户input事件来做,(听起来很奇怪)我在播放声音之前只是读了一个input字段。

所以

  $('#start-lesson').click(function() { return startThisLesson(); }); startThisLesson = function() { var value; value = $('#key-pad-value')[0].value; playSoundFile(yourBuffer); } 

playSoundFile是你用来创build缓冲区源的任何东西。