Crear un Lector de Podcasts con Adobe Flex 3
August 29, 2009 – 3:04 pmTags: adobe flex 3, Tutorial
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.
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
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:
- Making a Flex MP3 Player
- Simple Flex Mp3 Player
- Librerías de íconos utilizadas: Librería 1, Librería 2
One Response to “Crear un Lector de Podcasts con Adobe Flex 3”
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