スマホで音声再生をスムーズに行うためにオーディオスプライトを作成/使用する

iPhoneやAndroidで音声を再生しようとするとき、ブラウザの仕様上の制約から色々と壁にぶちあたります。

## 壁たち

* ユーザのアクションでしかロードできない
* 同時に一つの音しかならせない
	* ※iOS6のSafariとAndroidの一部機種のChromeでは同時に2つ再生できる)
* ファイルを最後まで読み込んでくれない
* 発生するイベントがOSやブラウザによってバラバラ
* 再生できるコーデックがバラバラ

他にも細かい制約やブラウザごとの仕様の違いでオーディオの扱いは相当な苦労を強いられます。

参考:

* [iPhoneでHTML5のaudio要素を使うときに気をつけたいこと](http://www.riaxdnp.jp/?p=1912)
* [iOS における HTML5 の audio 要素に関する制約を克服する](http://www.ibm.com/developerworks/jp/web/library/wa-ioshtml5/)
* [地獄のvideo／audio要素](http://takazudo.github.io/blog/entry/2013-02-06-videoaudio.html)
* [iOS/Android で HTML5 の audio/video を任意のタイミングで再生する方法](http://dsuket.hatenablog.com/entry/2013/05/05/101430)

単純な再生なら「Audioタグを用意して再生する」で大丈夫なのですが、ゲームとかゲームとか音の演出とか効果音とかに使おうとすると途端に大変になります。

効果音をバシバシならす、なんてときに複数の音声ファイルを別々に再生しようとすると"読込"->"再生"をしているみたいでかなりの遅延が発生します。
そこで有効なのがオーディオスプライトです。

CSSスプライトのように複数の音声をまとめて一つの音声ファイルにします。位置を指定して再生することで遅延を軽減してスムーズな再生がおこなえます。

でもでもオーディオスプライトを作るのも、再生位置を管理するのもかなり面倒です。(コーデックの問題も)
面倒なことはライブラリにまかせるのが一番です。

## Zynga Jukebox

Zyngaが作ったオーディオ用のライブラリです。([参考](http://code.zynga.com/2011/11/html5-open-source-meet-zynga-jukebox/))
開発が止まっていてメンテされない(zyngaのgithubからも消え去ってる)のが残念ですが、かなり強力なライブラリです。

ダウンロードは[作者のgithubリポジトリ](https://github.com/martensms/martens.ms/tree/master/raw)からできます。

Zynga Jukeboxは機能的にかなりスマホ向けになっていて、オーディオスプライトの再生に特化しています。

## 使い方

読み込む必要のあるファイルは`Player.js`と`Manager.js`の2つです。

```html:
<button id="audio1">play audio1</button>
<button id="audio2">play audio2</button>
<button id="audio3">play audio3</button>
<button id="stop">stop</button>
<script src="./zynga-jukebox/src/Player.js"></script>
<script src="./zynga-jukebox/src/Manager.js"></script>

<script>
var settings = {
  "resources": [
    "output.caf",
    "output.ac3",
    "output.mp3",
    "output.m4a",
    "output.ogg"
  ],
  "spritemap": {
    "audio_1": {
      "start": 0,
      "end": 3.455986394557823,
      "loop": false
    },
    "audio_2": {
      "start": 5,
      "end": 8.024013605442176,
      "loop": true
    },
    "audio_3": {
      "start": 10,
      "end": 13.36,
      "loop": false
    }
  },
  "autoplay" "audio_3"
}

document.addEventListener('DOMContentLoaded',function(){

  // Playerのインスタンス作成 引数にはJSON形式の設定ファイル
  var player = new jukebox.Player(settings);

  document.getElementById('audio1').addEventListener('click',function(){
    player.play('audio_1',true);
  });

  document.getElementById('audio2').addEventListener('click',function(){
    player.play('audio_2',false);
  });

  document.getElementById('audio3').addEventListener('click',function(){
    player.play('audio_3',true);
  });
  document.getElementById('stop').addEventListener('click',function(){
      player.stop();
  });
});
</script>
```

#### インスタンスの作成
`new jukebox.Player()`でPlayerのインスタンスを作成します。
引数にはオーディオスプライトのresourceと、スプライトの再生位置の情報が書かれたJSONを渡します。

## Playerのメソッド

### .play(pointer,enforce)

第一引数にはsettingsの`spritemap`の下にぶらさがっているオブジェクトのkeyを指定します。(audio_1とか)
第二引数は再生中の音があった場合に再生中の音を全部停止して再生するかを決めるオプションで、trueで停止させる。falseの場合再生中の多重再生を試みる(ブラウザの制約で出来ない場合単独再生される)

`spritemap`の子要素の`loop`プロパティが`true`の場合、その音声が無限ループで再生されます。

`autoplay`に自動再生したい音声のkeyを指定しておくと、再生可能になったタイミングで自動再生されます。BGMを再生したい場合に指定するのに向いています。
autoplayで指定した部分の読み込みが終わってない場合に別の部分が再生される可能性があるので、できるだけファイルの前方に配置するといいです。


基本的に`play()`と`stop()`だけで事足りる気がします。

### その他のメソッド

* `.stop()`
	* 停止
* `.pause()`
	* 一時停止
* `.resume()`
	* 再開
* `.getVolume()`
	* 音量の取得
* `.setVolume(value)`
	* 音量の設定
* `.getCurrentTime()`
	* 再生位置の取得
* `.setCurrentTime(value)`
	* 再生位置の指定

`.setVolume()`はiOS、Androidでは動作しません。(スマホブラウザではJavaScriptでボリュームのコントロールができないため)

### Flashでの再生へのフォールバック

JukbeboxにはAudio要素が使えないブラウザのためにFlashで再生してくれるフォールバック機能があります。
jsと同じ階層に入っているswfフォルダを置いておくことで、特に意識することなくFlashでの再生を行なってくれます。

## オーディオスプライトの作成

「音声ファイルの作成とか結合とかよくわかんない」という人もいると思いますが、これもライブラリでやっちゃいます。

## audiosprite

[github:tonistiigi / audiosprite](https://github.com/tonistiigi/audiosprite)

audiospriteはffmpegのラッパーとして動くツールで、動作にはnode.jsとffmpegが必要です。

### インストール

`npm install -g audiosprite` でインストールできます。
ffmpegは[ダウンロード](http://ffmpeg.zeranoe.com/builds/)してパスを設定してください。

### スプライトの作成

ターミナル(シェル)で

`audiosprite -o output_filename inputfile1.mp3 inputfile2.mp3 inputfile3.mp3`

といった感じで実行すると`output_filename.caf`,`output_filename.ac3`,`output_filename.mp3`,`output_filename.m4a`,`output_filename.ogg`の音声ファイルが出力されます。
※`.caf`はMac環境のみ。

同時に`output_filename.json`が作成されます。このJSONファイルはZynga jukebox用に出力されるもので、読み込ませればスプライトのコントロールが行えます。


その他のコマンドオプションは以下に。

> audiosprite --help
info: Usage: audiosprite [options] file1.mp3 file2.mp3 *.wav
info: Options:
  --output, -o      Name for the output file.                                    [default: "output"]
  --export, -e      Limit exported file types. Comma separated extension list.   [default: ""]
  --log, -l         Log level (debug, info, notice, warning, error).             [default: "info"]
  --autoplay, -a    Autoplay sprite name                                         [default: null]
  --silence, -s     Add special "silence" track with specified duration.         [default: 0]
  --samplerate, -r  Sample rate.                                                 [default: 44100]
  --channels, -c    Number of channels (1=mono, 2=stereo).                       [default: 1]
  --rawparts, -p    Include raw slices(for Web Audio API) in specified formats.  [default: ""]
  --help, -h        Show this help message.

silenceを適度に設定してあげると他の音声のアタマがうっかり鳴るケースが回避できます。

## おわりに

iOS5と6、Androidの2.3.5~4.1.1の6機種程度で動作確認しましたが、だいたいの機種でいい感じに再生できてました。
Androidの一部機種では再生位置の指定がずれて変なことになります。silenceを長めに挟めば解決するかもしれません。（ファイルサイズがでかくなっちゃいますが…)

開発が止まっているとはいえ、スマートフォンの音声再生に使うにはかなり使えるツールだと思います。

---

参考:
* [github:martensms / martens.ms](https://github.com/martensms/martens.ms/tree/master/raw)
* [github:tonistiigi / audiosprite](https://github.com/tonistiigi/audiosprite)
* [そろそろ実践投入もありうる？Audio Sprite](http://docs.komagata.org/tags/audiosprite/)
* [【HTML5】iPhone向けWEBアプリで、Audioを使ってみてハマった点](http://www.riaxdnp.jp/?p=1953)