Back

Web Audio APIを使った音声再生

Web Audio APIを使った音声再生

ブラウザで音声を再生する際に、スケジューリング、エフェクト、シンセシスなどを精密に制御したい場合、<audio>要素では不十分です。Web Audio APIがこの問題を解決しますが、そのドキュメントには非推奨のパターンと現在のベストプラクティスが混在しています。本記事では、Web Audio APIにおける音声再生の現代的なアプローチを明確にし、AudioContextの基礎、ソースノード、オーディオグラフモデルについて、レガシーな負債を除いて解説します。

重要なポイント

  • アプリケーションごとに1つのAudioContextを作成し、ユーザーインタラクション後に必ずresume()でサスペンド状態を処理する
  • プリロードされた音声ファイルにはAudioBufferSourceNodeを、シンセサイズされたトーンにはOscillatorNodeを使用する—どちらも使い捨てノード
  • 非推奨のScriptProcessorNodeは避け、カスタム音声処理にはAudioWorkletを使用する
  • setSinkId()による出力デバイスのルーティングは実験的機能として扱い、ブラウザサポートが不完全であることを認識する

AudioContextの基礎を理解する

すべてのWeb AudioアプリケーションはAudioContextから始まります。このオブジェクトはすべての音声操作を管理し、スケジューリングのためのクロックを提供します。オーディオグラフのランタイム環境と考えてください。

const audioContext = new AudioContext()

重要な制約として、ブラウザはユーザージェスチャーが発生するまで新しいコンテキストをサスペンドします。常にaudioContext.stateをチェックし、クリックまたはキープレスハンドラーからresume()を呼び出してください:

button.addEventListener('click', async () => {
  if (audioContext.state === 'suspended') {
    await audioContext.resume()
  }
  // これで安全に音声を再生できます
})

ほとんどのアプリケーションでは、単一のAudioContextで十分であり、推奨されます。複数のコンテキストを作成すると、リソース使用量が増加し、タイミングと同期の管理が困難になります。(参照: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext)

オーディオグラフモデル

Web Audioは有向グラフのノードを使用します。音声はソースノードから処理ノードを経由して、デスティネーションに流れます。audioContext.destinationはデフォルトの出力を表し、通常はユーザーのスピーカーです。

ノードはconnect()メソッドで接続します:

sourceNode.connect(gainNode)
gainNode.connect(audioContext.destination)

このモジュラー設計により、チェーンの任意の場所にフィルター、ゲインコントロール、アナライザーを挿入できます。グラフを再構成する際は、disconnect()でノードを切断します。

音声再生のためのソースノード

2つのソースタイプがほとんどの再生シナリオに対応します。

AudioBufferSourceNode

プリロードされた音声ファイルの場合、データをAudioBufferにデコードし、AudioBufferSourceNodeを通じて再生します:

const response = await fetch('sound.mp3')
const arrayBuffer = await response.arrayBuffer()
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer)

const source = audioContext.createBufferSource()
source.buffer = audioBuffer
source.connect(audioContext.destination)
source.start()

AudioBufferSourceNodeは一度だけ再生されます。再生ごとに新しいソースノードを作成してください—軽量です。基礎となるAudioBufferは再利用可能です。

なお、decodeAudioData()は最新のブラウザではPromiseをサポートしていますが、古いバージョンのSafariではコールバック形式が必要でした。これは主にレガシーな懸念事項ですが、ロングテール互換性には依然として関連があります。

OscillatorNode

シンセサイズされたトーンには、OscillatorNodeを使用します:

const oscillator = audioContext.createOscillator()
oscillator.type = 'sine'
oscillator.frequency.setValueAtTime(440, audioContext.currentTime)
oscillator.connect(audioContext.destination)
oscillator.start()
oscillator.stop(audioContext.currentTime + 1)

オシレーターも一度だけ再生されます。サンプル精度の再生のために、audioContext.currentTimeを使用してstart()stop()の時間をスケジュールします。

AudioWorklet vs ScriptProcessor

カスタム音声処理には、ScriptProcessorNodeを避けてください。これは非推奨であり、メインスレッドで実行され、負荷がかかると音声の途切れを引き起こします。

AudioWorkletが現代的な代替手段です。専用の音声レンダリングスレッドで決定論的なタイミングで実行されます:

// processor.js
class MyProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // ここで音声を処理
    return true
  }
}
registerProcessor('my-processor', MyProcessor)
// main.js
await audioContext.audioWorklet.addModule('processor.js')
const workletNode = new AudioWorkletNode(audioContext, 'my-processor')
workletNode.connect(audioContext.destination)

AudioWorkletはすべての最新のエバーグリーンブラウザでサポートされています。主な制約は、APIサポートの欠如ではなく、環境関連(セキュアコンテキスト、バンドリング、クロスオリジン設定)である傾向があります。

Web Audio出力ルーティングの制約

APIの出力ルーティング機能は限定的で、ブラウザに依存します。デフォルトでは、音声はaudioContext.destinationにルーティングされ、これはシステムのデフォルト出力デバイスにマッピングされます。

AudioContext.setSinkId()は特定の出力デバイスを選択できますが、これは実験的で非常に制約があるものとして扱ってください。セキュアコンテキスト(HTTPS)、ユーザー許可、適切なパーミッションポリシーヘッダーが必要です。実際には、SafariやiOSでは利用できず、クロスプラットフォームのスピーカー切り替えには依存すべきではありません。

出力デバイス選択が必要なアプリケーションでは、サポートを明示的に検出してください:

if (typeof audioContext.setSinkId === 'function') {
  // 存在しても使用可能性は保証されません。パーミッションやポリシーがブロックする可能性があります
}

メソッドが存在しても、ポリシーやプラットフォームの制限により呼び出しが失敗する可能性があります。フォールバックを構築し、これらの制約をユーザーに明確に伝えてください。

実践的な考慮事項

自動再生ポリシーは、ユーザーインタラクションまで音声をブロックします。再生を初期化する前にクリックを要求するようUIを設計してください。

CORS制限は、クロスオリジンで音声ファイルを取得する際に適用されます。適切なヘッダーを確保するか、同一オリジンでファイルをホストしてください。

メモリ管理は、バッファを多用するアプリケーションで重要です。未使用のAudioBufferオブジェクトの参照を解除し、使用が終わったノードを切断してください。

モバイルブラウザは追加の制約を課します—特にiOS Safariです。シミュレーターだけでなく、実機でテストしてください。

まとめ

Web Audio APIは、グラフベースのモデルを通じて強力な低レベルの音声制御を提供します。単一のAudioContextから始め、自動再生ポリシーを尊重し、現代的なパターンを使用してください:AudioBufferSourceNodeでサンプルを、OscillatorNodeでシンセシスを、AudioWorkletでカスタム処理を行います。出力デバイスのルーティングは非常に制約があり、プラットフォーム依存であるものとして扱ってください。すべてを機能検出し、ブラウザが実際に課す制約に対応して構築してください。

よくある質問

ブラウザはユーザーインタラクションが発生するまでAudioContextの作成をブロックします。開発環境ではより寛容な設定になっている可能性があります。再生を試みる前に、必ずaudioContext.stateをチェックし、クリックまたはキープレスハンドラー内でresume()を呼び出してください。この自動再生ポリシーは最新のブラウザ全体に普遍的に適用されます。

いいえ。AudioBufferSourceNodeインスタンスは設計上、使い捨てです。start()を呼び出した後、ノードを再起動することはできません。基礎となるAudioBufferを再利用しながら、再生ごとに新しいソースノードを作成してください。ソースノードは軽量なので、このパターンはパフォーマンスへの影響が最小限です。

AudioContext.setSinkId()を使用しますが、非常に制約があるものとして扱ってください。HTTPS、ユーザー許可、寛容なポリシーが必要であり、SafariやiOSではサポートされていません。常に機能検出を行い、失敗を適切に処理し、デバイス選択が利用できない場合はユーザーに通知してください。

ScriptProcessorNodeは非推奨であり、メインスレッドで実行されるため、重い処理中に音声の途切れを引き起こします。AudioWorkletは専用の音声レンダリングスレッドで決定論的なタイミングで実行されるため、リアルタイムの音声操作に適しています。新しいプロジェクトでは、カスタム処理には常にAudioWorkletを使用してください。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay