O hirunewani blog

iOS Safariの音声再生における制限とその回避策の検証

Created at

ブラウザでは音声の自動再生を防ぐ目的で、音声の再生が制限されている。この記事では、特に制限が厳しいと言われるiOS Safariについて、その制限と回避策の検証を行った内容をまとめる。

iOS Safariの音声周りで不可解な現象に遭遇したため、調査を行った際の内容をまとめる。 なお、その現状自体はSafariのバグであることが分かり無駄足になった。

注意

この記事は、2024年8月に調査した内容を元に書かれている。 2023年に書かれた記事でも再現性が確認出来なかったため、この記事も同様に数ヶ月後には再現性が確認出来ないものになる可能性がある。

また調査は不十分であり間違いが多分に含まれている可能性があるため、自身でも確認することをお勧めする。 なお、このページには実際に動作を確認できる要素を埋め込んでいるため、そのまま動作を確認できる。

ブラウザにおける音声再生の制限

Audio要素やVideo要素を用いて音声を自動再生する場合、ブラウザによる制限が掛かる。

<audio src="sample.mp3" autoplay></audio>

この制限はAutoplay policyと呼ばれるポリシーによるが、実質JavaScriptによる音声の再生そのものへの制限にもなっている。 JavaScriptつまりWeb Audio APIやWeb Video APIが自由に使えれば、自動再生が出来てしまうので妥当な制限と言える。

const audio = new Audio("sample.mp3");
audio.play();

従って、ブラウザで音声を扱おうとする場合、Autoplay policyへの理解はほぼ必須になっている。

以降、Web Audio API及びaudio要素などをAudioContextと総称する。この記事におけるAudioContextはWeb Audio APIのAudioContextオブジェクトのみを指すものではない。

一般的な制限

Safariほどではないものの、現在はChromeのAutoplay policy in Chrome相当の制限がどのブラウザでも適用されている。

条件を満たさなければ自動再生することは出来ない

次のいずれかを満たさない場合、音声を自動的に再生することは出来ない。

  • 音声がミュートされている。
  • ユーザーがクリックやタップなどを行っている。
  • ユーザーがモバイルでサイトをホーム画面に追加するか、PWAをインストールしている。
  • ユーザーがデスクトップにおいて、そのサイトで今までに十分音声を再生している。

iOS Safariにおける制限

iOS Safariにおける制限に付いて詳細にまとめられている公式のドキュメントがないのか、調べると経験則のみに基づいた情報が多く見つかる。

実際、少し調べただけでは2017年に書かれたAuto-play policy changes for macOS程度しか公式の情報は見つからなかった。 だが、これはiOSに限定した話でもなければ、ここに書かれている制限はmacOS Safariでは既に適用されなくなっているものが含まれているように思う。現在ではmacOS Safariでも、Chromeなどとほぼ同じ規則が適用されているように思われる。

iOS Safariにおける制限や回避策の検証

iOS Safariの音声再生に関する制限について、不確かな情報がとても多かったため、実際に試すことにした。 以下に検証した内容をまとめる。

AudioContextはユーザーのクリックやタップ毎に生成する必要がある→再現なし

iOS Safariでは、クリックやタップをトリガーにしてAudioオブジェクトを生成しなければ、最初の1回しか表示されないという記述があったが、再現しなかった。何度押しても音声を再生することが出来る。

const audio = new Audio("/sample0.mp3");

document.getElementById("a1").addEventListener("click", () => {
  audio.currentTime = 0;
  audio.play();
});

クリックやタップ1回に付き1回しか音声を再生できない→再現なし

iOS Safariでは、クリックやタップ1回に付き1回しか音声を再生できないという記述があったが、再現しなかった。一度のクリックで何度も音声を再生することが出来た。

let audio = new Audio("/sample0.mp3");
document.getElementById("a2").addEventListener("click", () => {
  const audio1 = new Audio("/sample0.mp3");
}
window.setInterval(() => {
  if (audio1) {
    audio1.currentTime = 0;
    audio1.play();
  }
}, 1000);

非同期処理を利用すると音声が再生されなくなる→再現なし

非同期処理を挟むだけでクリックなどを行っても音声が再生されなくなるという記述があったが、再現しなかった。

document.getElementById("a3").addEventListener("click", async event => {
  const btn = event.currentTarget;
  btn.classList.add("loading");
  await sleep(4000);
  const audio1 = new Audio("/sample0.mp3");
  audio1.play();
  btn.classList.remove("loading");
});

仕様が変わったわけでなければ、これは非同期処理であることが悪いのではなく、クリックやタップを行ってから時間が経過しすぎていたことが原因ではないかと次のセクションの内容から推測できる。

クリックやタップから5秒程度経過してからAudioContextを生成すると再生できない→再現あり

これは記述を見つけたわけではないが、非同期処理の確認をしている際に発生を確認した。 macOS Safariでは発生せず、iOS Safariでのみ再現した。

document.getElementById("a4").addEventListener("click", async event => {
  const btn = event.currentTarget;
  btn.classList.add("loading");
  await sleep(5000);
  const audio1 = new Audio("/sample0.mp3");
  audio1.play();
  btn.classList.remove("loading");
});

クリックやタップをされてすぐにAudioContextを生成すれば時間が経過しても再生できる→再現あり

見つけた記述では、クリックやタップをされてすぐにAudioContextを生成して再生→停止を行えば、その後時間が経過しても再生できるとあった。

document.getElementById("a5").addEventListener("click", () => {
  const audio = new Audio("sample.mp3");
  audio.play().catch(() => {
    // playを待たずにpauseを呼ぶため、Abort Errorが発生する。
    // それをcatchを呼ぶことで握り潰している。
  });
  audio.pause();
});

これは正しいが、再生→停止を行わなくてもAudioContextを生成するだけで問題はない。

document.getElementById("a5").addEventListener("click", async event => {
  const audio = new Audio("/sample0.mp3");
  const btn = event.currentTarget;
  btn.classList.add("loading");
  await sleep(10000);
  audio.play();
  btn.classList.remove("loading");
});