Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Chromeで Web Audio API の音が鳴らない現象への対処 (Warning: The AudioContext was not allowed to start)

久しぶりに Web Audio API を使ったコードを書いていたら、音を読み込んで再生するだけの簡単なサンプルコードでも音が鳴らないという現象に遭遇しました。

class AudioPlayer {
    constructor() {
        this.isPlaying = false;
    }

    init = () => {
        window.AudioContext = window.AudioContext || window.webkitAudioContext;
        this.context = new AudioContext();
    }

    setBuffer = async () => {
        const response = await fetch("audio/sample.wav");
        const arrayBuffer = await response.arrayBuffer();
        this.sampleSource.buffer = await this.context.decodeAudioData(arrayBuffer);
    }

    play = () => {
        if (this.isPlaying){
            this.sampleSource?.stop();
        }
        this.sampleSource = this.context.createBufferSource();
        this.setBuffer();

        this.sampleSource.connect(this.context.destination);
        this.sampleSource.start();

        this.isPlaying = true;
    }
    stop = () => {
        this.sampleSource?.stop();
        this.isPlaying = false;
    }
}

audioPlayer = new AudioPlayer();
audioPlayer.init()
document.querySelector("#play").addEventListener("click", audioPlayer.play);
document.querySelector("#stop").addEventListener("click", audioPlayer.stop);
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <button id="play">play</button>
    <button id="stop">stop</button>
  </body>
  <script src="js/audio.js"></script>
</html>

ブラウザを変えて検証したところ、Chromeでのみ音が鳴りませんでした。

そこでChromeでF12を押して開発者ツールを立ち上げたところ、以下のようなWarningが表示されていました。

audio.js:17 The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page. https://goo.gl/7K7WLu

Autoplay policy in Chrome - Chrome Developers や以下の記事によると、Chrome 71 以降は自動再生に制限があり、ユーザー操作で new AudioContext()を作成する必要があるようです。

stackoverflow.com

Autoplay policy in Chrome - Chrome Developers では、ユーザー操作をボタンクリックとした場合での対処法を2つ紹介しています。

(1) window.onload でコンテキストを準備し、ボタンクリック時にresume()で再開する

// Existing code unchanged.
window.onload = function() {
  var context = new AudioContext();
  // Setup all nodes
  // ...
}

// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
  context.resume().then(() => {
    console.log('Playback resumed successfully');
  });
});

(2) ボタンクリック時にコンテキストを生成する

document.querySelector('button').addEventListener('click', function() {
  var context = new AudioContext();
  // Setup all nodes
  // ...
});

試しに (2) の方法に基づいて、掲載したコードの後半を以下のように変更すると無事Chromeでも音が鳴りました。

//...
audioPlayer = new AudioPlayer();
document.querySelector('button').addEventListener('click', audioPlayer.init);
document.querySelector("#play").addEventListener("click", audioPlayer.play);
document.querySelector("#stop").addEventListener("click", audioPlayer.stop);