Lecture de sons avec l'API Web Audio
Vous souhaitez lire de l’audio dans le navigateur avec un contrôle précis — planification, effets, synthèse — mais l’élément <audio> ne suffit pas. L’API Web Audio résout ce problème, mais sa documentation mélange des modèles obsolètes avec les bonnes pratiques actuelles. Cet article clarifie l’approche moderne de la lecture audio avec l’API Web Audio, couvrant les bases d’AudioContext, les nœuds sources et le modèle de graphe audio sans le poids de l’héritage.
Points clés à retenir
- Créez un seul
AudioContextpar application et gérez toujours l’état suspendu avecresume()après une interaction utilisateur - Utilisez
AudioBufferSourceNodepour les fichiers audio préchargés etOscillatorNodepour les tonalités synthétisées — les deux sont des nœuds à usage unique - Évitez le
ScriptProcessorNodeobsolète ; utilisezAudioWorkletpour le traitement audio personnalisé - Considérez le routage vers les périphériques de sortie via
setSinkId()comme expérimental avec un support navigateur incomplet
Comprendre les bases d’AudioContext
Toute application Web Audio commence par un AudioContext. Cet objet gère toutes les opérations audio et fournit l’horloge pour la planification. Considérez-le comme l’environnement d’exécution de votre graphe audio.
const audioContext = new AudioContext()
Une contrainte critique : les navigateurs suspendent les nouveaux contextes jusqu’à ce qu’un geste utilisateur se produise. Vérifiez toujours audioContext.state et appelez resume() depuis un gestionnaire de clic ou de touche :
button.addEventListener('click', async () => {
if (audioContext.state === 'suspended') {
await audioContext.resume()
}
// Maintenant sûr de lire l'audio
})
Dans la plupart des applications, un seul AudioContext est suffisant et recommandé. Créer plusieurs contextes augmente l’utilisation des ressources et rend la synchronisation et le timing plus difficiles à gérer. (Voir : https://developer.mozilla.org/en-US/docs/Web/API/AudioContext)
Le modèle de graphe audio
Web Audio utilise un graphe orienté de nœuds. L’audio circule depuis les nœuds sources, à travers les nœuds de traitement, jusqu’à une destination. Le audioContext.destination représente la sortie par défaut — typiquement les haut-parleurs de l’utilisateur.
Les nœuds se connectent via la méthode connect() :
sourceNode.connect(gainNode)
gainNode.connect(audioContext.destination)
Cette conception modulaire vous permet d’insérer des filtres, des contrôles de gain ou des analyseurs n’importe où dans la chaîne. Déconnectez les nœuds avec disconnect() lors de la reconfiguration du graphe.
Nœuds sources pour la lecture audio
Deux types de sources gèrent la plupart des scénarios de lecture.
AudioBufferSourceNode
Pour les fichiers audio préchargés, décodez les données dans un AudioBuffer, puis lisez-le via un 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()
Chaque AudioBufferSourceNode se lit une seule fois. Créez un nouveau nœud source pour chaque lecture — ils sont légers. L’AudioBuffer sous-jacent est réutilisable.
Notez que bien que decodeAudioData() supporte les promesses dans les navigateurs modernes, les anciennes versions de Safari nécessitaient la forme callback. C’est principalement une préoccupation héritée mais toujours pertinente pour une compatibilité étendue.
OscillatorNode
Pour les tonalités synthétisées, utilisez 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)
Les oscillateurs se lisent également une seule fois. Planifiez les temps start() et stop() en utilisant audioContext.currentTime pour une lecture précise au niveau de l’échantillon.
Discover how at OpenReplay.com.
AudioWorklet vs ScriptProcessor
Pour le traitement audio personnalisé, évitez ScriptProcessorNode. Il est obsolète, s’exécute sur le thread principal et provoque des glitchs audio sous charge.
AudioWorklet est le remplacement moderne. Il s’exécute dans un thread de rendu audio dédié avec un timing déterministe :
// processor.js
class MyProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
// Traiter l'audio ici
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 est pris en charge dans tous les navigateurs modernes à mise à jour automatique. Les principales contraintes tendent à être liées à l’environnement (contexte sécurisé, bundling, configuration cross-origin) plutôt qu’à un manque de support de l’API.
Contraintes de routage de sortie Web Audio
Les capacités de routage de sortie de l’API sont limitées et dépendent du navigateur. Par défaut, l’audio est routé vers audioContext.destination, qui correspond au périphérique de sortie par défaut du système.
AudioContext.setSinkId() permet de sélectionner des périphériques de sortie spécifiques, mais considérez cela comme expérimental et fortement contraint. Cela nécessite un contexte sécurisé (HTTPS), une permission utilisateur et des en-têtes de politique de permissions appropriés. En pratique, ce n’est pas disponible sur Safari ou iOS et ne devrait pas être utilisé pour la commutation de haut-parleurs multi-plateformes.
Pour les applications nécessitant la sélection du périphérique de sortie, détectez explicitement le support :
if (typeof audioContext.setSinkId === 'function') {
// La présence ne garantit pas l'utilisabilité ; les permissions et la politique peuvent toujours le bloquer
}
Même lorsque la méthode existe, les appels peuvent échouer en raison de limitations de politique ou de plateforme. Prévoyez des solutions de repli et communiquez clairement ces contraintes aux utilisateurs.
Considérations pratiques
Les politiques d’autoplay bloquent l’audio jusqu’à l’interaction utilisateur. Concevez votre interface utilisateur pour nécessiter un clic avant d’initialiser la lecture.
Les restrictions CORS s’appliquent lors de la récupération de fichiers audio cross-origin. Assurez-vous d’avoir les en-têtes appropriés ou hébergez les fichiers sur la même origine.
La gestion de la mémoire est importante pour les applications gourmandes en buffers. Déréférencez les objets AudioBuffer inutilisés et déconnectez les nœuds dont vous avez terminé l’utilisation.
Les navigateurs mobiles imposent des contraintes supplémentaires — iOS Safari en particulier. Testez sur de vrais appareils, pas seulement sur des simulateurs.
Conclusion
L’API Web Audio fournit un contrôle audio puissant et de bas niveau via un modèle basé sur des graphes. Commencez avec un seul AudioContext, respectez les politiques d’autoplay et utilisez des modèles modernes : AudioBufferSourceNode pour les échantillons, OscillatorNode pour la synthèse et AudioWorklet pour le traitement personnalisé. Considérez le routage vers les périphériques de sortie comme fortement contraint et dépendant de la plateforme. Détectez les fonctionnalités systématiquement et développez en tenant compte des contraintes réellement imposées par les navigateurs.
FAQ
Les navigateurs bloquent la création d'AudioContext jusqu'à ce qu'une interaction utilisateur se produise. Votre environnement de développement peut avoir des paramètres plus permissifs. Vérifiez toujours audioContext.state et appelez resume() à l'intérieur d'un gestionnaire de clic ou de touche avant de tenter la lecture. Cette politique d'autoplay s'applique universellement dans les navigateurs modernes.
Non. Les instances d'AudioBufferSourceNode sont à usage unique par conception. Après avoir appelé start(), le nœud ne peut pas être redémarré. Créez un nouveau nœud source pour chaque lecture tout en réutilisant l'AudioBuffer sous-jacent. Les nœuds sources sont légers, donc ce modèle a un impact minimal sur les performances.
Utilisez AudioContext.setSinkId(), mais considérez-le comme fortement contraint. Il nécessite HTTPS, une permission utilisateur et des politiques permissives, et n'est pas pris en charge sur Safari ou iOS. Détectez toujours les fonctionnalités, gérez les échecs avec élégance et informez les utilisateurs lorsque la sélection de périphérique n'est pas disponible.
ScriptProcessorNode est obsolète et s'exécute sur le thread principal, provoquant des glitchs audio lors de traitements intensifs. AudioWorklet s'exécute dans un thread de rendu audio dédié avec un timing déterministe, le rendant adapté à la manipulation audio en temps réel. Utilisez toujours AudioWorklet pour le traitement personnalisé dans les nouveaux projets.
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.