ブラウザで音源をきれいにループ再生させたい
まえがき
ブラウザゲームを作るときに、音源をループ再生させたいことがある が、こだわりだすと設定が面倒なので、まとめておく
ループ再生の方法
いろいろあるが、楽な方法よりもきれいにループさせる方法を紹介する
AudioContext.createBufferSource()を使う方法
// 音源をフェッチしてデコード
const audioContext = new AudioContext();
const response = await fetch('bgm.ogg');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
// BufferSourceを作成してループ設定
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.loop = true;
source.loopStart = 10; // ループ開始位置(秒)
source.loopEnd = 60; // ループ終了位置(秒)
source.connect(audioContext.destination);
source.start();
注意:oggはSafariで再生できない Safariはogg形式をサポートしていないため、Safari対応が必要な場合はmp3やm4aなど別の形式を使う必要がある
余談: awaitの部分の処理の話
// 96kbpsのoggの場合、60秒の音源で約720KB
// 低速回線(10Mbps)ではダウンロード時間は約0.6秒
const response = await fetch('bgm.ogg');
// ArrayBufferに変換、これは圧縮されたままのデータでほぼ一瞬
const arrayBuffer = await response.arrayBuffer();
// ここで非圧縮PCMにデコードされるので、メモリ使用量は
// 44100Hz×16bit×2ch×60秒 = 約10MB
// デコード時間は環境によるが数百ミリ秒〜数秒程度
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
余談2: ループポイントの決め方
- 音源を二つ用意して、片方をフェードイン・フェードアウトさせながら切り替える方法もあるらしい
- 実際、音源の最後から最初にループさせてしまうと、リバーブなどの残響が途切れてしまい不自然になることがある
- そのため、ループポイントは音源の途中に設定しておくのがよい
参考までに、他の方法
-
HTMLAudioElementを使う方法
<audio>タグのloop属性を使う方法。ループポイントの指定ができない。
mp3の場合は前後に無音が入るので良くない。 -
SetTimeoutで再生終了を検知して再生し直す方法
音源の終了時間を指定して、SetTimeoutのコールバックでaudio.currentTimeをループポイントに戻して再生し直す方法。 ブラウザ依存であり、別タブや負荷が高いときに時間がずれるが、割と許容範囲。スマホだとかなり不安定。
あとがき
safariさぁ……