Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Building a Low-Latency Soundboard in Next.js: Audio Preloading, Web Audio and Mobile Autoplay Gotchas

2026年10月15日に「Dev.to」が公開したITニュース「Building a Low-Latency Soundboard in Next.js: Audio Preloading, Web Audio and Mobile Autoplay Gotchas」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Next.jsで低遅延サウンドボードを作る際、`new Audio()`だと遅延やモバイルでの再生問題が起きる。Web Audio APIを使い音源を事前デコードしてメモリに読み込み、表示に応じて賢くプリロードすることで高速化。`onPointerDown`で再生し、モバイルの再生制限は初回操作で解除する。音源最適化や同時再生数制限も重要だ。

ITニュース解説

Webサイトやアプリケーションで音声を再生する機能は、一見するとシンプルに見える。例えば、ボタンを押すと特定の短い音が鳴る「サウンドボード」のようなアプリを開発する場合、多くの開発者はまずHTMLのnew Audio()オブジェクトを使うことを考えるだろう。しかし、実際にスマートフォンなどのモバイル環境で試してみると、ボタンをタップしてから音が鳴るまでに300ミリ秒程度の明らかな遅延が発生したり、iOSデバイスでは最初のタップが全く反応しなかったり、あるいはボタンを連打すると音が途切れたり、ゆっくりと順番に再生されたりする、といった問題に直面することがある。特にジョークのタイミングが重要なミーム系サウンドボードでは、わずかな遅延が体験を損ねてしまう。

このような遅延や不具合が発生する主な原因は、new Audio()オブジェクトが内部的に行う処理にある。ボタンがタップされるたびに、新しいメディア要素が作成され、その都度、音声ファイルをネットワークから取得するリクエストが発行される(たとえキャッシュされていても参照処理は発生する)。その後、取得した音声データが少しずつデコードされ、ある程度のデータがバッファリングされてからようやく再生が開始されるという一連のステップを踏むため、短い音声を即座に再生するには時間がかかってしまうのだ。HTMLAudioElementは、元々YouTubeのような長いメディアストリーミングのために設計されており、短い音の瞬時な再生には最適ではない。また、同じ音を複数回重ねて再生しようとする場合も、一つの要素を使い回すと困難であり、再生中の要素の再生位置をcurrentTime = 0に設定して頭出しをしても、それ自体に遅延が生じる問題がある。

この問題を解決し、音声再生をほぼ瞬時に行うためには「Web Audio API」の利用が有効だ。Web Audio APIは、音声を事前にメモリ上にダウンロードしてデコードし、「AudioBuffer」という生のPCM(Pulse Code Modulation)データとして保持できる。このAudioBufferを再生するのは、単にオーディオグラフに再生をスケジュールするだけなので、非常に高速だ。このAPIを使うことで、開発者はより低レベルで音声処理を制御できるようになる。具体的には、AudioContextというオーディオ処理の環境を準備し、その中に音源(AudioBufferSourceNode)、音量調整(GainNode)、出力先(DestinationNode)といった要素を組み合わせて音の流れを構築する。AudioContextを初期化する際にlatencyHint: "interactive"というオプションを設定することで、ブラウザに可能な限り最小の出力バッファを使用するように要求し、さらなる遅延の削減を図れる。また、複数のボタンが同時に同じ音声ファイルを要求した場合に、無駄なネットワークリクエストを避けるため、現在読み込み中のファイルを管理する仕組み(inflightマップ)を導入することで、効率的なデータ取得を実現する。そして、Web Audio APIでは、再生するたびに新しいAudioBufferSourceNodeを作成できるため、同じ音を同時に複数再生(重ねて再生)することも容易になる。これはnew Audio()では難しかった点だ。

ただし、事前にデコードされたAudioBufferはファイルサイズに比べてメモリ上の容量が大きくなる傾向がある。例えば、3秒のステレオ音声クリップがMP3形式で50KB程度でも、デコード後は約1MBの生データになることがある。そのため、ページ読み込み時にすべての音声を一度にプリロードするのは、特にモバイルユーザーのデータ通信量を考えると現実的ではない。そこで、プリロードの戦略を工夫する必要がある。最初に画面に表示されているボタンの音声のみを、ページがアイドル状態になったときにプリロードし、その他の音声はユーザーがボタンにポインターを合わせたり(onPointerEnter)、フォーカスしたりしたときにプリロードを開始する。これにより、クリックされるまでに100〜200ミリ秒の猶予が生まれ、ユーザーがクリックする前に準備が完了する可能性が高まる。さらに、スクロールに応じてIntersectionObserverを利用し、画面に近づいてきたボタンの音声を順次プリロードしていくことで、ユーザー体験を損なわずにリソースを節約できる。

ユーザーのインタラクションに対する反応速度も重要だ。通常、WebサイトのボタンはonClickイベントで処理を実行するが、onClickはボタンから指を離した瞬間に発火するため、指を押し下げてから離すまでの時間(80〜150ミリ秒)がそのまま遅延となってしまう。これを避けるために、onPointerDownイベント(ボタンを押し下げた瞬間に発火する)で音声再生を開始することで、物理的なボタンを押すような即時性の高いフィードバックをユーザーに提供できる。

モバイル環境、特にiOSにおける「自動再生の制限」は、Web Audio APIを使用する上で注意すべき大きな落とし穴の一つだ。多くのブラウザは、ユーザーがページと何らかのインタラクションを行うまで、Webページからの音声再生を許可しない。Web Audio APIの場合、これはAudioContextが「suspended」(一時停止)状態で開始され、ユーザーのジェスチャーハンドラ(例えばタップイベント)の中でresume()メソッドを呼び出して再開する必要があることを意味する。iOS Safariは特に厳しく、ページ読み込み時にAudioContextを作成しても、それがsuspendedのままになり、最初のタップでsource.start()を呼び出しても何も再生されないことがある。この問題への対策として、最初のユーザーインタラクションイベント(pointerdownやkeydown)内でAudioContextを再開(または作成)し、さらにiOSでは1サンプル分の無音バッファを再生してオーディオ出力を「暖機」するという裏技も有効だ。その他、iOS固有の挙動として、iPhoneの物理的な「消音スイッチ」がオンになっているとWeb Audioの音がミュートされるという点も挙げられる。これはHTMLの<video>要素などとは異なる挙動のため、ユーザーが「音が出ない」と報告した際に消音スイッチの確認を促すヒントを表示するなどの配慮が必要になる。また、電話がかかってきたり、他のアプリに切り替えたりすると、AudioContextが「interrupted」や再び「suspended」状態になることがあるため、再生前にctx.stateを確認し、必要に応じてresume()を呼び出す処理を組み込むことが堅牢な実装につながる。

音声ファイル自体の最適化も忘れてはならない。多くのミームクリップなどには、先頭に200〜400ミリ秒程度の無音部分が含まれていることがあり、どれだけコードを最適化してもこの無音部分は消えない。ffmpegのようなツールを使って、あらかじめ音声ファイルの先頭の無音部分をトリミングしておくことが重要だ。また、モバイルのスピーカーで再生することを考慮すると、ステレオではなくモノラル、そして96kbps程度のMP3またはAAC形式で十分な品質を得られる。これにより、ファイルサイズを半分程度に削減でき、ダウンロード速度向上に寄与する。ほとんどのモダンブラウザはMP3をサポートしているため、複数のフォーマットを用意する必要も少ない。さらに、これらの音声ファイルをWebサーバー(VercelのようなCDNが有効なサービス)の適切なパスに配置し、next.config.tsなどで長期キャッシュを指示するヘッダ(Cache-Control: public, max-age=31536000, immutableなど)を設定することで、一度ダウンロードされたファイルはブラウザのキャッシュから即座に利用され、ネットワークリクエストのコストを削減できる。

最後に、ユーザーがボタンを激しく連打するような状況にも対応する必要がある。例えば、エアホーンのボタンを毎秒30回もタップされた場合、同じ音源が30回同時に再生されると音が歪んだり、スマートフォンの処理性能が追いつかずにアプリが一時停止したりする可能性がある。このような状況を防ぐため、特定の音声に対する同時再生数を制限するロジックを導入するのが効果的だ。例えば、同時に再生できる音源数を4つに制限し、5つ目の再生が要求された場合は最も古い再生中の音源を停止させて、新しい音源を再生する。これにより、音の歪みを抑え、デバイスへの負担を軽減できる。また、すべての音声を強制的に停止させる「ストップ」ボタンを用意することも、周囲への配慮として有効な機能となるだろう。

これらの対策を総合的に講じることで、開発者はスマートフォン上でほぼ遅延なく、そして安定して動作するサウンドボードアプリを実現できる。具体的には、new Audio()の代わりにWeb Audio APIとデコード済みのAudioBufferを使用し、画面表示やユーザーの操作に応じた賢いプリロード戦略を採用する。クリックではなくpointerdownイベントで再生を開始し、モバイル特有の自動再生制限に対してはユーザーインタラクション内でAudioContextを確実に再開させる。そして、音声ファイル自体も無音部分のトリミングや適切な圧縮、長期キャッシュといった形で最適化する。これらの工夫が、ユーザーにとって快適なWebオーディオ体験を生み出す鍵となる。

関連コンテンツ

関連IT用語

関連ITニュース