miércoles, 3 de diciembre de 2014

HaxeUI: Reproductor Mp3 (4ta Parte)

Muchos controles, algo de código pero todavía no hace ni mu el programa...solucionemos eso.

Los métodos y eventos para reproducir sonidos que vamos a utilizar pertenecen a la librería de OpenFL. Son bastante básicos en lo que se refiere a manipulación de un archivo de audio, ya que toda la biblioteca OpenFL está más orientada a la creación de juegos y por ende, las características que posee para manipular audio son escasas.





Posee dos métodos para abrir un archivo que va a depender de si se encuentra incrustado en el programa (cosa que no sucede con nuestro ejemplo) o si va a obtenerlo remotamente (nuestro caso).

El primero es sencillo y muy directo, ya que forma parte de los "assets" o recursos. lo vamos a tratar en otro programa de ejemplo, con sonidos que sean mucho más cortos.

El segundo necesita algo de preparación, pero veamos la lógica que sigue nuestro programa a ver donde nos lleva:

Tenemos entonces que el usuario ingresa una dirección de internet en el cuadro de texto, y al presionar el botón la misma se agrega a la lista de temas (si es válida, sino la omite).

Como el programa "acorta" la cadena al agregarla como ítem a la lista, a la hora de utilizarla como valor para ir a buscar el archivo, no nos serviría. Por ejemplo el método get() nos devolvería la versión corta y el archivo no sería encontrado.

Entonces, debemos guardar la cadena "original" ingresada por el usuario en algún lado. Para ello creamos una matriz o array.

Con haxe es sencillo de definir una matriz, la sintaxis sería: 

var identificador = new Array<tipo>();

En nuestro caso exclusivamente necesitamos solamente guardar un sólo tipo de dato que es una cadena de caracteres, entonces sería:

var items = new Array<String>();

Lo creamos inmediatamente por debajo de la declaración de la función main.

Hay un ejemplo en GitHub de como se utilizan.

Las matrices como la declarada (unidimensional) son muy sencillas de utilizar, es su forma más básica digamos. Poseen un puntero o índice que no es mas que un número de orden y el dato que deseamos guardar.

Como cada entrada que se encuentre en el control ListView posee un identificador numérico único a medida que se van agregando, es muy sencillo asociar ese número con el puntero de la matriz y así ir guardando los datos en ella.


Vamos a agregar una línea al evento click del botón para guardar el dato en la matriz:

items.push(direccion.text);





 
El puntero de nuestra matriz se auto incrementa, el puntero del control lista también, entonces van a coincidir siempre el dato mostrado por el control lista y el dato guardado en la matriz.

el método .push(dato), lo único que hace es guardar en el último lugar el dato que le asignemos...lo empuja (push en inglés) dentro de la matriz, auto incrementando el puntero.

Casi lista la entrada de direcciones. solo queda manejar un poco el botón "Cargar".

Cuando se inicia el programa, debería de estar desactivado, ya que no hay una entrada válida al inicio. (el cuadro de texto muestra la leyenda "Agregar").


En realidad no importa mucho si el usuario ni bien arranca el programa presiona el botón ya que la rutina de control de entrada que escribimos lo interceptaría perfectamente, pero si el botón se muestra desactivado, la interfaz se vuelve un poco mas intuitiva para el usuario ya que visualmente le esta señalando que no puede usarlo sin hacer algo antes.

En la declaración de propiedades del objeto, insertamos una nueva línea:

carga.disabled = true;

Al inicio del programa cuando el objeto se crea, aparece en pantalla desactivado. Lo único que debemos hacer para activarlo es volver la propiedad disabled a false (falso).

Lo activaremos cuando la propiedad del control TextInput (direccion) se esté modificando (que nos está diciendo que el usuario esta tipeando algo en él).

Para ello agregamos un nuevo evento:


Sabemos con este método que ni bien el usuario ingrese algún carácter en el cuadro, de inmediato el botón cambia su estado a activo. (con que solo haga click y se muestre el cursor no basta, el programa va a esperar que el usuario ingrese algo...por eso el método del evento a monitorear se llama CHANGE (cambiar)...el evento espera un "cambio" por parte del usuario y no simplemente un click. Tenemos lista la interfaz de entrada.

Necesitamos más botones:



Digamos uno para reproducir, otro para pausa, otro para detener y uno para borrar la lista.

Puede observarse que por ahora, muestran etiquetas de texto los botones. una vez que tengamos el código funcionando como queremos, vamos a reemplazarlas por imágenes (iconos).

Los botones se muestran al principio desactivados para evitar que el usuario los presione sin haver seleccionado una canción o haber ingresado alguna a la lista. Ello evita errores en la ejecución del programa. Claro que podría solucionarse con algo de código, pero prefiero mantenerlo simple por el momento.


Además sería interesante tener una barra de progreso para saber cuanto de la canción ha sido reproducido, junto con un par de etiquetas más.


Es igual que la barra que muestra el progreso de la descarga. De hecho el código es exactamente igual.

Con estos dos objetos terminamos de añadir controles al programa por ahora.

Veamos que hacer con todos ellos y como manipular su comportamiento.

Existen varios eventos que necesitamos monitorear:

1) Si el usuario presiona un doble click doble en la lista. La descarga comienza.
2) Si el archivo se descargó, que pueda reproducirlo.
3) Permitir la pausa del archivo en curso.
4) Permitir detener la reproducción del archivo en curso.
5) Permitir Limpiar la lista

y uno más que es actualizar las barras de progreso, pero donde no esperamos una intervención directa del usuario.

Vamos a arrancar con el evento de descarga:


Un solo click en la lista no significa que el usuario pretenda reproducir el archivo, sino que simplemente navega por los items. El doble click es, para mí, el evento que muestra la intención de hacer algo con ese item en particular.

Al dispararse el evento, suceden varias cosas:

La primera, crea una instancia de una nueva clase: URLRequest.

Simplemente captura toda la información en una sola petición de http para que puedan utilizarse los métodos de carga (load) de otras clases u objetos.

El parámetro que le estamos pasando, (items[lista.selectedIndex]) es el dato alojado en la matriz (items)que tiene el índice proporcionado por el control lista.

Habría que importarla junto con otra:

import openfl.net.URLRequest;

import openfl.net.URLLoader;

Como verán estas dos no pertenecen a HaxeUI sino a OpenFL propiamente dicho.

Ahora agreguemos los controladores de sonido:

Necesitamos importar dos nuevas clases:

import openfl.media.Sound;

import openfl.media.SoundChannel;

Luego, dentro de la función main, vamos a crear dos objetos de estas clases:

var sonido: Sound = new Sound();

var canal: SoundChannel = new SoundChannel();

La clase sound (sonido) permite el uso de sonidos en una aplicación. Nos habilita para crear objetos, cargar un archivo de sonido y reproducirlo.
Existen varias políticas de seguridad con respecto al reproductor flash y Adobe Air que no voy a tratar aquí para no extenderme.

La clase SoundChannel (canal) permite la manipulación un poco más profunda del objeto sonido. Hasta ahora, pueden existir (y reproducirse o manipularse hasta 32 canales al mismo tiempo).

y volviendo al evento, como primer medida, detenemos cualquier reproducción que haya en curso para poder cargar una nueva:

canal.stop();


En este punto, debemos conocer si existe algún archivo descargándose actualmente. La instancia isBuffering nos va a devolver un valor lógico verdadero si el objeto sonido está esperando que más datos se descarguen.

Si este es el caso (isBuffering=true), debemos cerrar la conexión actual (que podría ser otro archivo y no justamente el seleccionado por el usuario) para comenzar con una nueva.

Por último, habilitamos el botón de reproducir para que pueda ser presionado.

Al utilizar las librerías URLRequest y URLLoader, sólo basta con crear un nuevo objeto sonido para que OpenFL invoque la instancia Load() automáticamente.

Y por último, al habilitar el bóton de reproducir, creamos el evento que lo maneje:


La instancia sonido.play crea un nuevo objeto canal para poder controlarlo.

posicion, es una variable que guarda la última posición en la que el archivo de sonido se ha reproducido y deberíamos inicializarla al comienzo de la función main.
Esta variable, va a permitirnos poder realizar una pausa en la reproducción del archivo y volver a continuar desde el ultimo punto.

Por último, puede verse como los botones van tomando diferentes estados: si estamos ejecutando el evento reproducir, quiere decir que el botón debería de desactivarse (ya que existe una reproducción en curso), el botón de pausa debería activarse, para permitir su uso junto con el de detener.

Con todos estos agregados, el reproductor quedaría mas o menos así:



Visualmente no es la gran cosa, pero cumple con su cometido. Obviamente el programa puede ser mejorado; Controles de volumen, silencio, colores, etc.



El código completo puede encontrarse en la nube