元派遣プログラマの自称技術系ブログです。雑記とか自作のオープンソースプロジェクトの話とか。
Javaとか組込とかできます。お仕事ください。

スマホでも動くブラウザ用のサウンドイフェクト連射クラス

audioタグを使ったサウンド再生は、特にスマートフォンブラウザで短時間に連続して起動するとうまく動きません。
iOSsafariでは顕著で、同一ソースを複数読み込んでマルチチャンネル化しても他の処理に遅延などの影響が出たりします。

以下のクラスでは、音源のマルチチャンネル化と再生再開の手順を組み合わせて、連続再生の問題を解決します。

  class Se
  {
    constructor(jparent,src,channels=1)
    {    
      const setags=[];
      for(var i=0;i<channels;i++){
        jparent.append('<audio id="sound" preload="none" src="'+src+'" type="audio/mp3"></audio>');
        const a=jparent.children('audio:last-child')[0];
        a.load();
        setags.push(a);
      }
      this._tags=setags;
      this._channel=0;
    }
    play(){
      this._channel=(this._channel+1)%this._tags.length;
      const a=this._tags[this._channel];
      if(a.currentTime!=0){
        if(!a.ended){
          //終わってない場合は再生位置リセット
          a.currentTime=0;
        }else{
          //終わってたら再生再開
          a.play();
        }
      }else{
        a.play();
      }
    }
    /** 全てのチャンネルのSEを停止 */
    stop()
    {
      for(var a of this._tags){
        if(!a.ended){
          a.pause();
          a.currentTime=0;
        }
      }
    }
  }

使用例

const se_parent=$("body");
const se={
  touch1:new Se(se_parent,"./audio/boyon1.mp3",5),
  touch2:new Se(se_parent,"./audio/boyoyon1.mp3",2),
  water:new Se(se_parent,"./audio/toilet1.mp3",1),
};
se.play();

デモ

unko.shop

動作原理

Seクラスは1つの音源に対応するクラスです。

コンストラクタはchannels数のaudio要素を格納したリングバッファを持つオブジェクトを生成します。
src要素を持つaudioタグを動的にchannels個生成して、jparentへ挿入します。

playクラスは、リングバッファを巡回してサウンドを再生しようとします。
再生前に現在のaudio要素の状態を調べて、以下のように処理を分岐します。

  • 再生位置が0であれば、そのまま再生する。
  • 再生位置が0以外であれば、終了状態を確認する。

 - 再生が終了していなければ、再生位置を0にもどす。
 - 再生が終了していれば、そのまま再生する。

ポイントは、参照しているaudio要素の再生が終了していない場合に再生位置を0に戻すところです。
この方式であれば、最も古い音源は中断されますが、処理に影響を及ぼす遅延などは発生しません。