<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" 
                        layout="absolute" 
                        applicationComplete="init()" 
                        verticalScrollPolicy="off" 
                        horizontalScrollPolicy="off" 
                        width="400" 
                        height="530"
                        showFlexChrome="false" 
                        showStatusBar="false" 
                        viewSourceURL="srcview/index.html" 
                        xmlns:Comp="components.*" xmlns:display="flash.display.*">

    <mx:Script>
        <![CDATA[
            import mx.controls.advancedDataGridClasses.AdvancedDataGridListData;
            import mx.events.DragEvent;
        /*
            Copyright (c) 2008, Eric Carlisle
            All rights reserved.
            
            Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
            
                * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
                * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
            
            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            */
            /*
                Howler MP3 Player v0.5
                                
                Created by Eric Carlisle
                eric@ericcarlisle.com
                http://www.ericcarlisle.com
            */

            // Needed classes.
            import com.ericcarlisle.PlayModes;
            import com.ericcarlisle.PlayList;
            import com.ericcarlisle.Utility;
            import flash.desktop.NativeDragManager;
            import flash.media.Sound;
            import mx.controls.Alert;
            import mx.controls.FileSystemList;
            import mx.core.UIComponent;
            import mx.events.CloseEvent;
            import mx.events.FileEvent;
            import mx.events.ListEvent;

            
            // Player properties
            private var playMode:String;
            private var volume:uint;
            private const panning:int = 0;
            
            private var selectedFileCount:uint;
            private var loadedFileCount:uint;
            private var soundIndex:uint;

                        
            // Player objects
            private var SoundObj:Sound;
            private var Channel:SoundChannel;
            private var Transform:SoundTransform;            
            private var thumbTimer:Timer;
            private var PlayList:com.ericcarlisle.PlayList;
            private var SoundFilter:FileFilter = new FileFilter("Sounds", "*.mp3;*.wav");
            //private var PlaylistFilter:FileFilter = new FileFilter("Sounds", "*.pls;*.m3u");

            // Visualization objects.
            private var Spectrum:ByteArray;
            private const VISUALIZER_HEIGHT:Number = 50;
            private const VISUALIZER_COLOR:Number = 0x336699;
            
            
            // ID3 and other metadata
            private var ID3:ID3Info;
            private var Duration:int;
            

            /*---------- PLAYER INITIALIZER ----------*/
            
            // Initialization function used to add event handlers and set initial settings.
            private function init():void
            {
                // Set player initial settings.
                playMode = PlayModes.STOP;    
                selectedFileCount = 0;
                loadedFileCount = 0;
                soundIndex = 0;

                // Set initial application height.
                this.height= cvsControlBar.height + cvsPlayer.height;
                
                // Set volume.
                volume = sldrVolume.value;

                // Instantiate sound objects.
                Channel = new SoundChannel();
                Transform = new SoundTransform(volume/100, panning);
                PlayList = new com.ericcarlisle.PlayList();    
                
                // Bind playlist data to datagrid.
                dgPlaylist.dataProvider = PlayList.Sounds;
                
                // Create a timer to control the song position hslider.                
                thumbTimer = new Timer(500);
                thumbTimer.addEventListener(TimerEvent.TIMER, onTimerTick);
                
                // Create event handlers for application.
                this.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
                this.addEventListener(Event.ENTER_FRAME, onEnterFrame);

                this.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, onPlayerDragInto);
                this.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, onPlayerDropInto);        
                this.addEventListener(InvokeEvent.INVOKE, onInvoke);
                        
            }

            /*---------- DRAG/DROP & FILE MANAGEMENT ----------*/

            private function onInvoke(event:InvokeEvent):void
            {
                if (event.arguments.length > 0)
                {
                    var file:File;
                    var files:Array = new Array();
                    
                    for (var i:int = 0; i < event.arguments.length; i++)
                    {
                        file = new File(event.arguments[i]);    
                        files.push(file);
                    }
                    
                    if (PlayList.Sounds.length > 0) removeAllSounds();
                    loadFiles(files);
                }
            }


            // Handles file selection event dispatched by browse dialog.
            private function onFileSelect(event:FileListEvent):void
            {
                loadFiles(event.files);
            }

            // Handles folder selection event dispatched by browse dialog.
            private function onDirectorySelect(event:Event):void
            {
                var directory:File = event.target as File;
                loadFiles(directory.getDirectoryListing());
            }

            // Loads a batch of files into the playlist.
            private function loadFiles(files:Array):void
            {            

                var file:File;
                
                // Count the number of files selected.  Only accept files with .mp3 extension.
                selectedFileCount = 0;

                for (var i:uint = 0; i < files.length; i++)
                {
                    file = files[i];
                    if (file.extension == "mp3") selectedFileCount++;
                }

                // Reset the count on files currently loaded.
                loadedFileCount = 0;

                // Set the player mode so that loaded files are played automatically.
                if (PlayList.Sounds.length == 0) playMode = PlayModes.LOADTOPLAY;
                            
                // Load files as sound objects.                
                for(var j:uint = 0; j < files.length; j++)
                {
                    file = files[j];
                    if (file.extension == "mp3" || file.extension == "wav")
                    {
                        var sound:Sound = new Sound(new URLRequest(file.url));
                        sound.addEventListener(Event.ID3,onID3);
                    }
                }
            }
            
            // Presents file browse (multiple file) dialog.
            private function browseForFiles():void
            {
                var SoundFile:File = new File();
                SoundFile.browseForOpenMultiple("Open", [SoundFilter]);//, PlaylistFilter]);
                SoundFile.addEventListener(FileListEvent.SELECT_MULTIPLE, onFileSelect);
            }

            // Presents file browse (folder) dialog.
            private function browseForFolder():void
            {
                var directory:File = File.documentsDirectory;
                directory.browseForDirectory("Select Directory");
                directory.addEventListener(Event.SELECT, onDirectorySelect);
            } 

            // Accept files dragged into player.
            private function onPlayerDragInto(event:Event):void
            {
                NativeDragManager.acceptDragDrop(this);
            }    

            // Manages files dropped into player.
            private function onPlayerDropInto(event:NativeDragEvent):void
            {
                // Accept only files.
                if (event.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT))
                {
                    // Parse dragged contents into array of files.
                    var dragFiles:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
                    
                    // Load the files.
                    loadFiles(dragFiles);
                }
            }    
            
            /*---------- SOUND MANAGEMENT ----------*/
            
            private function loadSound():void
            {
                SoundObj = new Sound();
                SoundObj.load(new URLRequest(PlayList.Sounds[soundIndex]["url"]));
                SoundObj.addEventListener(Event.COMPLETE, onSoundLoaded);
            }

            private function onSoundLoaded(event:Event):void
            {
                // Retrieve data for current sound from playlist.
                var soundData:Object = PlayList.Sounds[soundIndex];
                
                // Place ID3 information into the readout panel.
                txtID3.htmlText = Utility.ReadoutHTML(soundData["title"], soundData["track"], soundData["album"], soundData["artist"], soundData["year"], soundData["duration"]);
            
                // Configure the holizontal slider to act as a playhead.
                sldrPosition.maximum = soundData["duration"];

                // Set the selected row in the playlist display.
                dgPlaylist.selectedIndex = soundIndex;
                
                // Start the player if the mode is correct.
                if (playMode == PlayModes.LOADTOPLAY)
                {
                    playSound();
                }
                else
                {
                    playMode = PlayModes.LOADED;                    
                }
                
            }

            // Plays the current sound.
            public function playSound():void
            {                    
                // Load sound into channel.
                Channel.stop();
                Channel = SoundObj.play(sldrPosition.value,0,Transform);
                playMode = PlayModes.PLAY;

                // Start position timer.
                thumbTimer.start();
                
                // Configure UI controls.
                btnPlay.visible = false;
                btnPause.visible = true;
                sldrPosition.enabled = true;
                btnPlay.enabled = true;
                btnStop.enabled = true;
                setBackForwardButtons();

                Channel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);
            }        

            private function setBackForwardButtons():void
            {
                if (soundIndex == PlayList.Sounds.length-1 || PlayList.Sounds.length == 0)
                {
                    btnForward.enabled = false;
                }
                else
                {
                    btnForward.enabled = true;
                }
                
                if (soundIndex == 0 || PlayList.Sounds.length == 0)
                {
                    btnBack.enabled = false;
                }
                else
                {
                    btnBack.enabled = true;
                }
            }

            // Stops the current sound.
            public function stopSound():void
            {
                Channel.stop();                
                
                thumbTimer.stop();
                sldrPosition.value = 0;
                playMode = PlayModes.STOP;

                Visualizer.graphics.clear();

                btnPlay.visible = true;
                btnPause.visible = false;
            }

            // Pause a playing sound.                
            private function pauseSound():void
            {
                Channel.stop();                
                thumbTimer.stop();
                btnPlay.visible = true;
                btnPause.visible = false;
            }
            
            // Change the sound index
            private function changeSoundIndex(delta:int):void
            {
                stopSound();
                playMode = PlayModes.LOADTOPLAY;
                soundIndex = soundIndex + delta;
                loadSound();
            }
            

            // Change the volume and panning via the sound transform object.
            private function ChangeSoundTransform():void
            {
                volume = Math.round(sldrVolume.value);
                Channel.soundTransform = new SoundTransform(volume/100, panning);
            }
            
            // Handles event for sound completing.
            private function onSoundComplete(event:Event):void
            {
                stopSound();
                soundIndex++;
                
                if (soundIndex < PlayList.Sounds.length)
                {
                    playMode = PlayModes.LOADTOPLAY;
                    loadSound();
                }
                
            }

            // Load ID3 information into local variables.  
            // Update the readout panel.
            // Configure position slider.
            private function onID3(event:Event):void
            {
                var sound:Sound = Sound(event.target);
                ID3 = ID3Info(sound.id3);
                Duration = Math.floor(sound.length);
                
                // Load sound id3 data into the playlist.
                PlayList.AddSound(ID3.songName, ID3.album, ID3.artist, ID3.track, ID3.year, ID3.genre, Duration, sound.url);
                
                // Increment the loaded file count.
                loadedFileCount++;

                if (loadedFileCount == selectedFileCount * 2)
                {
                    // Refresh the playlist so that new results will be visually displayed.
                    PlayList.Sounds.refresh();
                    
                    // Set the count properties.
                    selectedFileCount = 0;
                    loadedFileCount = 0;

                    soundIndex = 0;
                    if (playMode == PlayModes.LOADTOPLAY) loadSound();
                }
                
            }

            /*---------- VISUALIZATION ----------*/
            
            private function UpdateVisualizer():void
            {
                // Instantiate a new byte array to contain spectrum data.
                Spectrum = new ByteArray();
                
                // Clear the visualizer graphics.
                Visualizer.graphics.clear();
                    
                // Dump the spectrum data into the byte array.
                SoundMixer.computeSpectrum(Spectrum,false,0);
                
                var f:Number;
                var i:int;
                var ave:int;
                
                Visualizer.graphics.lineStyle(1, VISUALIZER_COLOR,1);
                   Visualizer.graphics.beginFill(VISUALIZER_COLOR, 0.75);
                

                for (i = 0; i < 512; i=i+10) 
                {
                    f = Spectrum.readFloat();
                    Visualizer.drawRoundRect(Math.floor(i*0.7) + 7, cvsReadout.height - 10, 4, -Math.abs(f) * (cvsReadout.height-10));
                }
                Visualizer.graphics.endFill();

            }

            




            // Updates the position of the hslider thumb.
            private function onTimerTick(event:TimerEvent):void
            {
                sldrPosition.value = Math.round(Channel.position);
            }


            // Update the wave visualizer if the sound is playing.
            private function onEnterFrame(event:Event):void
            {
                if (playMode == PlayModes.PLAY)
                {
                    UpdateVisualizer();
                }
            }

            // Show application information.
            private function showHowlerInfo():void
            {
                cvsAbout.visible = true;
            }

            private function togglePlayList():void
            {
                if (this.height == cvsControlBar.height + cvsPlayer.height)
                {
                    this.height = cvsControlBar.height + cvsPlayer.height + cvsPlaylist.height;                
                }
                else
                {
                    this.height = cvsControlBar.height + cvsPlayer.height;                
                }
            }
            
            private function onItemDoubleClick(event:ListEvent):void
            {
                soundIndex = event.rowIndex;
                this.playMode = PlayModes.LOADTOPLAY;
                thumbTimer.stop();
                sldrPosition.value = 0;
                loadSound();
            }

            /*---------- ERROR HANDLING ----------*/
            // Handles IO errors.
            private function onIOError(event:IOErrorEvent):void
            {
                Alert.show("File load error: " + event.text);
            }
            
            private function startMove():void
            {
                stage.nativeWindow.startMove();
            }
            
            private function onPlaylistDragDrop(event:DragEvent):void
            {
                // Capture array of data corresponding to dragged item.
                var item:Array = event.dragSource.dataForFormat("items") as Array;

                // Identify playlist datagrid.
                var target:DataGrid = DataGrid(event.currentTarget);

                // Identify the index for which the item was dropped.
                var index:int = target.calculateDropIndex(event);

                // Add the data back to the playlist datasource at the correct index.
                PlayList.AddSoundAt(item[0].title, item[0].album, item[0].artist, item[0].track, item[0].year, item[0].genre, item[0].duration, item[0].url, index);
                    
            }
            
            private function unloadSound():void
            {
                stopSound();
                txtID3.text = "";
                btnPlay.visible = true;
                btnPause.visible = false;
                btnPlay.enabled = false;
                btnStop.enabled = false;
            }
            
            private function removeSound():void
            {
                var index:int = dgPlaylist.selectedIndex;
                
                if (index >= 0)
                {
                    if (index == soundIndex)
                    {
                        unloadSound();
                    }
                    PlayList.RemoveSoundAt(index);
                    PlayList.Sounds.refresh();
                    setBackForwardButtons();
                }
            }

            private function removeAllSounds():void
            {
                unloadSound();
                PlayList.Sounds.removeAll();
                PlayList.Sounds.refresh();
                setBackForwardButtons();
            }
            
            private function onKeyDown(event:KeyboardEvent):void
            {
                if (event.charCode.toString() == "8" || event.charCode.toString() == "127")
                {
                    removeSound();            
                }
            }

        ]]>
    </mx:Script>
    
    <mx:Style source="howler.css" />    

    <!-- Control Bar Canvas -->
    <mx:Canvas id="cvsControlBar" 
               styleName="ControlBar" 
               mouseDown="startMove()" 
               width="400" 
               height="20"
               x="0" 
               y="0" 
               horizontalScrollPolicy="off" 
               verticalScrollPolicy="off">
               
        <mx:Text text="HOWLER v0.5" 
                 styleName="Title" 
                 selectable="false" 
                 y="1" 
                 x="3"/>
        
        <mx:Button styleName="Close" 
                   click="close()"
                   x="383" 
                   y="4"/>

        <mx:Button styleName="Minimize" 
                   click="minimize()"
                   x="368" 
                   y="4"/>
    </mx:Canvas>
    
    <mx:Canvas id="cvsPlayer" 
               styleName="Player" 
               horizontalScrollPolicy="off" 
               verticalScrollPolicy="off" y="20" width="400" x="0" height="160">

        <mx:Canvas id="cvsReadout" 
                   styleName="Readout" 
                   width="385" 
                   height="80" 
                   x="8" 
                   y="7">
            
            <mx:Text x="2" 
                     y="5" 
                     id="txtID3" 
                     color="#000000" 
                     height="74" 
                     fontSize="17" 
                     fontFamily="Arial" 
                     selectable="false" 
                     width="383" />

            <mx:UIComponent id="Visualizer" 
                            height="65" 
                            width="375"
                            x="5" 
                            y="5" 
                            alpha="0.25" />

        </mx:Canvas>

        <mx:Button id="btnBack" 
                   styleName="Back" 
                   x="10" 
                   y="113" 
                   click="changeSoundIndex(-1)"
                   enabled="false" />

        <mx:Button id="btnStop" 
                   styleName="Stop" 
                   x="47" 
                   y="113" 
                   click="stopSound()" 
                   enabled="false" />

        <mx:Button id="btnPlay" 
                   styleName="Play" 
                   x="84" 
                   y="113" 
                   width="37" 
                   height="30"
                   click="playSound()" 
                   enabled="false" />
                   
        <mx:Button id="btnPause" 
                   styleName="Pause" 
                   x="84" 
                   y="113" 
                   width="37" 
                   height="30"
                   click="pauseSound()" 
                   visible="false" />

        <mx:Button id="btnForward" 
                   styleName="Forward" 
                   x="121" 
                   y="113" 
                   click="changeSoundIndex(1)"
                   enabled="false" />


        <mx:Button id="btnAbout" 
                   styleName="About" 
                   toolTip="About Howler"
                   x="327" 
                   y="109" 
                   click="showHowlerInfo()" />
                
        <mx:Button styleName="TogglePlaylist" 
                   x="293" 
                   y="109" 
                   click="togglePlayList()" 
                   toolTip="Toggle Playlist" />

        <mx:Button id="btnAddFile" 
                   styleName="AddFile"
                   click="browseForFiles()" 
                   x="222" 
                   y="109" 
                   width="34"
                   height="36" 
                   toolTip="Add Files" />
    
        <mx:Button id="btnAddFolder" 
                   styleName="AddFolder"
                   click="browseForFolder()" 
                   x="256" 
                   y="109" 
                   width="37"
                   height="36" 
                   toolTip="Add Folder" />
    
        <mx:Image x="158" 
                  y="113" 
                  source="@Embed('assets/Toolbar_Border.png')"/>
    
    
        <mx:HSlider id="sldrPosition" 
                    x="8" 
                    y="92" 
                    width="354" 
                    minimum="0"
                    enabled="false" 
                    change="playSound()" 
                    mouseDown="thumbTimer.stop()" 
                    mouseUp="thumbTimer.start()"
                    dataTipFormatFunction="Utility.FormatDuration"  height="12"/>
        
        <mx:VSlider id="sldrVolume" 
                    x="372" 
                    y="102" 
                    height="50" 
                    minimum="0" 
                    maximum="100" 
                    value="100" 
                    change="ChangeSoundTransform()" 
                    dataTipFormatFunction="Utility.FormatVolume"  width="17"/>
                
    <mx:Label x="371" y="89" text="VOL" fontSize="8" color="#FFFFFF"  width="24" height="16" fontWeight="bold"/>
    

    </mx:Canvas>
    <mx:Canvas id="cvsPlaylist" 
               styleName="Playlist" 
               width="400"
               height="350" 
               x="0"
               y="179" 
               horizontalScrollPolicy="off" 
               verticalScrollPolicy="off">


    
        <mx:DataGrid x="6" 
                     y="7" 
                     width="388"
                     height="310"
                     id="dgPlaylist"
                     keyDown="onKeyDown(event)"
                     dragMoveEnabled="true"
                     doubleClickEnabled="true"
                     dragEnabled="true"
                     dropEnabled="true"
                     dragComplete="onPlaylistDragDrop(event)"
                     itemDoubleClick="onItemDoubleClick(event)">
            <mx:columns>
                <mx:DataGridColumn width="168" headerText="Title" dataField="title" />
                <mx:DataGridColumn width="160" headerText="Album" dataField="album"/>
                <mx:DataGridColumn width="60" headerText="Duration" dataField="duration" textAlign="right" itemRenderer="components.DurationFormatter"/>
            </mx:columns>
        </mx:DataGrid>
        
        <mx:Button x="6" 
                   y="323" 
                   styleName="Remove" 
                   width="20" 
                   height="18"
                   click="removeSound()"
                    />

        <mx:Button x="31" 
                   y="323" 
                   styleName="RemoveAll" 
                   width="47" 
                   height="18" 
                   click="removeAllSounds()"
                   />

    </mx:Canvas>

    <!-- "About" Canvas -->
    <mx:Canvas id="cvsAbout"
               width="360" 
               height="120"
               top="35"
               left="20" 
               styleName="About" 
               visible="false">
        
        <mx:Text fontWeight="bold" fontSize="14" fontFamily="Arial" text="Howler v0.5"  x="0" width="360" textAlign="center" y="8"/>
        <mx:Text fontSize="12" fontFamily="Arial" text="An MP3 Player for the AIR platform&#xa;(c) Copyright 2008, Eric Carlisle"  y="27" x="79" width="202" height="40" textAlign="center"/>
        <mx:LinkButton x="97" y="63" label="http://www.ericcarlisle.com" fontWeight="normal" fontSize="12" color="#003366" fontFamily="Arial" textAlign="center" textDecoration="underline" click="navigateToURL(new URLRequest('http://www.ericcarlisle.com'),'_blank')"/>
        <mx:Button x="147" y="91" width="59" height="18" styleName="Groovy" click="cvsAbout.visible = false" />

        <mx:filters>
            <mx:DropShadowFilter />
        </mx:filters>
    </mx:Canvas>    

</mx:WindowedApplication>