読者です 読者をやめる 読者になる 読者になる

読み書きプログラミング

日常のプログラミングで気づいたことを綴っています

Mobile Safariで好きなタイミングで音を鳴らす

iOS Safariのaudio要素はユーザーイベントで再生をしないと音が出ないようになっています。iOS6でWeb Audio APIがサポートされ、この制約を緩和するノウハウが色々なブログで公開されています。
今回、このノウハウを実際に使うことになったので、CoffeeScriptのクラスで実装してみました。

class WAudio
    @NO_SOURCE: 0
    @LOADING: 1
    @LOADED: 2

    @context: if AudioContext? then new AudioContext() else if webkitAudioContext? then new webkitAudioContext() else null
    @unlock: ->
        # (iOS用) 何かのユーザーイベントの際に呼び出し、Web Audioを有効にする。
        # 空のソースを再生
        source = @context.createBufferSource()
        source.buffer = @context.createBuffer 1, 1, 22050
        source.connect @context.destination
        source.noteOn 0

    constructor: (@src) ->
        @forcePlay = false
        @state = WAudio.NO_SOURCE

    load: ->
        xhr = new XMLHttpRequest()
        xhr.open 'GET', @src
        xhr.responseType = 'arraybuffer'
        xhr.onload = =>
            WAudio.context.decodeAudioData xhr.response, (buffer) =>
                @buffer = buffer
                @state = WAudio.LOADED
                @play() if @forcePlay
        xhr.send()
        @state = WAudio.LOADING

    play: ->
        switch @state
            when WAudio.NO_SOURCE
                @forcePlay = true
                @load()
            when WAudio.LOADING
                @forcePlay = true
            when WAudio.LOADED
                if @source?
                  @source.disconnect()
                @source = WAudio.context.createBufferSource()
                @source.buffer = @buffer
                @source.connect WAudio.context.destination
                @source.noteOn 0
    pause: ->
        @source.noteOff 0

使い方は、
1. 音を鳴らす前にユーザーが起こすイベントを用意する。(Game Startボタンをクリックするとか)
2. 上記イベント処理でWAudio.unlock()を呼び出す。
3. 後はAudio要素をJavaScriptで扱うのと同様、URLを指定してインスタンスを作り、loadなりplayなりします。ユーザーイベントでなくても好きなタイミングで音を鳴らせます。

一点、注意事項。
iOS6では、サイトを「ホーム画面に追加」してウェブアプリ(ウェブクリップ)化した場合、Web Audioで音を鳴らすと、高確率でホームボタンと電源ボタンが効かなくなるという致命的なバグがあります。
ウェブアプリの場合には、Web Audioを使わないようにしましょう。
追記1: iOS7, iOS7.1でもこの不具合は続いています。
追記2: iOS7で音声合成APIが使えるようになりました。これも、一度ユーザーイベントで合成すれば、後は好きなタイミングで合成発声できるようになります。ただ、Web Audioとは独立のようで、Web Audioの上記unlockをしても、好きなタイミングで合成発声はできませんでした。あくまでユーザーイベントで一度合成発声させる必要があります。