iOS 9 Safari WebAudio::AudioBufferSourceNode does not play on first touchstart event.

このエントリの末尾に重要な追記があります。この問題は iOS 9.3 β1 で fix しました。

iOS 8 Safari と iOS 9 Safari では WebAudio + touchstart において一部動作が異なっています。

## iOS Safari の制限(おさらい)

まずは、前提となる知識について軽くおさらいから。
  
ご存知のように、 iOS Safari における Media(Audio,Video)の再生には、以下の制限があります。

- Audio や Video リソースの読み込み(load) や 再生(play)は、ユーザのインタラクションを起点としなければならない(パケ死対策)
  - `<audio src="mad.max.m4a" autoplay preload></audio>` の autoplay や preload は無視され、自動で再生されません

この制限は iOS Safari のみに存在します。Chrome や他のブラウザにはありません。


なお、この制限は、「音を鳴らしたい画面に遷移する前に、予めユーザに画面をタッチさせておき、そのタイミングで無音のmp3/aacを流しておくことで、以後自由に音を鳴らせるようにする」といった小技で回避することができます。
(興味がある方は、WebModule の [WMAudioUtil.js](https://github.com/uupaa/WMAudioUtil.js/wiki/WMAudioUtil) を参照してください)

## iOS 9.0 と iOS 9.1 beta で見つかっている不具合

2015-09-17 から配布が開始された iOS 9.0 や、開発者向けに配布されている iOS 9.1 beta には、以下の不具合が存在しています。

- touchstart イベントを起点とした WebAudio の createBufferSource で作成したバッファを再生できない
  - touchend なら問題ない
- createMediaElementSource で作成したバッファは touchstart でも touchend 起点でも再生できる

iOS 9.0 からは 3D Touch(force touch) 機能が追加されたため、touch event 周りの実装にも手が入っています。この不具合もその影響かもしれません[要出典]。

### ワークアラウンド

ワークアラウンドとしては addEventListener("touchstart") を addEventListener("touchend") または click にします。
ただ、音の発生タイミングを微細に制御しているケースでは要注意です。
この変更により 10〜120ms ほどの再生遅延が生まれるかもしれません。

## iOS 9 でも問題なく再生できるパターン

WebAudio::createMediaElementSource は touchstart 起点でも問題なく再生できます。(iOS 8.x と同じ動きになります)

```html
<audio src="mad.max.m4a"></audio>
<script>
var audioContext = new (window.AudioContext || window.webkitAudioContext)();
var audioNode = document.querySelector("audio");
var playable = false;

document.body.addEventListener("touchstart", function() {
  if (playable) {
    audioNode.play();
  } else {
    audioNode.addEventListener("canplay", function(event) {
      playable = true;
      audioContext.createMediaElementSource(audioNode).connect(audioContext.destination);
      audioNode.play();
    });
    audioNode.load();
  }
});
</script>
```

## iOS 9 から問題になるパターン

iOS 9 の WebAudio::createBufferSource は touchstart 起点では再生できません。

[こちらで実際に試せます](https://uupaa.github.io/Issues/issues/13/index.html)。

```js
var audioContext = new (window.AudioContext || window.webkitAudioContext)();

document.body.addEventListener("touchstart", function() {
  var source = createWhiteNoise(audioContext, 1.0); // 1sec
  source.connect(audioContext.destination);
  source.start(0);
});

function createWhiteNoise(audioContext, sec) {
  var channels = 2;
  var frameCount = audioContext.sampleRate * sec;
  var buffer = audioContext.createBuffer(2, frameCount, audioContext.sampleRate);

  for (var channel = 0; channel < channels; channel++) {
    var nowBuffering = buffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      nowBuffering[i] = Math.random() * 2 - 1;
    }
  }
  var source = audioContext.createBufferSource();
  source.buffer = buffer;
  return source;
}
```


おそらくは不具合と思われるため、以下の内容で Apple にバグレポートを出しておきました。  
続報がありましたら更新/追記します。

> Summary
> 
> iOS 9 Safari WebAudio::AudioBufferSourceNode does not play on first touchstart event.
> 
> OS Version
> 
> iOS 9.0 - 9.1 beta
> 
> Devices
> 
> iPhone 4s and iPhone 5. I have not tried in the other device.
> 
> What steps will reproduce the problem?
> 
> Open [https://uupaa.github.io/Issues/issues/13/index.html](https://uupaa.github.io/Issues/issues/13/index.html) in your iOS Device.
> Screen should become green or red. Green uses touchend, red uses a touchstart.
> White noise is played when you touch the text. If the screen is red, does not play it.
> What is the expected behavior?
> 
> AFAIK current playback regulation is [User Control of Downloads Over Cellular Networks](https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html).
> 
> I hope to more clearly the iOS Safari media playback specifications.
> 
> What went wrong?
> 
> There is a difference in behavior in iOS 8 and iOS 9.

## 2015-09-25 追記
バグレポートに返信がきました。Dup だそうです。22695878 の内容は確認できないのですが、番号的に 9/14〜9/15日頃に報告された不具合のようです。

> Engineering has determined that your bug report (22751809) is a duplicate of another issue (22695878) and will be closed.

## 2015-09-30, 2015-10-22 追記

WebKit にパッチが投入され、[safari-601.1.46-branch](https://trac.webkit.org/browser/branches/safari-601.1.46-branch/Source/WebCore/ChangeLog) にマージされましたが、iOS 9.1 には反映されていません。

[190327](https://trac.webkit.org/changeset/190327) REGRESSION: WebAudio user-gesture restriction is no longer lifted by touchstart event

このパッチの内容は https://bugs.webkit.org/show_bug.cgi?id=149367 を参照してください。かいつまむとこのような事が議論されているようです

- [186148](https://trac.webkit.org/changeset/186148) で行われた修正によりこの不具合が発生したようだ
- これヤバイ、影響デカイ。これ音を出してるゲームは大体死ぬじゃん
    - こことかで議論されてる http://www.html5gamedevs.com/topic/17326-ios-9-webaudio-issues/
- 文書化したほうがいいよねこれ
- 実のところ touchend をユーザインタラクションの区切りとみなすのは正しい仕様にみえるけど、後方互換性は失われているのでこれは元に戻すべきだね
    - 「将来はtouchend をユーザインタラクションの区切りとみなします」と言うのもありなのかもしれない


## 2016-01-12 追記

iOS 9.3 β1 で修正されている事を確認しました。touchstart でも iOS 8 と同様に音がなります。