Hasta aquí todo el código necesario. De todas formas, como las clases Control y Reproductor las he explicado a trozos y saltando de una a otra por conveniencia, vuelvo a mostrar su código completo:
clase Control:
package controlador { import flash.display.Sprite; import flash.net.URLLoader; import flash.net.URLRequest; import flash.events.Event; import flash.events.MouseEvent; import flash.net.SharedObject; import modelo.Playlist; import modelo.Cancion; import vista.Reproductor; import vista.Tip; import fl.controls.List; import fl.controls.Slider; import flash.text.TextField; import fl.events.ListEvent; public class Control { private var _reproductor:Reproductor; private var _playlist:Playlist; private var _shared:SharedObject; private const RUTA_LISTA_XML:String = "http://www.fast-forward.es/activos/flash/reproductor mp3/playlist.xml"; 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); } } private function onDatosXML(e:Event):void { var xml:XML = new XML(e.target.data); crearPlaylist(xml); } private function crearPlaylist(datos:*):void { _playlist = new Playlist(datos); // Una vez creada la playlist, mostrarla en el List del reproductor, mostrarLista(); // Una vez la lista completada, se puede inicializar reproductor para su reproducción _reproductor.inicializar(_playlist); // y llamar al método de control del reproductor controlReproductor(); } private function mostrarLista():void { // Obtener referencia al List del reproductor var list:List = _reproductor.getChildByName("listaCanciones") as List; // Recorrer la playlist y obtener canción a canción, procesándolas // de forma que se puedan añadir a la lista var obj:Object; var cancion:Cancion; for (var i:uint = 0; i < _playlist.lista.length; i++) { cancion = _playlist.lista[i]; obj = {data:cancion.id, label:cancion.artista + " - " + cancion.nombre}; list.addItem(obj); } list.selectedIndex = 0; } // Método que asigna el control de interacciones a los detectores private function controlReproductor():void { // Obtener referencias a los sprites gráficos ya creados del reproductor var display_txt:TextField = _reproductor.getElemento("temaActual_txt") as TextField; var botonPlay:Sprite = _reproductor.getElemento("botonPlay") as Sprite; var botonStop:Sprite = _reproductor.getElemento("botonStop") as Sprite; var botonPrevious:Sprite = _reproductor.getElemento("botonPrevious") as Sprite; var botonNext:Sprite = _reproductor.getElemento("botonNext") as Sprite; var botonNoRepeat:Sprite = _reproductor.getElemento("botonNoRepeat") as Sprite; var botonRandomOff:Sprite = _reproductor.getElemento("botonRandomOff") as Sprite; var botonAbrir:Sprite = _reproductor.getElemento("botonAbrir") as Sprite; var list:List = _reproductor.getElemento("listaCanciones") as List; var bVolumen:Slider = _reproductor.getElemento("barraVolumen") as Slider; // Poner inicialmente el elemento seleccionado en el display del reprod. display_txt.text = list.getItemAt(list.selectedIndex).label; // Control de los eventos: // Controles de reproducción botonPlay.addEventListener(MouseEvent.CLICK, accionReproductor); botonStop.addEventListener(MouseEvent.CLICK, accionReproductor); botonPrevious.addEventListener(MouseEvent.CLICK, accionReproductor); botonNext.addEventListener(MouseEvent.CLICK, accionReproductor); botonPlay.doubleClickEnabled = true; botonStop.doubleClickEnabled = true; botonPrevious.doubleClickEnabled = true; botonNext.doubleClickEnabled = true; botonPlay.addEventListener(MouseEvent.DOUBLE_CLICK, accionReproductor); botonStop.addEventListener(MouseEvent.DOUBLE_CLICK, accionReproductor); botonPrevious.addEventListener(MouseEvent.DOUBLE_CLICK, accionReproductor); botonNext.addEventListener(MouseEvent.DOUBLE_CLICK, accionReproductor); // Controles de opciones de reproducción botonNoRepeat.addEventListener(MouseEvent.CLICK, accionReproductor); botonRandomOff.addEventListener(MouseEvent.CLICK, accionReproductor); list.addEventListener(ListEvent.ITEM_DOUBLE_CLICK, accionReproductor); list.addEventListener(Event.CHANGE, accionReproductor); bVolumen.addEventListener(Event.CHANGE, accionReproductor); // Controles de hints o mensajes contextuales botonNoRepeat.addEventListener(MouseEvent.ROLL_OVER, crearTip); botonNoRepeat.addEventListener(MouseEvent.ROLL_OUT, cerrarTip); botonRandomOff.addEventListener(MouseEvent.ROLL_OVER, crearTip); botonRandomOff.addEventListener(MouseEvent.ROLL_OUT, cerrarTip); bVolumen.addEventListener(MouseEvent.ROLL_OVER, crearTip); bVolumen.addEventListener(MouseEvent.ROLL_OUT, cerrarTip); } // Manejador de los eventos de ROLL_OVER en botones de opción private function crearTip(e:MouseEvent):void { var b:Sprite = e.currentTarget as Sprite; switch (b.name) { case "botonNoRepeat": Tip.crear(_reproductor, "Repeat Off"); break; case "botonRepeatOnce": Tip.crear(_reproductor, "Repeat One"); break; case "botonRepeatAll": Tip.crear(_reproductor, "Repeat All"); break; case "botonRandomOff": Tip.crear(_reproductor, "Random Off"); break; case "botonRandomOn": Tip.crear(_reproductor, "Random On"); break; case "barraVolumen": Tip.crear(_reproductor, "Volumen"); b.removeEventListener(MouseEvent.ROLL_OVER, crearTip); //b.removeEventListener(MouseEvent.ROLL_OUT, cerrarTip); break; } } // Manejador de evento ROLL_OUT en botones de opción private function cerrarTip(e:MouseEvent):void { Tip.cerrar(); } // Manejador de eventos de CLICK y DOUBLE_CLICK en botones private function accionReproductor(e:Event):void { var boton:Sprite = e.currentTarget as Sprite; var botonPlay, botonPause, botonRep, botonRandom:Sprite; trace(boton); switch (boton.name) { case "botonPlay": _reproductor.play(); botonPause = new BotonPause(); intercambiar(boton, botonPause, "botonPause"); break; case "botonStop": _reproductor.stop(); if ((botonPause = Sprite(_reproductor.getChildByName("botonPause"))) != null) { botonPlay = new BotonPlay(); intercambiar(botonPause, botonPlay, "botonPlay"); } break; case "botonPause": _reproductor.pause(); botonPlay = new BotonPlay(); intercambiar(boton, botonPlay, "botonPlay"); break; case "botonPrevious": _reproductor.previous(); break; case "botonNext": _reproductor.next(); break; case "botonNoRepeat": // Al clicar "no repeat" pasamos al estado "repeat once", y poner su icono correspondiente _reproductor.estadoRepeat = 1; botonRep = new BotonRepeatOnce(); intercambiar(boton, botonRep, "botonRepeatOnce"); break; case "botonRepeatOnce": // Al clicar "repeat once" pasamos al estado "repeat all", y ponemos su icono _reproductor.estadoRepeat = 2; botonRep = new BotonRepeatAll(); intercambiar(boton, botonRep, "botonRepeatAll"); break; case "botonRepeatAll": // Al clicar "repeat all" pasamos al estado "no repeat", y ponemos su icono _reproductor.estadoRepeat = 0; botonRep = new BotonNoRepeat(); intercambiar(boton, botonRep, "botonNoRepeat"); break; case "listaCanciones": // Comprobar si se trata de un doble clic en un item o de un cambio de item trace("Tipo de evento: " + e.type); if (e.type == Event.CHANGE) { _reproductor.reset(); } if (e.type == ListEvent.ITEM_DOUBLE_CLICK) { if (_reproductor.isPlaying) { _reproductor.stop(); } _reproductor.play(); if ((botonPlay = _reproductor.getChildByName("botonPlay") as Sprite) != null) { botonPause = new BotonPause(); intercambiar(botonPlay, botonPause, "botonPause"); } } break; case "botonRandomOff": // Al hacer clic en "estado random off", pasamos a "estado random on" y mostramos su botón _reproductor.estadoRandom = true; botonRandom = new BotonRandomOn(); intercambiar(boton, botonRandom, "botonRandomOn"); break; case "botonRandomOn": // Al hacer clic en "estado random on", pasamos a "estado random off" y mostramos su botón _reproductor.estadoRandom = false; botonRandom = new BotonRandomOff(); intercambiar(boton, botonRandom, "botonRandomOff"); break; case "barraVolumen": // Ordenar al reproductor que se actualice su volumen dependiendo del // nuevo valor del slider "barraVolumen" _reproductor.cambiarVolumen(); } } private function intercambiar(boton1:Sprite, boton2:Sprite, nomBoton2:String):void { // Por si había un tip abierto, cerrarlo Tip.cerrar(); // Intercambio boton2.x = boton1.x; boton2.y = boton1.y; boton2.name = nomBoton2; _reproductor.addChild(boton2); // Añadir detectores correspondientes para el nuevo botón boton2.addEventListener(MouseEvent.CLICK, accionReproductor); boton2.addEventListener(MouseEvent.ROLL_OVER, crearTip); boton2.addEventListener(MouseEvent.ROLL_OUT, cerrarTip); // Eliminar detectores del botón que se va a eliminar boton1.removeEventListener(MouseEvent.CLICK, accionReproductor); boton1.removeEventListener(MouseEvent.ROLL_OVER, crearTip); boton1.removeEventListener(MouseEvent.ROLL_OUT, cerrarTip); _reproductor.removeChild(boton1); } } }
Clase Reproductor:
package vista { import flash.display.Sprite; import flash.display.DisplayObject; import modelo.Playlist; import modelo.Cancion; import vista.BarraTiempo; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.net.URLRequest; import flash.events.Event; import flash.utils.Timer; import flash.events.TimerEvent; import flash.text.TextFormat; import flash.events.ProgressEvent; import flash.filters.DropShadowFilter; import flash.events.MouseEvent; import flash.display.Graphics; import fl.managers.StyleManager; import flash.text.Font; import fl.controls.List; import fl.controls.Slider; public class Reproductor extends Sprite { private var _playlist:Playlist; private var _sonido:Sound; private var _channel:SoundChannel; private var _posicion:Number; private var _timer:Timer; private var _tiempoCargado:Number; // establece diferencia entre bytes cargados y totales // para estimar tiempo total de la canción mientras se carga private var _list:List; private var _barraTiempo:BarraTiempo; private var _estadoRepro:uint = 0; // 0 -> parado, 1 -> play, 2 - > pause private var _estadoRepeat:uint = 0; // 0 -> No repetir (solo una secuencia), 1 -> Repetir una, 2 - > Repetir todo private var _estadoRandom:Boolean = false; private var _playing:Boolean = false; private var _volumen:Number; 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(); } // Una vez disponible una playlist, se inicializa con ella el reproductor (su lista de canciones) public function inicializar(playlist:Playlist):void { _list = this.getChildByName("listaCanciones") as List; formatear(_list); _playlist = playlist; _timer = new Timer(10, 0); _timer.addEventListener(TimerEvent.TIMER, onTimer); } private function nuevaPosicion(e:Event):void { if (_channel != null) { var ratio:Number = _barraTiempo.xPuntero / _barraTiempo.width; this.stop(); _posicion = _sonido.length * ratio; trace("Buscar nueva posicion... " + _posicion); this.play(); } } public function getElemento(nombre:String):DisplayObject { return this.getChildByName(nombre); } public function play():void { if (_playlist != null) { _list.scrollToSelected(); var indice:uint = _list.selectedIndex; var ruta:String = _playlist.lista[indice].ruta; if (_channel != null && mismoTema(ruta)) { _channel = _sonido.play(_posicion); } else { crearSonido(ruta); var display_txt:TextField = this.getChildByName("temaActual_txt") as TextField; display_txt.text = _list.getItemAt(_list.selectedIndex).label; } _channel.addEventListener(Event.SOUND_COMPLETE, finCancion); var soundTrans:SoundTransform = _channel.soundTransform; soundTrans.volume = _volumen; _channel.soundTransform = soundTrans; _playing = true; _timer.start(); } } // Comprueba si el sonido actual y el seleccionado en la lista coinciden, o se ha cambiado // la selección. private function mismoTema(ruta:String):Boolean { if (_sonido.url.indexOf(ruta) != -1) { // El sonido a reproducir o reproduciéndose coincide con el seleccionado en la lista return true } else { // Se ha cambiado el sonido seleccionado en la lista return false; } } private function crearSonido(ruta:String):void { if (_sonido != null) { _sonido.removeEventListener(ProgressEvent.PROGRESS, datosRecibidos); _sonido = null; } _sonido = new Sound(new URLRequest(ruta)); _sonido.addEventListener(ProgressEvent.PROGRESS, datosRecibidos); trace("Reproduciendo sonido " + _sonido + " desde " + _posicion); _channel = _sonido.play(_posicion); } private function datosRecibidos(e:ProgressEvent):void { _tiempoCargado = e.bytesLoaded / e.bytesTotal; _barraTiempo.actualizarBuffer(_tiempoCargado); } private function finCancion(e:Event):void { next(); } public function stop():void { if (_channel != null) { _channel.stop(); _posicion = 0; _playing = false; _timer.stop(); } // Actualizar display de tiempo a 0 var timer_txt:TextField = this.getChildByName("time_txt") as TextField; timer_txt.text = "00:00"; // Resetear barra de tiempo _barraTiempo.reset(); } public function pause():void { // Comprobar si se ha cambiado de canción en la lista. // Si se ha cambiado, el pause se convertirá en un stop y la reproducción se reanudará al inicio de la nueva canción. // Si no ha habido cambio, se almacena la posición donde ha quedado la reproducción para reanudar ahí if (mismoTema(_playlist.lista[_list.selectedIndex].ruta)) { _posicion = _channel.position; _channel.stop(); _playing = false; _timer.stop(); } else { this.stop(); } } public function previous():void { var indice:int = _list.selectedIndex; if (_estadoRepeat == 1) { // Modo repetir una, se reproduce el principio de la misma } else if (_estadoRepeat == 0 && !_estadoRandom) { // Modo aleatorio en off, y en modo no repetir if (_list.selectedIndex == 0) { // Estaba reproduciendose primera cancion de playlist // Se sigue en la primera canción indice = 0; } else { // No estaba reproduciendose primera cancion de playlist indice = _list.selectedIndex - 1; } _list.selectedIndex = indice; } else if (_estadoRandom && _estadoRepeat != 1) { // Modo aleatorio ON, y modo reproduccion no es repetir solo una indice = Math.floor(Math.random()*(_list.length - 1)); _list.selectedIndex = indice; trace("Reproducir aleatoriamente tema de indice " + indice); } else if (!_estadoRandom && _estadoRepeat == 2) { // Modo random OFF y modo repetir todas if (_list.selectedIndex > 0) { // No es principio de lista principal indice = _list.selectedIndex - 1; } else { // Es principio de lista principal indice = _list.length - 1; } _list.selectedIndex = indice; } if (this._playing) { this.stop(); this.play(); } } public function next():void { var indice:int = _list.selectedIndex; if (_estadoRepeat == 1) { // Modo repetir una, se reproduce el principio de la misma } else if (_estadoRepeat == 0 && !_estadoRandom) { // Modo no repeat, secuencial no aleatorio (random OFF) if (_list.selectedIndex < _list.length - 1) { // No es final de lista principal indice = _list.selectedIndex + 1; } else { // Es final de lista principal // No hacer nada } _list.selectedIndex = indice; } else if (_estadoRandom && _estadoRepeat != 1) { // Modo random ON y no activo modo repetir una indice = Math.floor(Math.random()*(_list.length - 1)); _list.selectedIndex = indice; } else if (!_estadoRandom && _estadoRepeat == 2) { // Modo random OFF y modo repetir todas if (_list.selectedIndex < _list.length - 1) { // No es final de lista principal indice = _list.selectedIndex + 1; } else { // Es final de lista principal indice = 0; } _list.selectedIndex = indice; } if (this._playing) { this.stop(); this.play(); } } public function cambiarVolumen():void { // Actualizar volumen del reproductor dependiendo del nuevo valor del slider var volumen:Slider = this.getChildByName("barraVolumen") as Slider; _volumen = volumen.value; if (_channel != null) { var soundTrans:SoundTransform = _channel.soundTransform; soundTrans.volume = _volumen; _channel.soundTransform = soundTrans; } } private function onTimer(e:TimerEvent):void { // Actualizar display de tiempo dependiendo de posición del canal de audio var timer_txt:TextField = this.getChildByName("time_txt") as TextField; timer_txt.text = displayTime(_channel.position); _barraTiempo.actualizar(_channel.position, _sonido.length / _tiempoCargado); } private function displayTime(pos:Number):String { var horas, minutos, segundos:uint; var output:String; var totalSec:uint = pos / 1000; var resto:uint; horas = Math.floor(totalSec / (60*60)); resto = totalSec % (60*60); minutos = Math.floor(resto / 60); resto = resto % 60; segundos = Math.floor(resto); output = ((horas > 0) ? String(horas) + ":" : "") + ((minutos > 9) ? String(minutos) : "0" + String(minutos)) + ":" + ((segundos > 9) ? String(segundos) : "0" + String(segundos)); return output; } private function formatear(lista:List):void { var fuente:Font = new CenturyGothicRegular(); // Importar fuente incrustada en biblioteca var formatoTexto:TextFormat = new TextFormat(); formatoTexto.color = 0x666688; formatoTexto.letterSpacing = .5; formatoTexto.font = fuente.fontName; StyleManager.setStyle("textFormat", formatoTexto); } public function set estadoRepeat(estado:uint):void { _estadoRepeat = estado; } public function set estadoRandom(estado:Boolean):void { _estadoRandom = estado; } public function get isPlaying():Boolean { return _playing; } public function reset():void { _posicion = 0; _list.scrollToSelected(); } } }
Hasta aquí el reproductor de música. Espero que os sea de utilidad. Cualquier comentario, sugerencia y crítica constructiva es bienvenida.