プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

連打しても同期的に一度ずつしか鳴らない再生ボタンをjsで作る

概要

  • 非同期で要求された処理(キュー)を、1つずつ順番に同期っぽく消化していく
  • 全て消化し終える前に、追加でキューが送られてくるケースも想定

そんな感じのことをやってみたかった

処理例・イメージ

今回は「サウンド再生ボタン」を例にとってみました。

"押すと数秒の音源ファイルを再生するボタン" があったとして、これを3連打したら
押したぶんだけサウンドが一度ずつ順番に再生処理される感じ。

全ての再生処理を終える前に追加でボタンを押せば、そのぶんキューが積まれるようにもした。
ただし今回はキュー数に上限を設け、幾ら連打しても所定数を超えた再生処理は積まない仕様も追加。

コード例

  • ※ 🔊 同階層に file.wav を用意してください。
    ( .mp3 とかでも良いと思う )
const CueList = []; // キューの保持
const CueLimit = 5; // 上限

window.onload = ()=>{
    const _btn = document.getElementById("play_button");
    _btn.addEventListener("click",async()=>{
        const _count = CueList.length;
        console.log("未完了のキュー数" , _count);
        if(_count > 0){
            console.log("未完了のキューがまだあるので、キューに追加しておくだけ");
            if(_count >= CueLimit){
                console.warn("上限到達により、このキューを破棄します");
                return;
            }
            CueList.push("キューに関する任意の付随情報");
        }else{
            console.log("予約なし。キューに入れつつ即時消化のために再生を開始");
            CueList.push("キューに関する任意の付随情報");
            CuePlay();
        }
    });
};

// サウンドファイルを取得・再生する。
// ここは原則、手動で一度だけ呼ばれるが、
// 再生終了後に CueList をチェックし、要素が存在していれば自身を再帰的に呼び出す
async function CuePlay(){

    // 先頭のキュー情報を取り出し( 本例では特に使っていない )
    const _someCueInfo = CueList[0];
    console.log( _someCueInfo , "を実行" );
    // ファイルの通信取得
    const _res = await fetch( "file.wav" , { method : "GET" , responseType: 'arraybuffer' })
    .then(_res=> _res);
    // 取得した音源の再生準備
    {
        const _audioCtx = new window.AudioContext();
        const _arrBuf = await _res.arrayBuffer();
        const _srcNode = _audioCtx.createBufferSource();
        _srcNode.buffer = await _audioCtx.decodeAudioData( _arrBuf );
        _srcNode.connect( _audioCtx.destination );
        _srcNode.addEventListener("ended",(_e)=>{
            // 再生終了時に呼ばれる。「このキューを完了した」とみなし、キュー情報を削除
            CueList.shift();
            const _count = CueList.length;
            if(_count > 0){
                // 未消化のキューがあれば、再帰で同じ処理を掘る。
                CuePlay();  
            }
        });
        // 再生
        _srcNode.start(0);
    }
}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>再生テスト</title>
    <script src="./main.js"></script>
    <style>
        #play_button{
            padding:10px;
        }
    </style>
</head>
<body>
    <button id="play_button">キュー追加 兼 再生ボタン</button>
</body>
</html>

その他補足

  • 今回、_someCueInfo 部分の情報は活用していないものの、
    本来は「キューごとに独自の付随情報を入れる」みたいな使い方を想定して書いた。
  • ブラウザの制約で file:/// スキーム & fetch を用いた音源ファイル通信取得はできないので、
    適当にローカルサーバー立てたのち http://localhost/ 経由で触るなどしてください。

MDN > Fetch API
MDN > AudioScheduledSourceNode : ended