El sonido de aviso que dejó de sonar sin ningún error
Tras recibir unos catorce mensajes seguidos en la bandeja, el reporte fue claro: el sonido de aviso había sonado una o dos veces, no catorce. No había errores en la consola, el código que lo disparaba se ejecutaba cada vez, y en las pruebas sueltas siempre sonaba. El problema solo aparecía con uso real y sostenido.
Por qué sintetizar el sonido
Para un aviso breve, la Web Audio API permite generar un tono con un oscilador y una envolvente de volumen en pocas líneas. No hay que descargar un archivo de audio, el sonido está disponible al instante y se puede ajustar su tono y duración desde el código. Es una buena elección para notificaciones.
La causa: un límite de contextos
Todo sonido de la Web Audio API vive dentro de un
AudioContext. La implementación original creaba un contexto
nuevo en cada aviso. Chrome limita la cantidad de contextos de audio por
página a alrededor de seis. Los primeros avisos funcionan; a partir del
límite, el constructor lanza una excepción.
Como la función de sonido estaba envuelta en un bloque que capturaba errores sin registrarlos, pensado para que un fallo de audio no afectara al resto de la app, la excepción desaparecía. El sonido dejaba de sonar y nada lo indicaba.
La corrección: un solo contexto
El contexto se crea una sola vez, a nivel de módulo, y se reutiliza para todos los avisos. Cada aviso crea su oscilador y su nodo de volumen, que son baratos y se descartan solos al terminar.
let audio = null; // uno solo para toda la página
function sonar() {
const AC = window.AudioContext || window.webkitAudioContext;
if (!audio) audio = new AC();
if (audio.state === 'suspended') audio.resume();
// oscilador + ganancia, start y stop
} El detalle del estado suspendido
La llamada a resume() no es decorativa. Un contexto de audio
puede quedar suspendido por dos razones. La primera es la política de
reproducción automática: los navegadores no permiten producir sonido
antes de que la persona interactúe con la página, y un contexto creado
antes de esa interacción nace suspendido. La segunda es el segundo plano:
al volver de otra pestaña, el contexto puede estar suspendido de nuevo.
Reanudarlo antes de cada sonido cubre ambos casos.
En la práctica, conviene que el primer uso del audio ocurra después de algún clic de la persona en la app, por ejemplo al entrar a la bandeja, para que el contexto quede habilitado desde temprano.
Dos lecciones generales
Los recursos del navegador también se agotan
Se suele pensar en fugas de memoria, pero hay otros recursos con límites duros: contextos de audio, contextos WebGL, conexiones simultáneas por dominio, workers. Crear uno por evento en lugar de reutilizarlo funciona en la demo y falla con el uso real.
Capturar errores en silencio esconde bugs
Evitar que un fallo secundario rompa la app es correcto. Hacerlo sin dejar rastro convierte un error de una línea en una investigación. Aunque el fallo no deba mostrarse al usuario, debería registrarse al menos en la consola, o contarse, para que alguien pueda notar que ocurre.
Cómo probarlo
- Disparar el sonido veinte veces seguidas y contar cuántas suenan.
- Probar con la pestaña en segundo plano y al volver a ella.
- Probar recargando la página sin hacer ningún clic antes del primer aviso.