Crear un Lector de Podcasts con Adobe Flex 3

August 29, 2009 – 3:04 pm
Tags: ,

El código fuente publicado en esta entrada muestra una aplicación flex que permite la consulta y descarga de archivos podcast publicados en internet. A continuación una imagen de muestra del aplicativo:


Básicamente el usuario selecciona un canal del ComboBox superior y las entradas de podcast se cargan en la lista que tiene un botón de reproducción y descarga para cada uno de los items.
Cuando el usuario selecciona el botón de reproducción de una de las entradas, el componente de audio carga el mp3 relacionado en el componente del “player” que se encuentra en la parte inferior de la pantalla.

Ver aplicación en Línea (*)

Descargar Código Fuente

Notas (*) :

  • La carga de algunos RSS y el analizador de espectro solo funcionan ejecutando el aplicativo en forma local.
  • La descarga de archivos funciona únicamente si el usuario tiene instalado el  Flash Player versión 10

Código Fuente:

Mp3Player.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
	creationComplete="{init();}" width="100%" height="100%"
	xmlns:fx="com.fusiox.ui.*" 
	layout="vertical">
<mx:Script>
	<![CDATA[
		import mx.rpc.events.ResultEvent;
		import mx.collections.ArrayCollection;
		import mx.controls.Alert;
		import mx.utils.ObjectUtil;
        // Play & Pause Icons
		[Embed(source="assets/Pause.png")] 	[Bindable] public var iconPause:Class;
		[Embed(source="assets/Play.png")] 	[Bindable] public var iconPlay:Class;
		[Embed(source="assets/Next.png")] 	[Bindable] public var iconNext:Class;
 
 
		private var sound:Sound;						// Mp3 File 
		private var soundChannel:SoundChannel;			// Reference to playing channel 
		private var pausePosition:Number;				// Current play position (time)
		private var percent:Number;						// Current played percentage
		private var isPlaying:Boolean = false;			// Is the mp3 playing?
		private var isLoaded:Boolean = false;			// Is the mp3 loaded?
		private var updateSeek:Timer = new Timer(500);	// Timer for updating the seek bar
 
		[Bindable]
        public var podcastChannels:ArrayCollection = new ArrayCollection(
            [ {label:"Music - Free Music Everywhere Podcast", data:'http://feeds2.feedburner.com/FreeMusicEverywhere'}, 
              {label:"Music - Last.fm Free Mp3 - Electronic ", data:'http://ws.audioscrobbler.com/2.0/tag/Electronic/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Metal ", data:'http://ws.audioscrobbler.com/2.0/tag/Metal/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Jazz ", data:'http://ws.audioscrobbler.com/2.0/tag/Jazz/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Rock ", data:'http://ws.audioscrobbler.com/2.0/tag/Rock/podcast.rss'},
              {label:"Music - Last.fm Free Mp3 - Classical ", data:'http://ws.audioscrobbler.com/2.0/tag/Classical/podcast.rss'},
              {label:"News - Guardian Daily", data:'http://www.guardian.co.uk/news/series/guardiandaily/podcast.xml'},
              {label:"News - Tech Weekly", data:'http://www.guardian.co.uk/technology/series/techweekly/podcast.xml'},
              {label:"News - Business Weekly", data:'http://downloads.bbc.co.uk/podcasts/worldservice/bizweekly/rss.xml'},
              {label:"News - BBC Global News", data:'http://downloads.bbc.co.uk/podcasts/worldservice/globalnews/rss.xml'},
              {label:"News - La Historia del Mundo con Diana Uribe", data:'http://www.caracol.com.co/feed.aspx?id=PROG_320899'}, 
              {label:"News - Hora de Negocios con Paola Ochoa", data:'http://www.caracol.com.co/feed.aspx?id=PROG_555605'} 
            ]);
 
		private function init():void { 
			// Add event listener for seek bar updater & start timer							
            updateSeek.addEventListener(TimerEvent.TIMER, updatePosition);
            updateSeek.start();
            // Add mouse click listener for the seek bar
            seekAudioBar.addEventListener(MouseEvent.CLICK, seek);
			// Add event listener that blurs the visualization
			viz.addEventListener("beforeVisualization", vizFade);
			selectChannel();
			//Add Events that come from each renderer. 
			rssList.addEventListener('playPodcast', playPodcast);
		}
 
		private function selectChannel():void {
            rssService.url = channelsCb.selectedItem.data;
			rssService.send();
        } 
 
        protected function onServiceResult(event:ResultEvent):void {
			rssList.dataProvider = event.result.rss.channel.item;
			next();
		}
 
		protected function playPodcast(event:Event):void {
			var selection:Object = rssList.selectedItem ; 
			loadSound(selection.enclosure.url);
		}
 
		private function loadSound(mp3Url:String):void{
			// If the song isn't loaded yet, set up a new sound load request
			sound = new Sound();
			sound.load(new URLRequest(mp3Url));
			// Add an event listener to check for song load complete event
			sound.addEventListener(Event.COMPLETE, songLoaded);
			// Show text for loading song and add event listener for updating
			sound.addEventListener(ProgressEvent.PROGRESS, updateStatus);
			sound.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
			loadProgressBar.visible=true;
			loadProgressBar.text = "0 %"
		}
		private function updateStatus(e:ProgressEvent):void {
			var loadPercent:Number = Math.round((e.bytesLoaded / e.bytesTotal) * 100);
			loadProgressBar.text = loadPercent+" %";
		}
		private function onIOError(event:IOErrorEvent):void{
		    Alert.show("The sound could not be loaded: " + event.text, "Error");
		}
 
		// Once the song is loaded...
		private function songLoaded(e:Event):void {
			if(soundChannel){
				soundChannel.stop()
			}
 
			var selection:Object = rssList.selectedItem ; 
			podTitle.text = selection.title;
 
			// Hide status text
			loadProgressBar.visible = false;
			// Remove load event listener
			sound.removeEventListener(Event.COMPLETE, songLoaded);
			// Turn on seek bar
			enableSeek();
			// Play the song
			soundChannel = sound.play(0);
            // Pull the title/artist/album from the ID3 tags and display
            theArtistAlbum.text  = "";
            if(sound.id3.TIT2){
            	theArtistAlbum.text += sound.id3.TIT2;
            }
            if(sound.id3.artist){
            	theArtistAlbum.text += ", "+ sound.id3.artist;
            }
			// Song is loaded
			isLoaded = true;
			seekAudioBar.visible = isLoaded;
			isPlaying = true;
		}
 
		// Play or Pause button has been clicked
		private function playPause(e:Event = null):void  {
			// Song playing?
			if(isPlaying) {
				// Save the current position in the track, stop playback, change button icon
				pausePosition = soundChannel.position;
				soundChannel.stop();
				playPauseBtn.setStyle("icon", iconPlay);
			// Song is not playing?
			} else {
				// The song IS loaded, so play it
				soundChannel = sound.play(pausePosition);
				// Change the button icon to the pause style
				playPauseBtn.setStyle("icon", iconPause);
			}
			// Regardless of playing state, change it now to the opposite
			isPlaying = !isPlaying;
		}
 
		// Enabled the seek (progress) bar
		public function enableSeek():void {
			seekAudioBar.enabled = true;
			seekAudioBar.visible = true;
		}			
 
		// Add a color transform to the visulization to create the fade effect
		private function vizFade(e:Event):void {
			e.target.bitmapData.colorTransform(
				e.target.bitmapData.rect, new ColorTransform(0.75,0.75,0.75,.75));
		}
 
		// Called when the seek (progress) bar needs updating
		private function updatePosition(e:Event):void {
 			// Is a song playing?
 			if(isPlaying && soundChannel) { 
 				// If the position is beyond the song start, calculate & set the percentage
 				percent = soundChannel.position > 0 ? (soundChannel.position/sound.length)*100 : 0;
				seekAudioBar.setProgress(percent,100);
	   			// Tell the seekbar to redraw itself
	   			seekAudioBar.validateNow();
	   		}
		}
 
 
 
 
 
		// Mouse click event on the seekbar calls this function
		private function seek(e:MouseEvent):void {
			// Is the song playing AND is the seekbar enabled and visible?
			if(isPlaying && seekAudioBar.enabled) { 
				// Stop playing at current position, change to new location, continue playing
				soundChannel.stop();
				pausePosition = (seekAudioBar.contentMouseX/seekAudioBar.width)*sound.length;
				soundChannel = sound.play(pausePosition);
				setVolume(this.volumeControl.value)
			}
		}
 
		private function setVolume(volume:Number):void {
            if(soundChannel){
	            var transform:SoundTransform = soundChannel.soundTransform;
	            transform.volume = volume;
            	soundChannel.soundTransform = transform;
            }
        }
 
        private function next():void{
        	var selectedIdx:int = rssList.selectedIndex;
        	if(selectedIdx < rssList.dataProvider.length){
        		rssList.selectedIndex = ++selectedIdx;
        		playPodcast(null);
        	}
        }
 
	]]>
</mx:Script>
	<mx:Style source="style.css" />
	<mx:HTTPService id="rssService" result="{onServiceResult(event)}" resultFormat="object" />
	<mx:Fade id="FadeIn" duration="3000" alphaFrom="0.0" alphaTo="1.0"/>	
	<mx:Fade id="FadeOut" duration="3000" alphaFrom="1.0" alphaTo="0.0"/>
 
	<mx:VDividedBox width="100%" height="100%">
		<mx:VBox width="100%" height="50%"  >
			<mx:Label text="My Flex Based Podcast Reader" fontWeight="bold" fontSize="16" />
			<mx:HBox width="100%">
				<mx:Label text="Available Podcasts:"/>
				<mx:ComboBox id="channelsCb" dataProvider="{podcastChannels}" width="90%" close="selectChannel();"/>	
			</mx:HBox>
			<mx:List id="rssList" width="100%" height="100%" itemRenderer="RSSRenderer" labelField="title"  />
		</mx:VBox>
		<mx:VBox id="podBox" width="100%" height="50%" horizontalAlign="center"  >
			<mx:Text id="podTitle"  fontSize="15" fontWeight="bold" maxWidth="{podBox.width}" />
			<mx:Text id="theArtistAlbum" text="or enter a new URL to load below" fontStyle="italic"/>
			<mx:HBox width="100%" height="100" >
				<fx:Visualization id="viz" type="bars" bars="96" channel="stereo" 
					width="95%" height="{volumeControl.height}" 
					audioFillColor="0xFFFFFF" audioLineColor="0xFFFFFF"/>	
				<mx:VSlider id="volumeControl" allowTrackClick="false" toolTip="{}" liveDragging="true" 
					thumbDrag="setVolume(this.volumeControl.value)" 
					height="100" value=".5" maximum="1" minimum="0"/>						
			</mx:HBox>
 
			<mx:ControlBar width="100%" >
				<mx:Button id="playPauseBtn" click="{playPause(event)}"  icon="{iconPlay}" />
				<mx:Button icon="{iconNext}" click="{next();}"/>
				<mx:ProgressBar id="seekAudioBar"  showEffect="{FadeIn}" height="16" width="100%" 
					enabled="false" visible="false" 
					mode="manual" label="" indeterminate="false" labelPlacement="center"/>	
			</mx:ControlBar>
			<mx:Label id="loadProgressBar" showEffect="{FadeIn}" hideEffect="{FadeOut}" width="100%" height="20"
					enabled="false" visible="false" 
					text="" fontWeight="bold" />
		</mx:VBox>
	</mx:VDividedBox>
</mx:Application>

RSSRenderer.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" 
	width="100%" height="60" 
	verticalGap="0" verticalAlign="middle">
<mx:Script>
<![CDATA[
import flash.net.navigateToURL;
[Embed(source="assets/Play.png")] 	[Bindable] public var iconPlay:Class;
[Embed(source="assets/Save.png")] 	[Bindable] public var iconSave:Class;
//procesa el dato para ser configurado por el renderer	
override protected function commitProperties():void {
	super.commitProperties();
	if (data == null) { return; }
	lbl.label = data.title;
	lbl.toolTip = data.description;
	downloadProgress.text = "";
}
//
protected function playPodcast():void {
	dispatchEvent(new Event('playPodcast', true));
}
//abre la página html original donde se publicó el podcast
private function viewPost():void{
	navigateToURL(new URLRequest(data.link), "_blank");
}
//inicia el proceso de descarga del mp3
private function downloadMp3():void{
	var urlReq:URLRequest = new URLRequest(data.enclosure.url);
	var fileRef:FileReference = new FileReference();
	fileRef.addEventListener(ProgressEvent.PROGRESS, progressHandler);  
   	fileRef.addEventListener(Event.COMPLETE, completeHandler);  
	fileRef.download(urlReq);
}
//Progreso de la descarga del archivo mp3
private function progressHandler(e:ProgressEvent):void {  
     var loadPercent:Number = Math.round((e.bytesLoaded / e.bytesTotal) * 100);
	downloadProgress.text = loadPercent+" %";
} 
private function completeHandler(event:Event):void {  
     downloadProgress.text = "Download Complete";  
}  
	]]>
</mx:Script>
	<mx:LinkButton id="lbl" width="260" color="0x0" fontWeight="bold" click="{viewPost()}"  />
	<mx:Button toolTip="Play" icon="{iconPlay}" click="playPodcast()" height="32" />
	<mx:Button toolTip="Download" icon="{iconSave}" click="downloadMp3()" height="32" />
	<mx:Label id="downloadProgress" text=""  />
</mx:HBox>

Referencias:

Share
  1. One Response to “Crear un Lector de Podcasts con Adobe Flex 3”

  2. Estoy desarrollando un aplicacion para almacenar y gestionar el conocimiento basado en mapas de topicos tanto en Adobe Flex como Adobe AIR: http://www.quesucede.com/page/show/id/polishedcode. La experiencia fue muy agradable y productiva

    By Brett Kromkamp on Oct 3, 2009

Post a Comment

Switch to our mobile site