Back

Sounds mit der Web Audio API abspielen

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

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.

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.

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.

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 — 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