Android のスマートフォンで音声を鳴らすときに気をつけることは、ここ十数年で大きく変わりました。昔は機種ごとの違いに振り回されましたが、今はパソコンのブラウザーとほぼ同じ考え方で扱えます。ここでは、今の Android のブラウザーで音声を再生するときの決まりと、形式の選び方をまとめます。

昔は機種ごとにばらばらだった

初期の Android には、OS に付いてくる標準のブラウザーがあり、端末を作る会社ごとに手が加えられていることもありました。そのため、同じ OS の版でも、audio 要素が動かない、特定の形式だけ再生できない、イベントが起きない、といった違いが機種ごとにありました。動作を確かめるには、実際の端末を何台も用意するしかありませんでした。

今の Android では、Chrome のほか、Firefox や各社のブラウザーが使われています。多くは Chrome と同じ Chromium をもとにしていて、更新もアプリとして配られます。音声の扱いは、パソコン向けの同じブラウザーとほとんど変わりません。機種ごとの違いを細かく気にするより、標準の書き方に沿って作り、失敗したときの扱いを用意しておくことが大切です。

なお、アプリの中でページを表示する WebView では、自動再生を許すかどうかをアプリの側の設定 (WebSettings の setMediaPlaybackRequiresUserGesture()) で変えられます。ブラウザーとアプリの中とで動きが違うときは、この設定を疑います。

自動再生には利用者の操作が要る

Android の Chrome などでも、音の出る再生を勝手に始めることはできません。基本は、利用者がページで操作 (タップやクリック、キー入力) をしてから再生する、という決まりです。音を消した動画の自動再生は許されます。

ブラウザーによっては、過去にそのサイトで音声をよく再生していた場合や、ホーム画面に追加したサイトなどで、自動再生を許すことがあります。ただし、条件はブラウザーによって違い、利用者の設定でも変わります。作る側は「操作が無いと鳴らない」ことを前提にし、再生ボタンを用意しておきます。そもそも、ページを開いた途端に音が鳴るのは、電車の中などでは迷惑になりがちです。

const audio = document.querySelector("#talk");
const message = document.querySelector("#audio-message");

document.querySelector("#play-button").addEventListener("click", () => {
  audio.play().catch((error) => {
    // 再生が許されなかった、形式に対応していない、などの理由で失敗する
    message.textContent = "音声を再生できませんでした (" + error.name + ")";
  });
});

play() は Promise を返し、再生できなければ失敗します。失敗を捕まえないと、コンソールにエラーが出るだけで、利用者には何も起きていないように見えます。

対応する形式

今の Android のブラウザーは、おおむね次の形式を再生できます。

  • MP3 (audio/mpeg)
  • AAC を MP4 に入れたもの (拡張子 .m4a、audio/mp4)
  • Ogg Vorbis (audio/ogg)
  • Opus (Ogg や WebM に入れたもの)
  • WAV、FLAC

ただし、iPhone の Safari など、ほかのブラウザーでも再生できるかは別の話です。どの環境でも鳴らしたいなら、MP3 か AAC (.m4a) を用意しておくのが無難です。

再生できるかどうかは、canPlayType() で確かめられます。結果は "probably" (たぶんできる)、"maybe" (できるかもしれない)、"" (できない) の 3 通りで、「できる」と断言はしません。

const probe = document.createElement("audio");
const types = {
  "MP3": "audio/mpeg",
  "AAC (.m4a)": 'audio/mp4; codecs="mp4a.40.2"',
  "Ogg Vorbis": 'audio/ogg; codecs="vorbis"',
  "Opus (Ogg)": 'audio/ogg; codecs="opus"',
};

for (const [name, type] of Object.entries(types)) {
  // 空文字列なら再生できない
  console.log(name, probe.canPlayType(type) || "(非対応)");
}

codecs まで書くと、より確かな答えが返ります。形式名だけだと "maybe" になりやすいです。

audio 要素に複数の source を書く

形式ごとに canPlayType() で調べて切り替える処理を書かなくても、audio 要素の中に source 要素を並べておけば、ブラウザーが上から順に見て、再生できる最初のものを選びます。

<audio controls preload="metadata">
  <!-- 上から順に試され、再生できる最初のものが使われる -->
  <source src="/audio/talk.opus" type='audio/ogg; codecs="opus"'>
  <source src="/audio/talk.m4a" type="audio/mp4">
  <source src="/audio/talk.mp3" type="audio/mpeg">
  <p><a href="/audio/talk.mp3">音声ファイル (MP3)</a>をダウンロードして聞けます。</p>
</audio>
  • type を書いておくと、ブラウザーは再生できない形式のファイルを読みに行かずに済みます
  • ファイルの大きさが小さくなる形式 (Opus など) を先に、どこでも再生できる形式 (MP3) を最後に並べます
  • preload="metadata" にすると、最初は長さなどの情報だけを読みます。モバイル回線の通信量を抑えられます
  • audio 要素の中の文章やリンクは、audio 要素そのものに対応していないブラウザーでだけ表示されます

まとめ

  • 昔の Android は機種ごとに音声の扱いがばらばらだったが、今はパソコンのブラウザーとほぼ同じに扱える
  • 音の出る自動再生は、利用者の操作が無いとできないことを前提にし、play() の失敗を捕まえて再生ボタンを見せる
  • アプリの中の WebView では、自動再生の扱いがアプリの設定で変わる
  • 再生できる形式は canPlayType() で確かめられる。どこでも鳴らすなら MP3 か AAC を用意する
  • audio 要素の中に source を並べれば、ブラウザーが再生できるものを選ぶ

参考