12k
All articles

Sounds mit der Web Audio API abspielen

Mit Web Audio API-Werkzeugen wie AudioContext, AudioBuffer, OscillatorNode und AudioWorklet lassen sich Scheduling, Effekte und Synthese im Browser realisieren.

OpenReplay Team
OpenReplay Team
Sounds mit der Web Audio API abspielen

Sie möchten Audio im Browser mit präziser Kontrolle abspielen – Scheduling, Effekte, Synthese – aber das <audio>-Element reicht nicht aus. Die Web Audio API löst dieses Problem, doch ihre Dokumentation vermischt veraltete Muster mit aktuellen Best Practices. Dieser Artikel erläutert den modernen Ansatz zur Sound-Wiedergabe mit der Web Audio API und behandelt AudioContext-Grundlagen, Source-Nodes und das Audio-Graph-Modell ohne das Altlastengewicht.

Wichtigste Erkenntnisse

  • Erstellen Sie einen AudioContext pro Anwendung und behandeln Sie immer den suspendierten Zustand mit resume() nach Benutzerinteraktion
  • Verwenden Sie AudioBufferSourceNode für vorgeladene Audiodateien und OscillatorNode für synthetisierte Töne – beide sind Einweg-Nodes
  • Vermeiden Sie den veralteten ScriptProcessorNode; verwenden Sie AudioWorklet für benutzerdefinierte Audioverarbeitung
  • Behandeln Sie das Ausgabegeräte-Routing über setSinkId() als experimentell mit unvollständiger Browser-Unterstützung

AudioContext-Grundlagen verstehen

Jede Web-Audio-Anwendung beginnt mit einem AudioContext. Dieses Objekt verwaltet alle Audio-Operationen und stellt die Uhr für das Scheduling bereit. Betrachten Sie es als Laufzeitumgebung für Ihren Audio-Graph.

const audioContext = new AudioContext()

Eine kritische Einschränkung: Browser suspendieren neue Contexts, bis eine Benutzergeste erfolgt. Überprüfen Sie immer audioContext.state und rufen Sie resume() von einem Click- oder Keypress-Handler auf:

button.addEventListener('click', async () => {
  if (audioContext.state === 'suspended') {
    await audioContext.resume()
  }
  // Jetzt sicher für Audio-Wiedergabe
})

In den meisten Anwendungen ist ein einzelner AudioContext ausreichend und empfohlen. Das Erstellen mehrerer Contexts erhöht die Ressourcennutzung und erschwert die Verwaltung von Timing und Synchronisation. (Siehe: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext)

Das Audio-Graph-Modell

Web Audio verwendet einen gerichteten Graphen aus Nodes. Audio fließt von Source-Nodes über Processing-Nodes zu einem Destination. Das audioContext.destination repräsentiert die Standard-Ausgabe – typischerweise die Lautsprecher des Benutzers.

Nodes werden über die connect()-Methode verbunden:

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

Dieses modulare Design ermöglicht es Ihnen, Filter, Gain-Controls oder Analyzer überall in der Kette einzufügen. Trennen Sie Nodes mit disconnect(), wenn Sie den Graphen neu konfigurieren.

Source-Nodes für Sound-Wiedergabe

Zwei Source-Typen decken die meisten Wiedergabe-Szenarien ab.

AudioBufferSourceNode

Für vorgeladene Audiodateien dekodieren Sie die Daten in einen AudioBuffer und spielen sie dann über einen AudioBufferSourceNode ab:

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()

Jeder AudioBufferSourceNode spielt einmal ab. Erstellen Sie für jede Wiedergabe einen neuen Source-Node – sie sind leichtgewichtig. Der zugrunde liegende AudioBuffer ist wiederverwendbar.

Beachten Sie, dass decodeAudioData() in modernen Browsern Promises unterstützt, ältere Safari-Versionen jedoch die Callback-Form erforderten. Dies ist hauptsächlich ein Legacy-Problem, aber dennoch relevant für Long-Tail-Kompatibilität.

OscillatorNode

Für synthetisierte Töne verwenden Sie 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)

Oszillatoren spielen ebenfalls nur einmal ab. Planen Sie start()- und stop()-Zeiten mit audioContext.currentTime für sample-genaue Wiedergabe.

AudioWorklet vs. ScriptProcessor

Für benutzerdefinierte Audioverarbeitung vermeiden Sie ScriptProcessorNode. Er ist veraltet, läuft im Haupt-Thread und verursacht Audio-Glitches unter Last.

AudioWorklet ist der moderne Ersatz. Es läuft in einem dedizierten Audio-Rendering-Thread mit deterministischem Timing:

// processor.js
class MyProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // Audio hier verarbeiten
    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 wird in allen modernen Evergreen-Browsern unterstützt. Die Haupteinschränkungen sind tendenziell umgebungsbedingt (Secure Context, Bundling, Cross-Origin-Setup) und nicht auf fehlende API-Unterstützung zurückzuführen.

Einschränkungen beim Web-Audio-Ausgabe-Routing

Die Ausgabe-Routing-Fähigkeiten der API sind begrenzt und browserabhängig. Standardmäßig wird Audio zu audioContext.destination geleitet, das auf das Standard-Ausgabegerät des Systems abgebildet wird.

AudioContext.setSinkId() ermöglicht die Auswahl spezifischer Ausgabegeräte, aber behandeln Sie dies als experimentell und stark eingeschränkt. Es erfordert einen Secure Context (HTTPS), Benutzererlaubnis und entsprechende Permissions-Policy-Header. In der Praxis ist es auf Safari oder iOS nicht verfügbar und sollte nicht für plattformübergreifendes Lautsprecher-Switching verwendet werden.

Für Anwendungen, die Ausgabegeräte-Auswahl benötigen, erkennen Sie die Unterstützung explizit:

if (typeof audioContext.setSinkId === 'function') {
  // Vorhandensein garantiert keine Verwendbarkeit; Berechtigungen und Policy können es dennoch blockieren
}

Selbst wenn die Methode existiert, können Aufrufe aufgrund von Policy- oder Plattform-Einschränkungen fehlschlagen. Bauen Sie Fallbacks ein und kommunizieren Sie diese Einschränkungen klar an die Benutzer.

Praktische Überlegungen

Autoplay-Richtlinien blockieren Audio bis zur Benutzerinteraktion. Gestalten Sie Ihre UI so, dass sie einen Klick vor der Initialisierung der Wiedergabe erfordert.

CORS-Einschränkungen gelten beim Abrufen von Audiodateien über Cross-Origin. Stellen Sie entsprechende Header sicher oder hosten Sie Dateien auf derselben Origin.

Speicherverwaltung ist wichtig für buffer-intensive Anwendungen. Dereferenzieren Sie ungenutzte AudioBuffer-Objekte und trennen Sie Nodes, mit denen Sie fertig sind.

Mobile Browser verhängen zusätzliche Einschränkungen – insbesondere iOS Safari. Testen Sie auf echten Geräten, nicht nur auf Simulatoren.

Fazit

Die Web Audio API bietet leistungsstarke, Low-Level-Audio-Kontrolle durch ein graphenbasiertes Modell. Beginnen Sie mit einem einzelnen AudioContext, respektieren Sie Autoplay-Richtlinien und verwenden Sie moderne Muster: AudioBufferSourceNode für Samples, OscillatorNode für Synthese und AudioWorklet für benutzerdefinierte Verarbeitung. Behandeln Sie Ausgabegeräte-Routing als stark eingeschränkt und plattformabhängig. Feature-Detection für alles, und bauen Sie für die Einschränkungen, die Browser tatsächlich auferlegen.

FAQs

Warum funktioniert mein Web-Audio-Code in der Entwicklung, schlägt aber in der Produktion stillschweigend fehl?

Browser blockieren die AudioContext-Erstellung, bis eine Benutzerinteraktion erfolgt. Ihre Entwicklungsumgebung hat möglicherweise permissivere Einstellungen. Überprüfen Sie immer audioContext.state und rufen Sie resume() innerhalb eines Click- oder Keypress-Handlers auf, bevor Sie versuchen abzuspielen. Diese Autoplay-Richtlinie gilt universell für moderne Browser.

Kann ich einen AudioBufferSourceNode wiederverwenden, um denselben Sound mehrmals abzuspielen?

Nein. AudioBufferSourceNode-Instanzen sind per Design Einweg. Nach dem Aufruf von start() kann der Node nicht neu gestartet werden. Erstellen Sie für jede Wiedergabe einen neuen Source-Node, während Sie den zugrunde liegenden AudioBuffer wiederverwenden. Source-Nodes sind leichtgewichtig, daher hat dieses Muster minimale Performance-Auswirkungen.

Wie leite ich Web-Audio-Ausgabe zu einem bestimmten Lautsprecher oder Audiogerät?

Verwenden Sie AudioContext.setSinkId(), aber behandeln Sie es als stark eingeschränkt. Es erfordert HTTPS, Benutzererlaubnis und permissive Policies und wird auf Safari oder iOS nicht unterstützt. Führen Sie immer Feature-Detection durch, behandeln Sie Fehler elegant und informieren Sie Benutzer, wenn Geräteauswahl nicht verfügbar ist.

Was ist der Unterschied zwischen AudioWorklet und ScriptProcessorNode?

ScriptProcessorNode ist veraltet und läuft im Haupt-Thread, was Audio-Glitches bei intensiver Verarbeitung verursacht. AudioWorklet läuft in einem dedizierten Audio-Rendering-Thread mit deterministischem Timing, was es für Echtzeit-Audio-Manipulation geeignet macht. Verwenden Sie immer AudioWorklet für benutzerdefinierte Verarbeitung in neuen Projekten.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.