41

看到 iOS 6 支持 Web Audio API,我真的很兴奋,因为我们制作 HTML5 游戏。但是,我无法让 iOS 6 使用 Web Audio API 播放任何声音,其中的示例在桌面 Chrome 中运行良好。

这是一个带有触摸控制和通过 Web Audio API 播放音频的 HTML5 游戏(如果存在 - 如果不存在,它将回退到 HTML5 音频):

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

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

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

在桌面 Chrome 上一切正常,但 iOS 6 完全没有声音。我在调试它时遇到了麻烦,因为我只进行 Windows 开发,而 iOS 6 用远程 Web 检查器替换了调试模式,这显然在 Safari for Windows 上不可用。使用一些警报,我确实发现它正确识别了 Web 音频 API,使用它,检测到没有 Vorbis 支持,因此回退到 AAC 音频,解码缓冲区然后播放它,没有抛出任何错误,但我什么也没听到。而且,当然,我尝试将音量调到最大:)

不应该存在编解码器问题,因为 iOS 6 可以正常播放 AAC - 您可以浏览到游戏播放的 .m4a 文件之一,并且直接从 Safari 访问它可以正常播放。

查看 iOS 6 上的 Web 音频 API 示例:http: //chromium.googlecode.com/svn/trunk/samples/audio/samples.html - 其中一些有效,而另一些则无效。例如,Chrome Audio Visualizer可以工作,但Javascript Drone不能。

iOS 6 上的 Web Audio 和桌面 Chrome 之间肯定存在一些微妙的不兼容。我错过了什么?

4

12 回答 12

51

编辑(2015 年 11 月): iOS 9 不再允许在touchstart事件中启动音频,这破坏了以下解决方案。但是它在touchend事件中起作用。iOS 6 的原始答案在下面保持不变,但对于 iOS 9 支持,请确保使用touchend.

好吧,很抱歉回答我自己的赏金问题,但经过数小时的调试,我终于找到了答案。iOS 6 上的 Safari 有效地从 Web Audio API 静音开始。直到您尝试在用户输入事件中播放声音(创建缓冲区源,将其连接到目标,然后调用noteOn()) ,它才会取消静音。在此之后,它会取消静音并且音频播放不受限制,并且应该如此。这是 Web Audio API 如何在 iOS 6 上工作的一个未记录的方面(Apple 的文档在这里,希望他们尽快更新它并提及这一点!)

用户可以经常触摸屏幕,参与游戏。但它将保持静音。您必须touchstart在[edit: for iOS 9+] 之类的用户输入事件中播放touchend一次,然后将所有音频取消静音。之后,您可以随时播放音频(不必在用户输入事件中)。

请注意,这与 HTML5 音频的限制不同:通常您只能在用户输入事件中启动音频,并且一次只能播放一种声音;Web Audio API 在第一次play-in-user-input 后​​完全取消静音,这样您就可以随时播放声音,然后您可以进行复音混音、处理炫酷效果等。

这意味着已经在网络上使用 Web Audio API 的许多游戏将永远不会播放音频,因为它们不会碰巧在触摸事件中发出 noteOn。您必须对其进行调整以等待第一个用户输入事件。

有几种方法可以解决这个问题:在用户触摸屏幕之前不要播放标题音乐;初始“触摸以启用音频”屏幕并播放声音,然后在他们触摸时开始游戏;等等。希望这将帮助其他有同样问题的人节省一些时间来尝试调试它!

于 2012-09-24T16:39:30.950 回答
7

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

  1. 在 Mobile Safari 设置/高级中启用“Webkit Inspector”。
  2. 使用 USB 电缆将设备连接到运行 Safari 6 的 Mac。
  3. 加载您的页面/游戏
  4. 转到菜单开发-> [设备名称]-> [pageurl]

它对我来说不是开箱即用的,但是通过几次尝试它可以帮助缩小问题的范围。

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

更新:在 iPhone4+iOS6 上的网络音频取得了一些成功。发现在 iOS6 上创建新的音频上下文后,“currentTime”会停留在 0 一段时间。为了让它移动,您首先需要执行一个虚拟 API 调用(喜欢createGainNode()并丢弃结果)。只有在 currentTime 开始运行时才会播放声音,但在 currentTime 准确调度声音似乎不起作用。他们需要一点点未来(例如:10ms)。您可以使用以下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)

请不要问我为什么要这么做。这就是目前的情况——即疯狂。

于 2012-09-22T03:49:22.447 回答
5

我设法找到了一个简单的解决方案,我敢肯定它一定已经在其他地方记录过了——但有时我们不得不花几个小时自己弄清楚这些事情......

因此,似乎有很多教程(例如html5rocks上的这篇教程)指导您执行以下步骤:

  • 创建一个实例,window.AudioContext如果不存在(在 iOS 上不存在),则创建window.webkitAudioContext.

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

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

正如其他人指出的那样,您必须AudioContext作为用户交互(单击或触摸启动)的结果创建(顺便说一下,您必须在页面的生命周期内存储和使用)。

但是: 要让 iOS “解锁”其音频功能,您 必须在创建AudioContext. 如果您异步加载数据,则没有什么可以播放的。AudioContext仅仅创建内部click事件是不够的。

这里有两种可靠的 iOS 播放解决方案:

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

  • 或 2) 在内存中动态创建声音并播放。

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

记住 -对于 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

我想这是一个常见的错误——三年后我很惊讶,似乎没有人指出或发现这一点:-/

于 2015-09-29T09:45:42.530 回答
3

所以,我想我已经想通了。

这是 Apple 在允许播放声音之前需要用户操作的问题。事实证明,至少对我来说,你根本不应该创建音频上下文,除非用户需要它。在页面加载时创建上下文然后在用户操作上使用 createGainNode 或类似内容是不够的。

在您的情况下,当用户单击“触摸开始”按钮时,我会创建上下文。

于 2012-09-24T08:01:32.087 回答
1

回答原始问题,我可以确认 iPhone 4S/iOS 6 和 MacOSX 上的文件格式存在一些问题。如果一个 MP3 文件对 Safari 来说“不好”,那么解码就会出错,调用 AudioContext.createBuffer(array, bool) 会给你带来错误。

奇怪的是错误:“SYNTAX_ERR,DOM Exception 12”,正如其他人所指出的那样。这让我觉得这是一个错误......

在 Safari 6.0 (7536.25) 上也有相同的行为。

于 2012-09-28T16:35:19.383 回答
1

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

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

2)构建一个声音切换器功能,它切换音频源,然后在短暂超时后播放音频元素。

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

这是因为音频元素在第一次触摸时未静音,带有静音音频文件,并且保持未静音,因此可以即时切换源。

switchSound: (id) ->
        @soundSwitch.pause()
        @soundSwitch.src = @sounds[id]._src

        clearTimeout @switchSoundDelay
        @switchSoundDelay = setTimeout =>
            # @soundSwitch.volume = 1
            @soundSwitch.play()
        ,50 
于 2014-08-07T07:34:23.180 回答
1

为 2015 年解决方案更新: 大家好,如果您在这里使用 ios6+ 解决网络音频问题,我发现这些链接可以帮助您。

- 这是一篇带有代码解决方案的好文章:http: //matt-harrison.com/perfect-web-audio-on-ios-devices-with-the-web-audio-api/

- 这是上述 ^ 解决方案文章编写后对 api 的更新https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext

- 下面是我对第一篇文章的更新解决方案,使用了第二篇文章的更改。我遇到的问题是 iOS 7 Safari 抛出了一个奇怪的 not-enough-args 错误。这修复了它:

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;
});
于 2015-11-17T17:24:17.137 回答
1

更新:iOS 仍然需要用户输入才能播放声音(iOS 6 Web Audio API 上没有声音

我之前在 iOS 网络上被网络音频卡住了。更糟糕的是,它需要在 android 和其他桌面平台上运行。这篇文章是我阅读的那些帖子之一,但没有立即找到答案。

直到我找到howler.js

这是跨平台网络音频解决方案的解决方案:

<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>
于 2017-04-24T03:52:28.337 回答
0

这不是一个实际的答案,只是一个看看事情是否仍然不起作用的方向。iOS6 在某些设备上存在音频问题(特别是在特定时期制造的 64gb 4s,尽管我见过其他设备,因此它实际上可能与硬件无关)并且会神秘地停止播放某些声音(对于某些人来说,不是铃声或语音)原因,但许多其他声音),它的音量滑块将消失。我发现调试非常困难,因为它通常(并非总是如此,有时您可以抓住它)仅在未连接电源线时才会发生。

在控制台中查找来自 VirtualAudio_Device 和各种编解码器的 ASSERTION FAILURE 消息。这可能与您的特定问题无关,但话又说回来,声音设备的一个区域中的错误可能与另一个区域有关。至少,如果没有其他帮助,这是一个需要调查的领域。

于 2012-09-29T19:58:48.970 回答
0

该 API 在 iOS 6.1 上似乎被破坏了,或者至少有一个重大变化,这意味着目前没有网站可以使用它。

于 2013-02-15T15:50:31.553 回答
0

我在使用所有简单的解决方案时遇到了麻烦。特别是当我想多次播放声音时。

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

于 2013-12-21T20:57:19.143 回答
0

好的,我喜欢 AshleysBrain 的回答,它帮助我解决了问题。但我找到了一个更通用的解决方案。

在您必须从用户事件启动播放声音之前,现在他们强制您通过用户输入事件来执行此操作,(听起来很奇怪)我所做的只是在播放声音之前读取一个输入字段。

所以

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

playSoundFile 是您用来创建缓冲区源的任何内容。

于 2017-09-23T01:22:13.360 回答