Reproductor de música en Actionscript 3, usando POO y patrón MVC

Fase de implementación. Programación.

Antes que nada, decir que para mayor claridad en el código, yo sigo la convención de empezar todos los nombres de propiedades con una barra baja [_] para distinguirlas de las variables locales y parámetros. Por ejemplo en un método dado de una clase, _lista sería una propiedad de esa clase, y lista, una variable local o pasada como parámetro. No es obligatorio hacer esto pero sí lo recomiendo.

Para empezar, necesitaré ayuda de una clase que inicialice la aplicación, y que sea creada desde el archivo FLA. Esta clase la llamo Inicio. En el panel Acciones del documento FLA, en el fotograma 1, añado lo siguiente:

var menuContexto:ContextMenu = new ContextMenu();
menuContexto.hideBuiltInItems();
this.contextMenu = menuContexto;
var inicio:Sprite = new Inicio(this);

stop();

Gracias a la clase ContextMenu, y a su método hideBuiltInItems() desactivo ciertas acciones disponibles en el menú contextual al hacer clic sobre una película Flash, como rebobinar, hacer zoom, etc., por lo demás, se crea una instancia de la clase inicializadora Inicio, y se ejecuta un stop(), como ya hemos visto anets, para que no nos desplacemos al siguiente fotograma de la línea de tiempo principal. No nos va a hacer falta una línea de tiempo.

Ahora vamos con la clase Inicio, localizada en el archivo Inicio.as, en el mismo directorio que el FLA:

package {

	import flash.display.Sprite;
	import flash.events.Event;
	import vista.Preload;
	import controlador.Control;

	public class Inicio extends Sprite {

		private var _padre:Sprite;
		private var _preload:Preload;
		private var _control:Control;

		public function Inicio(padre:Sprite) {
			// Control preload
			_padre = padre;
			_padre.addChild(this);
			_preload = new Preload();
			_preload.x = _padre.stage.stageWidth / 2 - _preload.width / 2;
			_preload.y = _padre.stage.stageHeight / 2 - _preload.height / 2;
			_padre.addChild(_preload);
			_preload.addEventListener(_preload.CARGA_COMPLETA, cargaCompleta);
		}

		private function cargaCompleta(e:Event):void {
			// Ahora se elimina el preloader,
			// y se cera el controlador que mostrará el reproductor y controlará
			// sus eventos
			_preload.removeEventListener(_preload.CARGA_COMPLETA, cargaCompleta);
			_padre.removeChild(_preload);
			_preload = null;
			_control = new Control(_padre);
		}
	}

}

Muestro todo el código de la clase de una sola vez porque su funcionamiento es bien sencillo. Se crea el preload y se coloca en el centro del escenario. Al haber pasado como parámetro la línea de tiempo principal, tenemos acceso a través de esta al escenario y sus propiedades. Después se añade el preload a la lista de visualización (display list) de la linea de tiempo principal o clase principal. Y se le asigna un detector del evento personalizado cargaCompleta, al cual tenemos acceso a través de la constante pública CARGA_COMPLETA de la clase Preload, que al activarse elimina el preload y crea una instancia de la clase Control, el controlador de la aplicación.

Ahora echemos un vistazo a la clase Preload. El funcionamiento de los preloaders es más o menos el mismo siempre:

package vista {

	import flash.display.Sprite;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import flash.display.LoaderInfo;
	import flash.events.Event;

	public class Preload extends Sprite {

		private var _timer:Timer;
		public const CARGA_COMPLETA = "cargaCompleta";

		public function Preload() {
			_timer = new Timer(10, 0);
			_timer.addEventListener(TimerEvent.TIMER, checkProgress);
			_timer.start();
		}

		private function checkProgress(e:TimerEvent):void {
			var info:LoaderInfo = this.parent.loaderInfo;
			var escala:int = getScale(info);
			var barraProgreso:Sprite = this.getChildByName("barraProgreso") as Sprite;
			barraProgreso.scaleX = escala;
			if (escala >= 1) {
				dispatchEvent(new Event(CARGA_COMPLETA));
				_timer.removeEventListener(TimerEvent.TIMER, checkProgress);
				_timer.stop();
				_timer = null;
			}
		}

		private function getScale(info:LoaderInfo):int {
			return int(info.bytesLoaded / info.bytesTotal);
		}
	}

}

El preloader, nada más ser creado, crea a su vez un temporizador que se activará cada 10 milisegundos indefinidamente. El detector de estas activaciones irá consultando el porcentaje de carga de la línea de tiempo principal, o sea, de toda la aplicación, y lo reflejará en la propia escala del Sprite de barraProgreso que importaremos de la biblioteca. Una vez cargado todo, se elimina el temporizador y se activa el evento ya mencionado cargaCompleta, creándose como hemos visto arriba, una instancia de la clase Control.

El constructor de la clase Control realiza lo siguiente:

public function Control(padre:Sprite) {
        // Crear y mostrar reproductor
	_reproductor = new Reproductor();
	_reproductor.x = padre.stage.stageWidth / 2 - _reproductor.width / 2;
	_reproductor.y = padre.stage.stageHeight / 2 - _reproductor.height / 2;
	padre.addChild(_reproductor);
	// Cargar datos externos de la playlist
	_shared = SharedObject.getLocal("playlist");
	if (_shared.data.lista is Array && _shared.data.lista[0] is Cancion) {
		// Hay datos locales almacenados de la playlist
		// Obtenerlos del Shared Object y pasarlos al método de creación de la playlist
		crearPlaylist(_shared.data.lista);
	} else {
		// No hay datos locales almacenados de la playlist
		// Se carga la playlist por defecto desde archivo xml
		var datosXML:URLLoader = new URLLoader(new URLRequest(RUTA_LISTA_XML));
		datosXML.addEventListener(Event.COMPLETE, onDatosXML);
	}
}

Primero crea una instancia de la clase Reproductor, la cual veremos más adelante, y la coloca en el escenario, en el centro. Seguidamente pasa a cargar los datos externos. Puede ser bien por XML o por SharedObject, que es como las cookies de Flash. Si es la primera vez que se accede a la aplicación o se ha borrado anteriormente la caché del navegador, se cargarán los datos XML, cuya ruta está definida por una constante. Una vez cargados, estos serán almacenados localmente como SharedObject y ya no será necesario volver a descargarlos del servidor. Mediante el método getLocal() intentamos acceder al objeto compartido (SharedObject) llamado “playlist”. Si se comprueba que tenemos ya dicho objeto, y es el objeto que esperamos, se crea directamente la lista de canciones, si no se procede a la carga del XML, asignándole un detector de carga completada. Los datos XML están formados como sigue:

<canciones>
	<cancion>
		<id>5</id>
		<nombre>Something So Strong</nombre>
		<artista>Crowded House</artista>
		<ruta>http://www.fast-forward.es/activos/flash/reproductor mp3/musica/Crowded House - Something So Strong.mp3</ruta>
	</cancion>
	<cancion>
		<id>6</id>
		<nombre>When You Come</nombre>
		<artista>Crowded House</artista>
		<ruta>http://www.fast-forward.es/activos/flash/reproductor mp3/musica/Crowded House - When You Come.mp3</ruta>
	</cancion>
....
</canciones>

Vamos a echar un vistazo ahora al constructor del reproductor y unos pequeños métodos que vienen bien explicarlos ahora:

public function Reproductor() {
	_barraTiempo = this.getChildByName("barraTiempo") as BarraTiempo;
	_barraTiempo.addEventListener(_barraTiempo.PUNTERO_SOLTADO, nuevaPosicion);
	_posicion = 0;
	_volumen = 1;
	// Añadir efecto de sombra al reproductor
	var sombra:DropShadowFilter = new DropShadowFilter(4, 45, 0, .4, 5, 5);
	this.filters = [sombra];
	// Añadir área de arrastre del reproductor
	var zonaActiva:Sprite = new Sprite();
	var g:Graphics = zonaActiva.graphics;
	g.beginFill(0x000000, 0);
	g.drawRect(0, 0, this.width, 15);
	g.endFill();
	addChild(zonaActiva);
	zonaActiva.addEventListener(MouseEvent.MOUSE_DOWN, moverReproductor);
	zonaActiva.addEventListener(MouseEvent.MOUSE_UP, soltarReproductor);
	zonaActiva.buttonMode = true;
}

private function moverReproductor(e:MouseEvent):void {
	this.startDrag();
}

private function soltarReproductor(e:MouseEvent):void {
	this.stopDrag();
}

Nada más crear el reproductor, obtenemos una referencia a la barra de tiempo (gracias el nombre de instancia que le dimos), y le añadimos un detector de un evento personalizado llamado punteroSoltado que veremos más adelante. Se inicializan propiedades de volumen y posición de reproducción, se le añade al reproductor un filtro de sombra, y se crea la zona de arrastre en la parte superior del reproductor. Al clicar y soltar en esta zona de arrastre se ejecutan sendos manejadores de eventos asignados por los detectores, que nos permiten arrastrar y soltar el reproductor.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>