Son sobradamente conocidas la posibilidades que ofrece HTML5 en el campo multimedia y la facilidad con la que podemos interactuar con objetos de audio y vídeo a través del API de JavaScript de HTML5. Hoy en día existen numerosos reproductores de audio y video que explotan dicha característica HTML5; y junto con jQuery y CSS permiten incluir en nuestros proyecto de forma espectacular implementaciones multimedia.

Sin embargo lo que pretendo con este post es mostrar mediante un ejemplo el patrón Modelo Vista Controlador aplicado a JavaScript/jQuery y creo que no se me ocurre una manera mejor y más gráfica de explicarlo que con un reproductor multimedia. También aplicaremos Programación Orientada a Objetos en entorno JavaScript. Que nadie se asuste, es mucho más sencillo de lo que parece.

Podeís acceder al código del reproductor desde el botón inferior. Para hacer un poco más comprensible el código he utilizado también jQuery.

Audio Player con JavaScript

Antes de que se me olvide. La pista de audio que utilizo la he extraido de Jamendo (https://www.jamendo.com), una magnífica plataforma para la distribución musical y la pista de audio es un fragmento del disco «S.L.A.S.H.» del grupo Other Noises. Os recomiendo que lo escucheis: https://www.jamendo.com/en/artist/439311/other-noises

Programación Orientada a Objetos en JS

La Programación Orientada a Objetos puede considerarse como el diseño de software a través de objetos que interactúan entre sí de una forma más cercana al «mundo real»; a diferencia de un punto de vista tradicional en el que un programa puede considerarse como un conjunto de funciones o simplemente como una lista de instrucciones y condicionales. Su principal ventaja es que es mucho más fácil de comprender dado que nuestra mente comprende mucho mejor el concepto de «interacción entre objetos» y por lo tanto es más sencillo de mantener y desarrollar. Por otro lado la existencia de «objetos» favorece la modularidad y por lo tanto poder emplear el mismo código en otros desarrollos.

JavaScript hasta hace años era tratado como un «pariente pobre» en programación, recluido en su mayor parte en el desarrollo de aplicaciones web. Sin embargo en la actualidad esta disfrutando de un importante auge, aprovechándose de las posibilidades que ofrece en la Programación Orientada a Objetos. De hecho existen múltiples modos de crear y trabajar con clases y objetos en JS. He preferido utilizar una de las más simples. Si te interesa seguir profundizando en la Programación Orientada a Objetos en JS recomiendo este libro: Professional JavaScript for Web Developers de Nicholas C. Zakas.

El patrón Modelo Vista Controlador

Primero un poco de teoría y luego nos ponemos manos a la obra. Según Wikipedia: «El pátrón Modelo Vista Controlador es un patrón de arquitectura de software que separa los datos y la lógica de negocio de una aplicación de la interfaz de usuario y el módulo encargado de gestionar los eventos y las comunicaciones. Para ello MVC propone la construcción de tres componentes distintos que son el modelo, la vista y el controlador, es decir, por un lado define componentes para la representación de la información, y por otro lado para la interacción del usuario.»

Así pues vamos a necesitar tres componentes en nuestra programación claramente diferenciados: el Modelo, que será quien realice el verdadero trabajo de nuestro reproductor multimedia, reproduciendo la pista de audio, deteniendo la reproducción, saltando a una posición determinada o subiendo y bajando el volumen. Como un motor vaya. La Vista que será quien informe al usuario en todo momento del nombre de la pista de audio, su duración, la posición en la que se encuentra la pista de audio y en definitiva de interactuar con el usuario. Y por último el Controlador, el cerebro que pone en comunicación entre sí el resto de componentes y recibe las peticiones del usuario.

Las ventajas que nos ofrece el patrón MVC principalmente son las siguientes:

  • Orden a la hora de trabajar. En lugar de tener un código «espaguetti» es mucho más sencillo abordar cambios de diseño, o incluso pasarle un código a otro compañero cuando los componentes se encuentran desagregados y claramente separadas sus funciones.
  • Separación de las funciones. De este modo podríamos cambiar partes de un componente sin tener que realizar cambios a todo el código. Por ejemplo, si quisieramos cambiar la presentación de la Vista por que añadimos más botones o cambiamos su representación no teńdría por qué afectar al resto de componentes y tendríamos que tocar menos código.
  • Reutilización de código. Podemos tomar componentes separados para aplicarlos a otros desarrollos adaptando lo que ya tenemos.

El reproductor de audio

Comencemos pues con el ejemplo. Por supuesto todo el código está disponible para mostrar su funcionamiento y simplemente me limitaré a comentar el código y explicar el funcionamiento de cada módulo por separado. En sí la apariencia final del reproductor no será demasiado espectacular dado que simplemente utilizaremos HTML en la parte de la vista y nada de CSS. En un próximo post partiremos de este ejemplo para darle una apariencia más atractiva con CSS3, WebFonts y algo de animación.

El Modelo

Como hemos dicho con anterioridad el Modelo es el motor de nuestro desarrollo, quien hace posible el hecho de que podamos escuchar el audio, detenerlo, saltar atrás etc. Como veremos es quien se comunica directamente con la API de HTML5. De igual modo cargará el fichero de audio y creará el objeto Audio.

function audioEngine(audioList) {

En primer lugar definimos la clase o función del Modelo. En este caso le damos el nombre de audioEngine y le pasamos como parámetro la lista de ficheros de audio a cargar, en formato JSON. En nuestro caso sólo uno. Se podría desarrollar a partir de este código uno que cargara una lista completa. 😉

  // Properties
  this.audioList = $.parseJSON(JSON.stringify(audioList));
  this.audio = {};
  this.title = null;
  this.timeStep = 5;
  this.stepVolume = 0.1;

A continuación convertimos la lista de audio recibida en JSON para poder trabajar con él y definimos algunas propiedades del objeto básicas como timeStep que es el valor en segundos de los saltos con los botones Back y Forward.

   // Engine methods
  this.init = function() {
    var audioData = this.audioList;
    this.audio = new Audio();
    this.audio.src = audioData.file;
    this.title = audioData.title;
    this.audio.load();
    console.log("Loaded audio '" + this.title + "'");
  }

El primer método o función del objeto que nos encontramos es el que lo pone en marcha. Una vez recibida la orden de arrancar carga el fichero de audio y crea el objeto de audio. Asigna también el título del fichero de audio y muestra un mensaje en nuestra consola una vez cargado.

A continuación el resto de métodos simplemente interactúan con el objeto de audio cuando reciba la orden de reproducirlo, pausarlo, detenerlo, avanzar o retroceder, subir o bajar el volumen, etc.

   this.play = function() {
    this.audio.play();
  }
 
  this.pause = function() {
    this.audio.pause();
  }
 
  this.stop = function() {
    this.audio.pause();
    this.audio.currentTime = 0;
  }
 
  this.forward = function() {
    this.audio.currentTime += this.timeStep;
  }    
 
  this.back = function() {
    this.audio.currentTime -= this.timeStep;
  }    
 
  this.volumeUp = function() {
    this.audio.volume += (this.audio.volume <= (1 - this.stepVolume)) ? this.stepVolume: null;     this.audio.volume = this.audio.volume.toFixed(1);   }   this.volumeDown = function() {     this.audio.volume -= (this.audio.volume >= this.stepVolume) ? this.stepVolume : null;
    this.audio.volume = this.audio.volume.toFixed(1);
  }
}

La Vista

Por otro lado, y junto con el código HTML, la Vista es la parte visible de la aplicación y más cercana al usuario. Es la encargada de recibir las instrucciones del controlador de actualizarse e informar de los cambios en la aplicación al usuario. Comencemos su análisis.

function audioDisplay(audioObject, title) {
 
  // Hooks
  var currentTimeShow = $("#currentTime");
  var durationShow = $("#duration");
  var positionSlider = $("#position");
  var titleAudio = $("#title");
  var volumeSlider = $("#volume");
  var self = this;

En las anteriores lineas solo identificamos los componentes HTML con los que podremos interactuar. El indicar de tiempo de reproducción, la duración total, el slider de posición y el de volumen.

A continuación las dos propiedades con las que interactuaremos en la Vista serán el objeto de audio para recabar información a su duración, si se encuentra reproduciendo audio, si ha finalizado, etc.; y el título de la pista para incluirlo en el código HTML.

  // Properties
  this.audioObject = audioObject;
  this.title = title;

Lo que tenemos ahora son un conjunto de listeners que analizan el objeto de audio que ha remitido el Controlador para actualizar la vista y el conjunto de métodos que individualmente actualizan cada elemento HTML por separado. Por defecto hemos configurado que la Vista se actualice automáticamente una vez por segundo.

  // Events Listeners while playing
this.audioObject.addEventListener("playing", function() {
setInterval(function(){
self.updatePlayer();
}, 1000);
});
 
this.audioObject.addEventListener("loadedmetadata", function() {
self.updatePlayer();
self.showDuration();
self.showTitle();
self.setFinalPositionSlider();
});
 
this.audioObject.addEventListener("volumechange", function() {
volumeSlider.val(self.audioObject.volume*10).val();
});
 
this.audioObject.addEventListener("ended", function() {
self.audioObject.currentTime = 0;
});
 
// Display methods
this.updatePlayer = function() {
var currentTime = parseTime(self.audioObject.currentTime);
currentTimeShow.html(currentTime);
self.updatePositionSlider();
}
 
this.updatePositionSlider = function() {
var currentTime = Math.round(self.audioObject.currentTime);
positionSlider.val(currentTime).val();
}
 
this.showDuration = function() {
var duration = parseTime(self.audioObject.duration)
durationShow.html(duration);
}
 
this.showTitle = function() {
titleAudio.html(self.title);
}
 
this.setFinalPositionSlider = function() {
positionSlider.attr("max", self.audioObject.duration);
}

También existen funciones auxiliares que simplemente se utilizan para fines muy sencillos, como convertir la duración en segundos al formato 00:00.

   function parseTime(time) {
var currentTime = new Date(time * 1000);
var min = currentTime.getMinutes();
var sec = currentTime.getSeconds();
min = (min < 10) ? "0" + min : min;
sec = (sec < 10) ? "0" + sec : sec;
return  min + ':' + sec;
}

El Controlador

Como hemos dicho con anterioridad el Controlador es el componente que recibirá las instrucciones del usuario y ordenará al Modelo que ejecute las acciones pertinentes. Lo dejamos para el final pues es el elemento que pone en comunicación todos los componentes.

Así pues en primer lugar lo que nos encontramos es un conjunto de variables que corresponden con objetos jQuery. Estos objetos no son más que los botones y sliders de nuestra aplicación desde los cuales puede recibir una instrucción del usuario.

 function audioControls(audioList) {
 
// Hooks
var playButton = $("#play");
var pauseButton = $("#pause");
var stopButton = $("#stop");
var backButton = $("#back");
var forwardButton = $("#forward");
var volumeUpButton = $("#volumeUp");
var volumeDownButton = $("#volumeDown");
var volumeSlider = $("#volume");
var muteButton = $("#mute");
var positionSlider = $("#position");

La línea siguiente quizás desconcierte un poco y puede que sea la parte más complicada. En Programación Orientada a Objetos cuando hacemos referencia a this estamos haciendo referencia a una propiedad o metodo (función) de la propia clase. Sin embargo en las siguientes lineas observaremos que en las funciones que definiremos a continuación no tendremos posibilidad a dicha variable, dado que se está ejecutando en un ámbito distinto.

Para salvar esta dificultad lo que hacemos el crear una nueva variable con un nombre distinto y a la que sí podremos acceder desde dicha variables. En caso contrario no podríamos acceder al Modelo y al objeto de audio para su manipulación.

   var self = this;

En las siguientes líneas es cuando instanciamos el Modelo y cargamos el fichero de audio.

   // Properties
this.engine = new audioEngine(audioList);
this.engine.init();

¡Ya tenemos nuestro motor en marcha! Sin embargo si no informamos al usuario de que se ha cargado la canción, su duración y título solo tenemos la mitad del trabajo hecho, así que lo instanciamos a continuación, enviándole el objeto de audio y el título de la pista de audio.

   this.display = new audioDisplay(this.engine.audio, this.engine.title);

Estos elementos son un conjunto de Listeners a la espera de recibir una pulsación de un botón, a partir de la cual se ejecutarán las correspondientes funciones del Modelo y de la Vista, según la acción. Como vemos es el auténtico cerebro de nuestro reproductor.

   // Interface Actions Listeners
playButton.click(function() {
self.engine.play();
});
 
pauseButton.click(function() {
self.engine.pause();
});
 
stopButton.click(function() {
self.engine.stop();
});
 
forwardButton.click(function() {
self.engine.forward();
});
 
backButton.click(function() {
self.engine.back();
});
 
volumeUpButton.click(function() {
self.engine.volumeUp();
});
 
volumeDownButton.click(function() {
self.engine.volumeDown();
});
 
muteButton.click(function() {
self.engine.audio.muted = (self.engine.audio.muted != true) ? true : false;
});
 
volumeSlider.change(function() {
self.engine.audio.volume = ($(this).val()/10).toFixed(1);
});
 
positionSlider.change(function() {
self.engine.audio.currentTime = $(this).val();
});
 
}

Arrancando el reproductor

Por supuesto deberemos de poner en marcha nuestro reproductor, y para ello lo hacemos de la forma clásica de jQuery. Al final de la carga del DOM. Primero definimos el fichero de audio mediante un array con la URL del fichero a descargar y el título a mostrar de la pista de audio. A continuación instanciamos el objeto de nuestro reproductor de audio.

 $(document).ready(function() {
 
var audioList =  { 'file' : 'https://s3-eu-west-1.amazonaws.com/static.oscargascon.es/wp-content/media/slash.mp3', 'title' : 'Slash by Other' };
// Launching!
new audioControls(audioList);

Y el código HTML

Por último incluimos el código HTML de nuestro reproductor. Realmente como podemos comprobar únicamente es un formulario con inputs y sliders que nos permiten interactuar con el reproductor. En un próximo post le daremos algo de estilo aprovechado las posibilidades de CSS y jQuery.

<form id="audioPlayer">
<div>
<button id="back" type="button"><<</button>
<button id="stop" type="button">Stop</button>
<button id="pause" type="button">Pause</button>
<button id="play" type="button">Play</button>
<button id="forward" type="button">>></button>
</div>
<div>
<button id="volumeUp" type="button">Volume Up</button>
<button id="volumeDown" type="button">Volume Down</button>
<button id="mute" type="button">Mute</button>
</div>
<div>
Volume: <input type="range" id="volume" min="0" max="10" value="10" />
</div>
<div>
Song: <span id="title"></span>
</div>
<div>
Time: <span id="currentTime"></span>/<span id="duration"></span>
</div>
<div>
Position: <input type="range" id="position" min="0" value="0" />
</div>
</form>

Para acabar

En este post simplemente he pretendido mostrar las posibilidades que ofrece el API de HTML5 en la interacción con JS y que la aplicación de patrones de programación también es recomendable en JavaScript. No soy un experto en Programación Orientada a Objetos en JavaScript así que espero me perdonéis y corrijais si he cometido algún error. gracias y espero no haber sido muy pesado. 😉

Fuentes

HTML Audio/Video DOM Reference: http://www.w3schools.com/tags/ref_av_dom.asp
Modelo Vista Controlador: http://es.wikipedia.org/wiki/Modelo%E2%80%93vista%E2%80%93controlador
Programación Orientada a Objetos en JS: https://developer.mozilla.org/es/docs/Web/JavaScript/Introducci%C3%B3n_a_JavaScript_orientado_a_objetos
Bindings in JavaScript: http://alistapart.com/article/getoutbindingsituations

Comparte si te ha gustado

Autor:
Última actualización:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

De acuerdo con lo dispuesto en el Reglamento (UE) 2016/679 de 27 de abril de 2016, consiento que mis datos sean tratados bajo la responsabilidad de Oscar Gascón Arjol para recibir respuesta a consultas. publicación de comentarios del blog y que las conserve mientras haya un interés mutuo para ello. Me doy por informado que tengo derecho a revocar este consentimiento en cualquier momento y a ejercer los de acceso, rectificación, portabilidad y supresión de mis datos y los de limitación y oposición al tratamiento dirigiéndome por email a me@oscargascon.es. También estoy informado de que puedo reclamar ante la autoridad de control a www.agpd.es.