【WebAudioAPI】JavaScriptでサーバー上のオーディオファイルを10数行でサクっと再生

概要

サーバー上にあるオーディオファイルをサクっと再生します。
書いた日付:2018/11/24

コード(最近風)

hoge.mp3 を(同じ位置にある)サーバーから拾ってきて、即再生。
【!】音量注意

{
	// 1. オーディオコンテキストを作成
	const audioContext = new (window.AudioContext || window.webkitAudioContext)();	
	fetch("./hoge.mp3").then(_res=>{
		// 2. このthenはArrayBufferを返すように仕向ける
		return _res.arrayBuffer();
	}).then(_arrayBuffer => {
		// 3. 受け取ったArrayBufferをデコードする
		audioContext.decodeAudioData(_arrayBuffer , _audioBuffer =>{
			// ArrayBufferをAudioBufferに変換し終えたらここに到達します。
			// 4. 再生用に、オーディオコンテキストからソースを作成。
			const _source = audioContext.createBufferSource();
			// 5. ソースに、変換されたAudioBufferをセットする
			_source.buffer = _audioBuffer;
			// 6. ソースをオーディオコンテキストのディスティネーションに接続する
			_source.connect(audioContext.destination);
			// 7. ここで再生!
			_source.start(0);
			// おまけ . 5秒後に停止してみる
			setTimeout(_e => _source.stop(), 5000);
		}); // END decodeAudioData()
	}); // END fetch
}

逐一コメントつけちゃってますが、削ればほんのちょっとのコードでファイル再生できるんだなーって印象を受けると思います。

接続には流行り?のfetch()を用いました。
幾らか前だと、XMLHttpRequest()とかが主流の時代でしたね。
という訳で、上と同じ趣旨の処理を下に書いてみました。
関数の書き方も何となく時代を反映したものにしています

コード(一世代前風)

動作趣旨は上のものと同じです。コメント割愛。
異なる箇所としては、.responseType"arraybuffer"を与えると、
使うデータを色々いい感じに返してくれるって所くらいでしょうか・・・?。
上のコードだと、一つ目の then の return _res.arrayBuffer(); が似た役割を持っていそうですね。
【!】音量注意

var audioContext = new (window.AudioContext || window.webkitAudioContext)();
var _httpObj = new XMLHttpRequest();
_httpObj.open('GET', "./hoge.mp3" , true);
_httpObj.responseType = 'arraybuffer';
_httpObj.onload = function(){
	audioContext.decodeAudioData(this.response , function(_audioBuffer){
		var _source = audioContext.createBufferSource();
		_source.buffer = _audioBuffer;
		_source.connect(audioContext.destination);
		_source.start(0);
	});
};
_httpObj.send();

音量弄ろう!

多分環境によってはクッソうるさいと思うので、音量を操作します。
AudioGainNodeと言うもの(以降ゲインと呼ぶ)を作成し、これを仲介役にする必要があります。

先程までのように、作成したsourceを音量を弄ることなく再生する場合は、
直接audioContext.destinationと繋いでいました。

// 音量を弄らないパターン
const _source = audioContext.createBufferSource();
_source.buffer = _audioBuffer;
_source.connect(audioContext.destination); // ソースとディスティネーションを繋ぐ
_source.start(0);

これが音量を弄る場合は、ゲインを仲介することになるので、接続先が変わります

// 音量を弄るパターン
const _source = audioContext.createBufferSource();
_source.buffer = _audioBuffer;
const _gainNode = audioContext.createGain();
_source.connect(_gainNode); // ソースとゲインをつなぐ
_gainNode.connect(audioContext.destination); // ゲインとディスティネーションを繋ぐ
_gainNode.gain.value =  0.2; // 音量を与える
_source.start(0);

多分これから即再生されなくなるかも?

※ 先に書いておくと「ボタン押すと再生」みたいな処理にしたい!って考えている方には、恐らく無縁な話だと思います

WebAudioAPI 使ってると、DevToolsに以下のようなメッセージが出てきます。
( ※ 2018/11月現在 )

The Web Audio autoplay policy will be re-enabled in Chrome 71 (December 2018). Please check that your website is compatible with it.

Googleホンニャク先生曰く、

Web Audioの自動再生ポリシーはChrome 71(2018年12月)で再び有効になります。 あなたのウェブサイトがそれと互換性があることを確認してください。

だそうです。
ちなみに「確認してね~」と出されるURLは↓こちら
developers.google.com - Autoplay Policy Changes

何故、即時再生されなくなると思うのか

このページ内から、WebAudioに関する欄を、翻訳かけた上でちょろっと読んでみたところ、
「Chrome71でAudoplay Policyってのが変わるから、ユーザーアクションをおこさないと勝手に鳴らないよ!」
っていう事なのかなぁと感じました。

先程用いたコードは、処理を書いたページを呼び出すと、そのまま勝手にオーディオファイルを再生する仕様になっていますが、 ポリシーの変更によりこれが今後認められなくなる?
今回挙げたコードだと、具体的には souce"suspended"状態つまり「中断/一時停止」になる事が予想されるのかなと。

どうすれば再生できそう?

ユーザーのアクションが無いとダメなので、パッと出てくる方法だと「再生ボタンを用意してそれを押させる」みたいな感じになりそうですね。 先程の処理全般を、イベントに張り付けるとかなら行けそうな気はします。
…あくまで予想ですが(´・ω・`)

let _buttonNode = document.createElement("button");
_buttonNode.textContent = "押すと再生するよ";
document.body.appendChild(_buttonNode);

_buttonNode.addEventListener("click", _e => {
 //ここに再生処理
});

終わりに / 参考URL

先程示したボリューム操作のほかに、
ループ再生 , クロスフェード , フィルタ , 精度の高い同期再生等も可能なようです。
いや~すごいっすねWebAudioAPI

…という訳で、昔遊んでいたソースを復習がてら記事におこしてみました。

ざっくりこの2つのURLがとても参考になりました。
本腰を入れて触って見ようと思ったら、この2つには必ず当たりそうな気がします
html5rocks.com - Web Audio API の基礎
developer.mozilla.org - Web Audio API