<?xml version="1.0"?>
<!--                                                               -->
<!-- Apache Flex 4 source file of APlayer.swf,                     -->
<!-- a FlashPlayer-10 compatible component for playing             -->
<!-- MP3 audio files and streams.                                  -->
<!--                                                               -->
<!-- version 20150601                                              -->
<!--                                                               -->
<!--                                                               -->
<!-- The free Apache Flex 4 SDK is required to compile             -->
<!-- this file. Get it from                                        -->
<!--                                                               -->
<!--         http://flex.apache.org/download-binaries.html         -->
<!--                                                               -->
<!-- and run                                                       -->
<!--                                                               -->
<!-- mxmlc -static-link-runtime-shared-libraries APlayer.mxml      -->
<!--                                                               -->
<!-- on the command line.                                          -->
<!--                                                               -->
<!--                                                               -->
<!-- Copyright (C) 2012-today  Alexander Grahn                     -->
<!--                                                               -->
<!-- This work may be distributed and/or modified under the        -->
<!-- conditions of the LaTeX Project Public License.               -->
<!--                                                               -->
<!-- The latest version of this license is in                      -->
<!--   http://www.latex-project.org/lppl.txt                       -->
<!--                                                               -->
<!-- This work has the LPPL maintenance status `maintained'.       -->
<!--                                                               -->
<!-- The current maintainer of this work is A. Grahn.              -->
<!--                                                               -->

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
  xmlns:s="library://ns.adobe.com/flex/spark"
  xmlns:mx="library://ns.adobe.com/flex/mx"
  preinitialize="initialise(this.parameters);"
  applicationComplete="initSound();addEventListeners();
    fadeTargets=new Array(playProgress, caption);"
  creationComplete="initCallBacks();initContext();"
  mouseDown="pause();setFocus();"
  mouseUp="play();"
  backgroundAlpha="0"
>

  <fx:Script>
    <![CDATA[
      [Bindable] private var source:String;
      [Bindable] private var policy:String=null;
      [Bindable] private var autoPlay:Boolean=false;
      [Bindable] private var loop:Boolean=false;
      [Bindable] private var vol:Number=0.75;
      [Bindable] private var pan:Number=0;
      [Bindable] private var hideBar:Boolean=false;
      [Bindable] private var fadeTargets:Array;
      [Bindable] private var txtAlign:String;

      private var snd:Sound;
      private var id3:ID3Info;
      private var sndCh:SoundChannel;
      private var sndTr:SoundTransform;
      private var playResumePos:Number = 0;
      private var pauseAtPos:Number = -1;
      private var toBePaused:Boolean = false;
      private var plyng:Boolean = false;
      private var muted:Boolean = false;
      private var lastVol:Number;

      private var deltaSeek:Number;
      private var curTime:Number;
      private var keyPressed:Boolean=false;
      private var mouseIsOver:Boolean=false;

      private function initialise(flashVars:Object):void {
        source=flashVars.source;
        if(flashVars.policy) policy=flashVars.policy;
        if(flashVars.autoPlay=='true') autoPlay=true;
        if(flashVars.loop=='true') loop=true;
        if(flashVars.volume) vol=Number(flashVars.volume);
        if(flashVars.balance) pan=Number(flashVars.balance);
        if(flashVars.hideBar=='true') hideBar=true;
      }

      import mx.controls.Alert;

      private function initSound():void {
        snd = new Sound(); id3=null;
        sndTr = new SoundTransform(vol, pan);
        snd.addEventListener(Event.ID3, onID3Info);
        snd.addEventListener(IOErrorEvent.IO_ERROR, onError);
        snd.addEventListener(ProgressEvent.PROGRESS, onProgress);
        snd.addEventListener(Event.COMPLETE, onComplete);
        if(policy) Security.loadPolicyFile(policy);
        snd.load(new URLRequest(source), new SoundLoaderContext(1000, true));
        if(autoPlay) play();
      }

      private function onMouseOver(e:MouseEvent):void {
        mouseIsOver=true;
        fadeEffect.end();
        playProgress.alpha=caption.alpha=1.0;
      }

      private function onMouseOut(e:MouseEvent):void {
        mouseIsOver=false;
        fadeEffect.play();
      }

      private function onKeyDown(e:KeyboardEvent):void {
        switch(e.keyCode) {
          case 32: //space bar
            playPause();
            break;
          case 36: //home
            pause();
            playResumePos=(0);
            break;
          case 35: //end
            if(playProgress.indeterminate) break;
            pause();
            playResumePos=(snd.length);
            break;
          case 37: //<--
            fadeEffect.end();
            playProgress.alpha=caption.alpha=1.0;
            if(e.ctrlKey){
              pan=Math.max(-1,pan-0.025);
              balance(pan);
              break;
            }
            if(!keyPressed){
              deltaSeek=Math.max(100,snd.length/10000);
            if(plyng)
              curTime=sndCh.position;
            else
              curTime=playResumePos;
            }
            keyPressed=true;
            playResumePos=Math.max(0,curTime-deltaSeek);
            if(plyng) seek(playResumePos/1000);
            deltaSeek*=1.1;
            break;
          case 39: //-->
            fadeEffect.end();
            playProgress.alpha=caption.alpha=1.0;
            if(e.ctrlKey){
              pan=Math.min(1,pan+0.025);
              balance(pan);
              break;
            }
            if(!keyPressed){
              deltaSeek=Math.max(100,snd.length/10000);
            if(plyng)
              curTime=sndCh.position;
            else
              curTime=playResumePos;
            }
            keyPressed=true;
            playResumePos=Math.min(snd.length-10,curTime+deltaSeek);
            seek(playResumePos/1000);
            deltaSeek*=1.1;
            break;
          case 38:
            vol=Math.min(1,vol+0.025);
            volume(vol);
            break;
          case 40:
            if(e.ctrlKey){
              pan=0;
              balance(pan);
              break;
            }
            vol=Math.max(0,vol-0.025);
            volume(vol);
            break;
          default:
          if(e.charCode==109) mute(); //`m'
        }
      }

      private function onKeyUp(e:KeyboardEvent):void {
        switch(e.keyCode) {
          case 37: //<--
          case 39: //-->
            deltaSeek=Math.max(100,snd.length/10000);
            keyPressed=false;
            if (!mouseIsOver) fadeEffect.play();
            break;
        }
      }

      private function formatLabel(s:Number):String {
        s/=1000;
        var hrs:Number = Math.floor(s / 3600);
        var min:Number = Math.floor(s / 60 % 60);
        var sec:Number = Math.floor(s % 60);
        var fmtd:String='';
        if(hrs>0) fmtd = String(hrs)+':';
        if(hrs>0 && min <10) fmtd+='0';
        fmtd += String(min)+':';
        if(sec<10) fmtd+='0';
        fmtd += String(sec);
        txtAlign="center";
        try{
          if(id3.songName)
            fmtd += ' '+ String.fromCharCode(0x2014) + ' ' + id3.songName;
          if(id3.artist) fmtd += ' | ' + id3.artist;
          if(id3.album) fmtd += ' | ' + id3.album;
          if(id3) txtAlign="start";
        }catch(e:Object){}
        return fmtd;
      }

      private function addEventListeners():void {
        this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
        this.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        this.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        this.addEventListener(MouseEvent.MOUSE_OUT, onMouseOut);
        this.addEventListener(MouseEvent.MOUSE_OVER, onMouseOver);
      }

      private function setSource(s:String):void {
        pause();
        source=s;
        initSound();
        pause();seek(0);
      }

      private function loadPolicy(p:String):void {policy=p;}

      private function play(p:Number=-1):void {
        if(p>=0) seek(p);
        if(plyng) return;
        try{sndCh = snd.play(playResumePos, 0, sndTr);}
        catch(e:Error){Alert.show(e.message,'Error');}
        sndCh.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);
        plyng = true;
      }

      private function pause(p:Number=-1):void {
        if(p>=0){pauseAtPos=p*1000;return;}
        if(!plyng) return;
        playResumePos = sndCh.position;
        sndCh.stop();
        plyng = false;
      }

      private function playPause():void {
        if(plyng) pause(); else play();
      }

      private function seek(p:Number):void {
        playResumePos = p*1000;
        if(!plyng) return;
        sndCh.stop();
        plyng = false;
        play();
      }

      private function rewind():void {
        seek(0);
      }

      private function volume(v:Number):void {
        sndTr.volume = v;
        sndCh.soundTransform=sndTr;
      }

      private function mute():void {
        if(muted) {
          if(lastVol==0) lastVol=0.75;
          volume(lastVol);
          muted=false;
        }
        else {
          lastVol = sndTr.volume
          volume(0);
          muted=true;
        }
      }

      private function balance(p:Number):void {
        sndTr.pan = p;
        sndCh.soundTransform=sndTr;
      }

      private function currentTime():Number {
        return sndCh.position/1000;
      }

      private function playing():Boolean {
        return plyng;
      }

      private function duration():Number {
        return snd.length/1000;
      }

      private function ismuted():Boolean {
        return muted;
      }

      private function initCallBacks():void {
        ExternalInterface.addCallback("play", play);
        ExternalInterface.addCallback("pause", pause);
        ExternalInterface.addCallback("playPause", playPause);
        ExternalInterface.addCallback("seek", seek);
        ExternalInterface.addCallback("rewind", rewind);
        ExternalInterface.addCallback("volume", volume);
        ExternalInterface.addCallback("balance", balance);
        ExternalInterface.addCallback("mute", mute);
        ExternalInterface.addCallback("setSource", setSource);
        ExternalInterface.addCallback("loadPolicy", loadPolicy);
        ExternalInterface.addCallback("currentTime", currentTime);
        ExternalInterface.addCallback("duration", duration);
        ExternalInterface.addCallback("playing", playing);
        ExternalInterface.addCallback("muted", ismuted);
      }

      private function initContext():void {
        this.contextMenu.hideBuiltInItems();

        var itemPlayPause:ContextMenuItem = new ContextMenuItem("N.N.");
        this.contextMenu.customItems.push(itemPlayPause);
        itemPlayPause.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
          function(e:ContextMenuEvent):void{playPause();});

        var itemRewind:ContextMenuItem = new ContextMenuItem("Rewind, [Home]");
        this.contextMenu.customItems.push(itemRewind);
        itemRewind.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
          function(e:ContextMenuEvent):void{pause();playResumePos=(0);});

        var itemGotoEnd:ContextMenuItem=new ContextMenuItem("Goto End, [End]");
        this.contextMenu.customItems.push(itemGotoEnd);
        itemGotoEnd.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
          function(e:ContextMenuEvent):void{
            if(playProgress.indeterminate) return;
            pause();playResumePos=(snd.length);});

        var itemMute:ContextMenuItem = new ContextMenuItem("N.N.");
        this.contextMenu.customItems.push(itemMute);
        itemMute.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
          function(e:ContextMenuEvent):void{mute();});

        this.contextMenu.customItems.push(
          new ContextMenuItem("via keyboard:", true, false, true));
        this.contextMenu.customItems.push(
          new ContextMenuItem("Seek, [\u2190]/[\u2192]", false, false, true));
        this.contextMenu.customItems.push(
          new ContextMenuItem("Volume, [\u2191]/[\u2193]", false, false, true));
        this.contextMenu.customItems.push(
          new ContextMenuItem("Balance, [Ctrl]+[\u2190]/[\u2193]/[\u2192]",
          false, false, true));

        this.contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT,
          function(e:ContextMenuEvent):void{
            itemPlayPause.caption=(plyng ? "Pause" : "Play")+", [Space]";
            itemMute.caption=(muted ? "Unmute" : "Mute")+", [m]";}
        );
      }

      private function onSoundComplete(e:Event):void {
        plyng = false;
        playResumePos = 0;
        if(loop) play();
      }

      private function onComplete(e:Event):void {
        playProgress.indeterminate=false;
      }

      private function onProgress(e:ProgressEvent):void {
        playProgress.indeterminate=true;
      }

      private function onEnterFrame(e:Event):void {
        if(!playProgress.indeterminate) {
          if(plyng) {
            playProgress.setProgress(sndCh.position,snd.length);
            caption.text=formatLabel(sndCh.position);
          } else {
            playProgress.setProgress(playResumePos,snd.length);
            caption.text=formatLabel(playResumePos);
          }
        }else{
          if(plyng) {
            playProgress.setProgress(Math.random(),1);
            caption.text=formatLabel(sndCh.position);
          } else {
            playProgress.setProgress(snd.bytesLoaded,snd.bytesTotal);
            caption.text=formatLabel(playResumePos);
          }
        }
        if(plyng&&pauseAtPos>=0&&sndCh.position<pauseAtPos)
          toBePaused=true;
        if(
          plyng&&pauseAtPos>=0&&
          sndCh.position>=pauseAtPos&&toBePaused
        ){
          pause();
          pauseAtPos=-1;
          toBePaused=false;
        }
      }

      private function onID3Info(e:Event):void {id3 = e.target.id3;}

      private function onError(e:ErrorEvent):void {
        Alert.show(e.type, e.text);
      }
    ]]>
  </fx:Script>

  <fx:Declarations>
    <s:Fade id="fadeEffect" targets="{fadeTargets}" alphaFrom="1.0" alphaTo="0"
      duration="2000"/>
  </fx:Declarations>

  <mx:ProgressBar width="100%" height="100%" mode="manual"
    labelPlacement="center" label="" id="playProgress"
    visible="{!hideBar}"
  />

  <s:Label id="caption" width="100%" height="100%" verticalCenter="0"
    paddingTop="2" fontWeight="bold" verticalAlign="middle" visible="{!hideBar}"
    textAlign="{txtAlign}" paddingLeft="5"
  />
</s:Application>
