El elemento AUDIO de HTML5 hace posible que los desarrolladores web puedan incorporar audio a sus aplicaciones.
En este artículo vamos a echar un vistazo a algunas de las buenas prácticas que deben seguirse para utilizar la etiqueta <audio> en las aplicaciones web, y también voy a dar una serie de consejos útiles y enseñanzas aprendidas de sitios web del mundo real.
Añadir audio a una página web
El primer paso, antes de nada, es añadir el elemento audio a la página. Esto lo podemos hacer simplemente declarando la etiqueta <audio> en el código HTML, instanciando un nuevo elemento de audio en el código de Javascript, o bien embebiendo el propio stream de audio dentro de la página:
<audio src="audio/sample.mp3" autoplay>
</audio>
var audio = document.createElement("audio");
if (audio != null && audio.canPlayType && audio.canPlayType("audio/mpeg"))
{
audio.src = "audio/sample.mp3";
audio.play();
}
<audio src="data:audio/mpeg,ID3%02%00%00%00%00%..." autoplay>
</audio>
La primera opción nos permite inicializar los componentes de audio durante la carga de la página. La segunda es más flexible y facilita la gestión del flujo de red, puesto que difiere la carga del archivo de audio a un momento concreto durante el ciclo de vida de la aplicación. La tercera alternativa (menos recomendable) consiste en embeber los propios archivos de audio como direcciones en formato de datos-uri en la propia página, lo que reduce el número de peticiones al servidor.
Podemos comprobar que el elemento de audio generado por Javascript se puede reproducir aunque no se haya añadido aún al árbol del DOM (como sucede en módulo de código anterior). No obstante, al añadir el elemento de audio a la página nos va a permitir mostrar la barra de control por defecto:
Aunque no hablaré de eso en este artículo, podemos utilizar más de un formato de archivo de audio. Además, si los archivos de audio los tenemos alojados en nuestro servidor, tenemos que acordarnos de registrar el tipo MIME para los archivos MP3 ("audio/mpeg") en el propio servidor. Aquí, por ejemplo, se puede ver la configuración Internet Information Services (IIS).
Precarga del audio antes de reproducir
Una vez tenemos el elemento audio, podemos decidir qué estrategia de precarga es la mejor. La especificación <audio> de HTML5 describe la propiedad preload con tres valores posibles:- "none": indica al agente de usuario que o bien el autor no espera que el usuario necesite el archivo de audio o que el servidor quiere reducir al mínimo todo tráfico innecesario. En casos como un blog tipo podcast con un archivo de audio en cada artículo, esta opción funciona bastante bien, puesto que reduce el ancho de banda consumido durante la precarga. Cuando el usuario reproduce el archivo (ya sea mediante los controles visuales por defecto o activando los métodos load() o play() de Javascript), el navegador procederá a descargar el stream de audio.
- "metadata": informa al agente de usuario que el autor no espera que el usuario necesite el archivo de audio, pero que es adecuado conocer sus metadatos (dimensiones, duración, etc.) Se recomienda esta opción si estamos diseñando un control de reproductor de audio y necesitamos información básica sobre la pista de audio a reproducir pero no tenemos que reproducirla en ese momento.
- "auto": indica al agente de usuario de que puede anteponer las necesidades del usuario sin comprometer al servidor, hasta e incluyendo- una descarga optimista del recurso completo. En otras palabras: si se puede, se descarga por adelantado. Si estamos creando un juego, esta estrategia es seguramente la mejor, ya que nos permite tener precargados los archivos de audio antes de que empiece el juego realmente.
El impacto sobre la red de estas tres opciones se puede predecir ejecutando esta página y utilizando las Herramientas de Desarrollo F12 (pestaña Network). Para fines de depuración, podemos simular nuevas llamadas y desactivar la cache local marcando la opción del menú "Always refresh from sever".
preload=none:
preload=metadata:
preload=auto:
Aunque esta propiedad es sumamente útil en la fase de inicialización, puede que necesitemos saber también en qué momento el navegador ha terminado de descargar el archivo de audio y ya podemos reproducirlo. Esto lo podemos saber escuchando el evento "canplaythrough"; a este evento lo llama el Agente de Usuario una vez que considera que si en ese momento se empezara a reproducir el archivo, el sonido podría empezar a escucharse a la tasa de reproducción prevista hasta su finalización, sin tener que detenerse para volver a cargar el buffer.
var audio = document.createElement("audio");
audio.src = "audio/sample.mp3";
audio.addEventListener("canplaythrough", function () {
alert('The file is loaded and ready to play!');
}, false);
Bucles
Otra petición muy frecuente en desarrollos con audio es la posibilidad de reproducción infinita (o "en bucle") de la pista de audio. Con la etiqueta <audio> de HTML5 podemos utilizar la propiedad "loop" para eso: este parámetro nos permite reproducir de forma continua el archivo, o hasta que el usuario o la aplicación activan el control pause().
<audio src="audio/sample.mp3" autoplay loop>
</audio>
Otra forma de reproducir en bucle los archivos de audio consiste en llamar desde programa al método play() cuando finaliza la pista de sonido; de esta manera podemos también controlar el tiempo de retardo entre una reproducción y la siguiente.
var audio = document.createElement("audio");
audio.src = "piano/3C.mp3";
audio.addEventListener('ended', function () {
// Wait 500 milliseconds before next loop
setTimeout(function () { audio.play(); }, 500);
}, false);
audio.play();
Interesa saber que cualquier llamada play() que se ejecute sobre el elemento de audio antes de llegar al final de la pista no tiene efecto alguno. Si tienes interés en "cancelar y recuperar" la reproducción de audio en un punto concreto, necesitarás resetear el valor de currentTime.
var audio = null;
audio = document.createElement("audio");
audio.src = "piano/3C.mp3";
audio.addEventListener('ended', function () {
audio.play();
}, false);
function play() {
audio.play();
}
function restart() {
audio.currentTime = 0;
audio.play();
}
Múltiples etiquetas de audio
Si en tu aplicación necesitas reproducir varias veces el mismo archivo de audio de forma simultánea (o sea, en forma de sonidos superpuestos), lo puedes conseguir insertando varias etiquetas de audio que apunten al mismo archivo. Lógicamente, esta misma solución funciona también para reproducir al mismo tiempo varios archivos distintos. Como he comentado antes, puedes añadirlos por programa o instanciarlos dentro del código HTML.Este ejemplo de código nos muestra cómo se cargan y reproducen múltiples archivos de audio desde el código HTML. Los archivos de ejemplo tienen todos la misma duración. Al final de la reproducción vuelven a reproducirse desde el inicio. Si se ejecuta este código en Internet Explorer 9, podremos ver que se sincronizan de manera automática durante cada una de las vueltas del bucle. También podrás comprobar que la combinación de estos cinco sonidos devuelve un sonido final que se parece mucho al archivo de audio utilizado en la demostración anterior ("sample.mp3").
<body>
<audio src="audio/Bass.mp3" autoplay loop>
</audio>
<audio src="audio/Drum.mp3" autoplay loop>
</audio>
<audio src="audio/Crunch.mp3" autoplay loop>
</audio>
<audio src="audio/Guitar.mp3" autoplay loop>
</audio>
<audio src="audio/Pizzicato.mp3" autoplay loop>
</audio>
</body>
Aunque la solución es muy sencilla y funciona bien, en la mayoría de casos los desarrolladores prefieren crear sus clips de audio por programación. En el ejemplo de código siguiente se muestra cómo se añaden dinámicamente tres archivos de audio por programa. Si se reproducen a la vez, nos devuelven el acorde MI Mayor al piano.
AddNote("3C");
AddNote("3E");
AddNote("3G");
function AddNote(name) {
var audio = document.createElement("audio");
audio.src = "piano/" + name + ".mp3";
audio.autoplay = true;
}
Este modelo de código funciona en cualquier navegador y nos permite diseñar escenarios de audio muy atractivos
Es muy importante tener en cuenta que a medida que una aplicación o juego se hacen más complicados, podríamos encontrarnos con dos límites en un momento dado: el número de elementos de audio que se pueden precargar desde la misma página y el número de elementos de audio que el sistema es capaz de reproducir de forma simultánea.
Estos límites dependen del navegador y de la capacidad del propio equipo. Basándome en mi experiencia personal, Internet Explorer 9 puede manejar decenas de elementos de audio a la vez sin problemas. Otros navegadores no llegan a tanto, y podemos encontrar ejemplos evidentes de retardos y distorsiones cuando se reproducen varios archivos en bucle.
Estrategias de sincronización
Dependiendo de las características de la red, debemos siempre tener mucho cuidado con la latencia o retardo que se aprecia entre el momento en que se añade la etiqueta y el momento en que se recupera el contenido de audio del servidor y queda listo para su reproducción. En concreto, si se trata de manejar varios archivos a la vez, todos ellos deberían estar listos para la reproducción antes o después. Aquí, por ejemplo, vemos una captura con los tres archivos utilizados antes, cargándolos desde la propia máquina (localhost) .
Como podemos ver en la columna Timings, cada archivo puede que esté disponible en un momento distinto.
Una estrategia de sincronización muy utilizada consiste en precargar todos los archivos. Una vez que todos ellos están disponibles, se meten rápidamente en un bucle para empezar a escucharlos.
var audios = [];
var loading = 0;
AddNote("2C");
AddNote("2E");
AddNote("2G");
AddNote("3C");
AddNote("3E");
AddNote("3G");
AddNote("4C");
function AddNote(name) {
loading++;
var audio = document.createElement("audio");
audio.loop = true;
audio.addEventListener("canplaythrough", function () {
loading--;
if (loading == 0) // All files are preloaded
StartPlayingAll();
}, false);
audio.src = "piano/" + name + ".mp3";
audios.push(audio);
}
function StartPlayingAll() {
for (var i = 0; i < audios.length; i++)
audios[i].play();
}
Vamos ahora a juntarlo todo. En la demo siguiente vamos a simular un concierto de piano Frère Jacques (también llamado Hermano Juan, Brother John o Fra Martino). La página empieza cargando todas las notas y muestra el progreso a medida que se van precargando en el cliente. Cuando ya tenemos todas, la canción empieza y se mantiene en un bucle de reproducción
Audio en sitios web reales
Ahora que ya hemos podido ver los principios básicos del manejo de múltiples archivos de audio, quisiera recomendar unos cuantos sitios Web como ejemplo de la aplicación de estas buenas prácticas con la etiqueta <audio>.Pirates Love Daises: www.pirateslovedaisies.com
En otro artículo del blog hablaba de Pirates Love Daises, un excelente juego escrito en HTML5 por Grant Skinner. Además de su calidad como juego y sus atractivos efectos visuales, el equipo de Gran ha desarrollado también una sofisticada biblioteca de audio que reproduce algunos temas a lo largo del desarrollo del juego. La lógica principal queda encapsulada dentro de la clase AudioManager. Como proponía en uno de los apartados, antes de empezar a jugar el sitio precarga todos los archivos de audio y va mostrando el progreso en la página inicial de carga. El sitio web tiene también en cuenta los timeouts y errores de la red al descargar un archivo.
addAudioChannel:function(b,a,f){
var h=document.createElement("audio");
if(f!=true){
this.currAsset=h;
this.timeoutId=setTimeout($.proxy(this,"handleAudioTimeout"),e.AUDIO_TIMEOUT);
h.addEventListener("canplaythrough",$.proxy(this,"handleAudioComplete"),false);
h.addEventListener("error",$.proxy(this,"handleAudioError"),false)
}
h.setAttribute("id",a);
h.setAttribute("preload","auto");
$("<source>").attr("src",b).appendTo(h);
$("<source>").attr("src",b.split(".mp3")[0]+".ogg").appendTo(h);
document.body.appendChild(h)
}
,handleAudioComplete:function(b){
if(LoadedAssets.getAsset(b.target.id)!=true){
LoadedAssets.addAsset(b.target.id,true);
clearTimeout(this.timeoutId);
this.calculatePercentLoaded(true)
}
}
,handleAudioError:function(b){
trace("Error Loading Audio:",b.target.id);
LoadedAssets.addAsset(b.target.id,true);
clearTimeout(this.timeoutId);
this.calculatePercentLoaded(true)
}
,handleAudioTimeout:function(){
trace("Audio Timed Out:",this.currAsset.id);
LoadedAssets.addAsset(this.currAsset.id,true);
this.calculatePercentLoaded(true)
}
Grant está trabajando en estos momentos en un proyecto de Biblioteca de Sonidos que permitirá a los desarrolladores utilizar su propia lógica para el reproductor de audio con cualquier otra aplicación. ¡Esperemos verlo pronto!
Firework (de Mike Tompkins): www.beautyoftheweb.com/firework
La demo Firework es especialmente interesante ya que nos permite interactuar con algunas pistas de audio a la vez, dinámicamente, cambiando el volumen de cada una de ellas. Todavía va más lejos, ya que podemos interactuar también con cada canal de audio, la interfaz reacciona dinámicamente a distintas entradas o configuraciones.
Esta vez las etiquetas de audio se declaran dentro del código HTML (son 6 pistas). El progreso se controla por programa, escuchando el evento canplaythrough. Cuando todos los archivos de audio están disponibles se ejecuta un bucle que va recorriendo la lista y los reproduce.
video.addEventListener('canplaythrough', onCanPlayAudio, false);
for (var i = 0; i < 5; i++) {
var aud = document.getElementById("aud" + i);
targetVolumes.push(0);
aud.volume = 0;
audioTags.push({
"tag": aud,
"ready": false
});
aud.addEventListener('canplaythrough', onCanPlayAudio, false);
}
// Set audio/video tracks
document.getElementById("tompkins").src = MediaHelper.GetVideoUrl("Firework_3");
for (var i = 0; i < audioTracks.length; i++) {
document.getElementById("aud" + i).src = MediaHelper.GetAudioUrl(audioTracks[i]);
}
En este caso los desarrolladores han optado por empezar con el volumen fijado a cero y aumentarlo dinámicamente a 1 en cuanto puede empezar la reproducción. Dependiendo de la calidad de tu tarjeta de audio y los drivers, este truco reduce la probabilidad de escuchar un ruido "toc" al principio, cuando empieza a oírse el sonido.
BeatKeep: www.beatkeep.net
El último ejemplo que os muestro es posiblemente el más complicado de todos. En este caso podemos crear nuestras propias canciones utilizando un generador de pulsos y reproduciendo varias pistas de audio en bucle. En esta aplicación lo esencial es llegar a tener una sincronización perfecta de los canales de audio, y un sistema ágil de buffering cargar varios archivos.
El generador de pulsos nos aporta un control total sobre el tempo y la firma de tiempo. Con la ayuda de una sofisticada lógica de timers y un modelo de acoplamiento, el resultado final es una experiencia realmente agradable.
Conclusiones
Te recomiendo que pruebes todos estos ejemplos y aplicaciones que propongo en el artículo utilizando Internet Explorer 9 u otros navegadores y que nos contéis cómo ha sido la experiencia. Puedes descargar todo el código de ejemplo utilizado en el artículo desde aquí.Si quieres informarte más sobre los controles de audio y vídeo, te recomiendo que veas la sesión de media hora del MIX titulada "5 cosas que debes saber para utilizar <audio> y <video> hoy mismo" o leer en MSDN estos interesantes artículos.
Quiero agradecer a DoubleDominant por los archivos de audio utilizados en este artículo, y a Grant Skinner y Archetype por sus excelentes producciones en HTML5.
Giorgio Sardo
Evangelista Técnico Senior | HTML5 e Internet Explorer