Reproducción de Sonidos con la Web Audio API
Quieres reproducir audio en el navegador con control preciso—programación, efectos, síntesis—pero el elemento <audio> se queda corto. La Web Audio API resuelve esto, sin embargo, su documentación mezcla patrones obsoletos con las mejores prácticas actuales. Este artículo aclara el enfoque moderno para la reproducción de sonido con la Web Audio API, cubriendo los fundamentos de AudioContext, nodos de origen y el modelo de grafo de audio sin la carga del código heredado.
Puntos Clave
- Crea un único
AudioContextpor aplicación y siempre maneja el estado suspendido conresume()después de la interacción del usuario - Usa
AudioBufferSourceNodepara archivos de audio precargados yOscillatorNodepara tonos sintetizados—ambos son nodos de un solo uso - Evita el obsoleto
ScriptProcessorNode; usaAudioWorkletpara procesamiento de audio personalizado - Trata el enrutamiento de dispositivos de salida mediante
setSinkId()como experimental con soporte incompleto en navegadores
Comprendiendo los Fundamentos de AudioContext
Toda aplicación de Web Audio comienza con un AudioContext. Este objeto gestiona todas las operaciones de audio y proporciona el reloj para la programación temporal. Piensa en él como el entorno de ejecución para tu grafo de audio.
const audioContext = new AudioContext()
Una restricción crítica: los navegadores suspenden los nuevos contextos hasta que ocurra un gesto del usuario. Siempre verifica audioContext.state y llama a resume() desde un manejador de clic o pulsación de tecla:
button.addEventListener('click', async () => {
if (audioContext.state === 'suspended') {
await audioContext.resume()
}
// Ahora es seguro reproducir audio
})
En la mayoría de las aplicaciones, un único AudioContext es suficiente y recomendado. Crear múltiples contextos aumenta el uso de recursos y dificulta la gestión de temporización y sincronización. (Ver: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext)
El Modelo de Grafo de Audio
Web Audio utiliza un grafo dirigido de nodos. El audio fluye desde los nodos de origen, a través de nodos de procesamiento, hasta un destino. El audioContext.destination representa la salida predeterminada—típicamente los altavoces del usuario.
Los nodos se conectan mediante el método connect():
sourceNode.connect(gainNode)
gainNode.connect(audioContext.destination)
Este diseño modular te permite insertar filtros, controles de ganancia o analizadores en cualquier punto de la cadena. Desconecta nodos con disconnect() al reconfigurar el grafo.
Nodos de Origen para Reproducción de Sonido
Dos tipos de origen manejan la mayoría de los escenarios de reproducción.
AudioBufferSourceNode
Para archivos de audio precargados, decodifica los datos en un AudioBuffer, luego reprodúcelo a través de 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()
Cada AudioBufferSourceNode se reproduce una vez. Crea un nuevo nodo de origen para cada reproducción—son ligeros. El AudioBuffer subyacente es reutilizable.
Ten en cuenta que aunque decodeAudioData() soporta promesas en navegadores modernos, las versiones antiguas de Safari requerían la forma de callback. Esto es principalmente una preocupación heredada, pero aún relevante para compatibilidad a largo plazo.
OscillatorNode
Para tonos sintetizados, usa 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)
Los osciladores también se reproducen una vez. Programa los tiempos de start() y stop() usando audioContext.currentTime para una reproducción precisa a nivel de muestra.
Discover how at OpenReplay.com.
AudioWorklet vs ScriptProcessor
Para procesamiento de audio personalizado, evita ScriptProcessorNode. Está obsoleto, se ejecuta en el hilo principal y causa interrupciones de audio bajo carga.
AudioWorklet es el reemplazo moderno. Se ejecuta en un hilo dedicado de renderizado de audio con temporización determinista:
// processor.js
class MyProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
// Procesa el audio aquí
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 es compatible con todos los navegadores modernos actualizados automáticamente. Las principales restricciones tienden a estar relacionadas con el entorno (contexto seguro, empaquetado, configuración cross-origin) en lugar de falta de soporte de la API.
Restricciones de Enrutamiento de Salida de Web Audio
Las capacidades de enrutamiento de salida de la API son limitadas y dependen del navegador. Por defecto, el audio se enruta a audioContext.destination, que se mapea al dispositivo de salida predeterminado del sistema.
AudioContext.setSinkId() permite seleccionar dispositivos de salida específicos, pero trata esto como experimental y altamente restringido. Requiere un contexto seguro (HTTPS), permiso del usuario y encabezados de política de permisos apropiados. En la práctica, no está disponible en Safari o iOS y no se debe confiar en él para el cambio de altavoces multiplataforma.
Para aplicaciones que requieren selección de dispositivo de salida, detecta el soporte explícitamente:
if (typeof audioContext.setSinkId === 'function') {
// La presencia no garantiza usabilidad; los permisos y la política aún pueden bloquearlo
}
Incluso cuando el método existe, las llamadas pueden fallar debido a limitaciones de política o plataforma. Construye alternativas y comunica estas restricciones claramente a los usuarios.
Consideraciones Prácticas
Las políticas de reproducción automática bloquean el audio hasta la interacción del usuario. Diseña tu interfaz de usuario para requerir un clic antes de inicializar la reproducción.
Las restricciones CORS se aplican al obtener archivos de audio de origen cruzado. Asegura encabezados apropiados u hospeda los archivos en el mismo origen.
La gestión de memoria importa para aplicaciones con uso intensivo de búferes. Desreferencia objetos AudioBuffer no utilizados y desconecta nodos que hayas terminado de usar.
Los navegadores móviles imponen restricciones adicionales—iOS Safari en particular. Prueba en dispositivos reales, no solo en simuladores.
Conclusión
La Web Audio API proporciona control de audio potente y de bajo nivel a través de un modelo basado en grafos. Comienza con un único AudioContext, respeta las políticas de reproducción automática y usa patrones modernos: AudioBufferSourceNode para muestras, OscillatorNode para síntesis y AudioWorklet para procesamiento personalizado. Trata el enrutamiento de dispositivos de salida como altamente restringido y dependiente de la plataforma. Detecta características en todo momento y construye para las restricciones que los navegadores realmente imponen.
Preguntas Frecuentes
Los navegadores bloquean la creación de AudioContext hasta que ocurra la interacción del usuario. Tu entorno de desarrollo puede tener configuraciones más permisivas. Siempre verifica audioContext.state y llama a resume() dentro de un manejador de clic o pulsación de tecla antes de intentar la reproducción. Esta política de reproducción automática se aplica universalmente en todos los navegadores modernos.
No. Las instancias de AudioBufferSourceNode son de un solo uso por diseño. Después de llamar a start(), el nodo no puede reiniciarse. Crea un nuevo nodo de origen para cada reproducción mientras reutilizas el AudioBuffer subyacente. Los nodos de origen son ligeros, por lo que este patrón tiene un impacto mínimo en el rendimiento.
Usa AudioContext.setSinkId(), pero trátalo como altamente restringido. Requiere HTTPS, permiso del usuario y políticas permisivas, y no es compatible con Safari o iOS. Siempre detecta características, maneja fallos con elegancia e informa a los usuarios cuando la selección de dispositivos no está disponible.
ScriptProcessorNode está obsoleto y se ejecuta en el hilo principal, causando interrupciones de audio durante el procesamiento intensivo. AudioWorklet se ejecuta en un hilo dedicado de renderizado de audio con temporización determinista, haciéndolo adecuado para manipulación de audio en tiempo real. Siempre usa AudioWorklet para procesamiento personalizado en proyectos nuevos.
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.