package {
  import com.axis.audioclient.AxisTransmit;
  import com.axis.ClientEvent;
  import com.axis.ErrorManager;
  import com.axis.http.url;
  import com.axis.httpclient.HTTPClient;
  import com.axis.IClient;
  import com.axis.Logger;
  import com.axis.mjpegclient.MJPEGClient;
  import com.axis.rtmpclient.RTMPClient;
  import com.axis.rtspclient.IRTSPHandle;
  import com.axis.rtspclient.RTSPClient;
  import com.axis.rtspclient.RTSPoverHTTPHandle;
  import com.axis.rtspclient.RTSPoverTCPHandle;

  import flash.display.LoaderInfo;
  import flash.display.Sprite;
  import flash.display.Stage;
  import flash.display.StageAlign;
  import flash.display.StageDisplayState;
  import flash.display.StageScaleMode;
  import flash.display.DisplayObject;
  import flash.display.InteractiveObject;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.external.ExternalInterface;
  import flash.media.Microphone;
  import flash.media.SoundMixer;
  import flash.media.SoundTransform;
  import flash.media.Video;
  import flash.net.NetStream;
  import flash.system.Security;
  import mx.utils.StringUtil;

  [SWF(frameRate="60")]
  [SWF(backgroundColor="#efefef")]

  public class Player extends Sprite {
    [Embed(source = "../VERSION", mimeType = "application/octet-stream")] private var Version:Class;

    public static var locomoteID:String = null;
//    public static var debugLogger:Boolean = false; //not used

    private static const EVENT_STREAM_STARTED:String  = "streamStarted";
    private static const EVENT_STREAM_PAUSED:String  = "streamPaused";
    private static const EVENT_STREAM_STOPPED:String  = "streamStopped";
    private static const EVENT_STREAM_ENDED:String  = "streamEnded";
    private static const EVENT_FULLSCREEN_ENTERED:String  = "fullscreenEntered";
    private static const EVENT_FULLSCREEN_EXITED:String  = "fullscreenExited";

    public static var config:Object = {
      'buffer': 3,
      'connectionTimeout': 10,
      'scaleUp': false,
      'allowFullscreen': true,
      'debugLogger': false
    };
    private var audioTransmit:AxisTransmit = new AxisTransmit();
    private var meta:Object = {};
    private var client:IClient;
    private var urlParsed:Object;
    private var savedSpeakerVolume:Number;
    private var fullscreenAllowed:Boolean = true;
    private var currentState:String = "stopped";
    private var streamHasAudio:Boolean = false;
    private var streamHasVideo:Boolean = false;
    private var newPlaylistItem:Boolean = false;

    public function Player() {
      var self:Player = this;

      Security.allowDomain("*");
      Security.allowInsecureDomain("*");

      trace('Loaded Locomote, version ' + StringUtil.trim(new Version().toString()));

      if (ExternalInterface.available) {
          setupAPICallbacks();
      } else {
        trace("External interface is not available for this container.");
      }

      /* Set default speaker volume */
      this.speakerVolume(50);

      /* Stage setup */
      this.stage.align = StageAlign.TOP_LEFT;
      this.stage.scaleMode = StageScaleMode.NO_SCALE;
      addEventListener(Event.ADDED_TO_STAGE, onStageAdded);

      /* Fullscreen support setup */
      this.stage.doubleClickEnabled = true;
      this.stage.addEventListener(MouseEvent.DOUBLE_CLICK, fullscreen);
      this.stage.addEventListener(Event.FULLSCREEN, function(event:Event):void {
        (StageDisplayState.NORMAL === stage.displayState) ? callAPI(EVENT_FULLSCREEN_EXITED) : callAPI(EVENT_FULLSCREEN_ENTERED);
      });
      this.stage.addEventListener(Event.RESIZE, function(event:Event):void {
        videoResize();
      });

      this.setConfig(Player.config);
    }

    /**
     * Registers the appropriate API functions with the container, so that
     * they can be called, and triggers the apiReady event
     * which tells the container that the Player is ready to receive API calls.
     */
    public function setupAPICallbacks():void {
      ExternalInterface.marshallExceptions = true;

      /* Media player API */
      ExternalInterface.addCallback("play", play);
      ExternalInterface.addCallback("pause", pause);
      ExternalInterface.addCallback("resume", resume);
      ExternalInterface.addCallback("stop", stop);
      ExternalInterface.addCallback("seek", seek);
      ExternalInterface.addCallback("streamStatus", streamStatus);
      ExternalInterface.addCallback("playerStatus", playerStatus);
      ExternalInterface.addCallback("speakerVolume", speakerVolume);
      ExternalInterface.addCallback("muteSpeaker", muteSpeaker);
      ExternalInterface.addCallback("unmuteSpeaker", unmuteSpeaker);
      ExternalInterface.addCallback("microphoneVolume", microphoneVolume);
      ExternalInterface.addCallback("muteMicrophone", muteMicrophone);
      ExternalInterface.addCallback("unmuteMicrophone", unmuteMicrophone);
      ExternalInterface.addCallback("setConfig", setConfig);

      /* Audio Transmission API */
      ExternalInterface.addCallback("startAudioTransmit", startAudioTransmit);
      ExternalInterface.addCallback("stopAudioTransmit", stopAudioTransmit);
    }

    public function fullscreen(event:MouseEvent):void {
      if (config.allowFullscreen) {
        this.stage.displayState = (StageDisplayState.NORMAL === stage.displayState) ?
          StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
      }
    }

    public function videoResize():void {
      var stagewidth:uint = (StageDisplayState.NORMAL === stage.displayState) ?
        stage.stageWidth : stage.fullScreenWidth;
      var stageheight:uint = (StageDisplayState.NORMAL === stage.displayState) ?
        stage.stageHeight : stage.fullScreenHeight;

      var video:DisplayObject = this.client.getDisplayObject();

      var scale:Number = ((stagewidth / meta.width) > (stageheight / meta.height)) ?
        (stageheight / meta.height) : (stagewidth / meta.width);

      video.width = meta.width;
      video.height = meta.height;
      if ((scale < 1.0) || (scale > 1.0 && true === config.scaleUp)) {
        Logger.log('scaling video, scale:' + scale.toFixed(2) + ' (aspect ratio: ' +  (video.width / video.height).toFixed(2) + ')');
        video.width = meta.width * scale;
        video.height = meta.height * scale;
      }

      video.x = (stagewidth - video.width) / 2;
      video.y = (stageheight - video.height) / 2;
    }

    public function setConfig(iconfig:Object):void {
      if (iconfig.buffer !== undefined) {
        if (this.client) {
          if (false === this.client.setBuffer(config.buffer)) {
            ErrorManager.dispatchError(830);
          } else {
            config.buffer = iconfig.buffer;
          }
        }
      }

      if (iconfig.scaleUp !== undefined) {
        var scaleUpChanged:Boolean = (config.scaleUp !== iconfig.scaleUp);
        config.scaleUp = iconfig.scaleUp;
        if (scaleUpChanged && this.client)
          this.videoResize();
      }

      if (iconfig.allowFullscreen !== undefined) {
        config.allowFullscreen = iconfig.allowFullscreen;

        if (!config.allowFullscreen)
          this.stage.displayState = StageDisplayState.NORMAL;
      }

      if (iconfig.debugLogger !== undefined) {
        config.debugLogger = iconfig.debugLogger;
      }

      if (iconfig.connectionTimeout !== undefined) {
        config.connectionTimeout = iconfig.connectionTimeout;
      }
    }

    public function play(param:* = null):void {
      this.streamHasAudio = false;
      this.streamHasVideo = false;

      if (param is String) {
        urlParsed = url.parse(String(param));
      } else {
        urlParsed = url.parse(param.url);
        urlParsed.connect = param.url;
        urlParsed.streamName = param.streamName;
      }

      this.newPlaylistItem = true;

      if (client) {
        /* Stop the client, and 'onStopped' will start the new stream. */
        client.stop();
        return;
      }

      start();
    }

    private function start():void {
      switch (urlParsed.protocol) {
      case 'rtsph':
        /* RTSP over HTTP */
        client = new RTSPClient(urlParsed, new RTSPoverHTTPHandle(urlParsed));
        break;

      case 'rtsp':
        /* RTSP over TCP */
        client = new RTSPClient(urlParsed, new RTSPoverTCPHandle(urlParsed));
        break;

      case 'http':
      case 'https':
        /* Progressive download over HTTP */
        client = new HTTPClient(urlParsed);
        break;

      case 'httpm':
        /* Progressive mjpg download over HTTP (x-mixed-replace) */
        client = new MJPEGClient(urlParsed);
        break;

      case 'rtmp':
      case 'rtmps':
      case 'rtmpt':
        /* RTMP */
        client = new RTMPClient(urlParsed);
        break;

      default:
        ErrorManager.dispatchError(811, [urlParsed.protocol])
        return;
      }

      addChild(this.client.getDisplayObject());

      client.addEventListener(ClientEvent.STOPPED, onStopped);
      client.addEventListener(ClientEvent.START_PLAY, onStartPlay);
      client.addEventListener(ClientEvent.PAUSED, onPaused);
      client.addEventListener(ClientEvent.META, onMeta);
      client.start();
      this.newPlaylistItem = false;
    }

    public function seek(position:String):void{
      if (!client || !client.seek(Number(position))) {
        ErrorManager.dispatchError(828);
      }
    }

    public function pause():void {
      try {
        client.pause()
      } catch (err:Error) {
        ErrorManager.dispatchError(808);
      }
    }

    public function resume():void {
      if (!client || !client.resume()) {
        ErrorManager.dispatchError(809);
      }
    }

    public function stop():void {
      if (!client || !client.stop()) {
        ErrorManager.dispatchError(810);
        return;
      }

      this.currentState = "stopped";
      this.streamHasAudio = false;
      this.streamHasVideo = false;
    }

    public function onMeta(event:ClientEvent):void {
      this.meta = event.data;
      this.videoResize();
    }

    public function streamStatus():Object {
      if (this.currentState === 'playing') {
        this.streamHasAudio = (this.streamHasAudio || this.client.hasAudio());
        this.streamHasVideo = (this.streamHasVideo || this.client.hasVideo());
      }
      var status:Object = {
        'fps': (this.client) ? this.client.currentFPS() : null,
        'resolution': (this.client) ? { width: meta.width, height: meta.height } : null,
        'playbackSpeed': (this.client) ? 1.0 : null,
        'protocol': (this.urlParsed) ? this.urlParsed.protocol : null,
        'audio': (this.client) ? this.streamHasAudio : null,
        'video': (this.client) ? this.streamHasVideo : null,
        'state': this.currentState,
        'streamURL': (this.urlParsed) ? this.urlParsed.full : null,
        'duration': meta.duration ? meta.duration : null
      };

      return status;
    }

    public function playerStatus():Object {
      var mic:Microphone = Microphone.getMicrophone();

      var status:Object = {
        'version': StringUtil.trim(new Version().toString()),
        'microphoneVolume': audioTransmit.microphoneVolume,
        'speakerVolume': this.savedSpeakerVolume,
        'microphoneMuted': (mic.gain === 0),
        'speakerMuted': (flash.media.SoundMixer.soundTransform.volume === 0),
        'fullscreen': (StageDisplayState.FULL_SCREEN === stage.displayState),
        'buffer': (client === null) ? 0 : Player.config.buffer
      };

      return status;
    }

    public function speakerVolume(volume:Number):void {
      this.savedSpeakerVolume = volume;
      var transform:SoundTransform = new SoundTransform(volume / 100.0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function muteSpeaker():void {
      var transform:SoundTransform = new SoundTransform(0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function unmuteSpeaker():void {
      if (flash.media.SoundMixer.soundTransform.volume !== 0)
        return;

      var transform:SoundTransform = new SoundTransform(this.savedSpeakerVolume / 100.0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function microphoneVolume(volume:Number):void {
      audioTransmit.microphoneVolume = volume;
    }

    public function muteMicrophone():void {
      audioTransmit.muteMicrophone();
    }

    public function unmuteMicrophone():void {
      audioTransmit.unmuteMicrophone();
    }

    public function startAudioTransmit(url:String = null, type:String = 'axis'):void {
      if (type === 'axis') {
        audioTransmit.start(url);
      } else {
        ErrorManager.dispatchError(812);
      }
    }

    public function stopAudioTransmit():void {
      audioTransmit.stop();
    }

    public function allowFullscreen(state:Boolean):void {
      this.fullscreenAllowed = state;

      if (!state)
        this.stage.displayState = StageDisplayState.NORMAL;
    }

    private function onStageAdded(e:Event):void {
      Player.locomoteID = LoaderInfo(this.root.loaderInfo).parameters.locomoteID.toString();
      ExternalInterface.call("LocomoteMap['" + Player.locomoteID + "'].__swfReady");
    }

    private function onStartPlay(event:ClientEvent):void {
      this.currentState = "playing";
      this.callAPI(EVENT_STREAM_STARTED);
    }

    private function onPaused(event:ClientEvent):void {
      this.currentState = "paused";
      this.callAPI(EVENT_STREAM_PAUSED, event.data);
    }

    private function onStopped(event:ClientEvent):void {
      this.removeChild(this.client.getDisplayObject());
      this.client = null;
      this.callAPI(EVENT_STREAM_STOPPED);

      /* If a new `play` has been queued, fire it */
      if (this.newPlaylistItem) {
        start();
      }
    }

    private function callAPI(eventName:String, data:Object = null):void {
      var functionName:String = "LocomoteMap['" + Player.locomoteID + "'].__playerEvent";
      if (data) {
        ExternalInterface.call(functionName, eventName, data);
      } else {
        ExternalInterface.call(functionName, eventName);
      }
    }
  }
}

package {
  import com.axis.audioclient.AxisTransmit;
  import com.axis.ClientEvent;
  import com.axis.ErrorManager;
  import com.axis.http.url;
  import com.axis.httpclient.HTTPClient;
  import com.axis.IClient;
  import com.axis.Logger;
  import com.axis.mjpegclient.MJPEGClient;
  import com.axis.rtmpclient.RTMPClient;
  import com.axis.rtspclient.IRTSPHandle;
  import com.axis.rtspclient.RTSPClient;
  import com.axis.rtspclient.RTSPoverHTTPHandle;
  import com.axis.rtspclient.RTSPoverTCPHandle;

  import flash.display.LoaderInfo;
  import flash.display.Sprite;
  import flash.display.Stage;
  import flash.display.StageAlign;
  import flash.display.StageDisplayState;
  import flash.display.StageScaleMode;
  import flash.display.DisplayObject;
  import flash.display.InteractiveObject;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.external.ExternalInterface;
  import flash.media.Microphone;
  import flash.media.SoundMixer;
  import flash.media.SoundTransform;
  import flash.media.Video;
  import flash.net.NetStream;
  import flash.system.Security;
  import mx.utils.StringUtil;

  [SWF(frameRate="60")]
  [SWF(backgroundColor="#efefef")]

  public class Player extends Sprite {
    [Embed(source = "../VERSION", mimeType = "application/octet-stream")] private var Version:Class;

    public static var locomoteID:String = null;

    private static const EVENT_STREAM_STARTED:String  = "streamStarted";
    private static const EVENT_STREAM_PAUSED:String  = "streamPaused";
    private static const EVENT_STREAM_STOPPED:String  = "streamStopped";
    private static const EVENT_STREAM_ENDED:String  = "streamEnded";
    private static const EVENT_FULLSCREEN_ENTERED:String  = "fullscreenEntered";
    private static const EVENT_FULLSCREEN_EXITED:String  = "fullscreenExited";

    public static var config:Object = {
      'buffer': 3,
      'connectionTimeout': 10,
      'scaleUp': false,
      'allowFullscreen': true,
      'debugLogger': false
    };
    private var audioTransmit:AxisTransmit = new AxisTransmit();
    private var meta:Object = {};
    private var client:IClient;
    private var urlParsed:Object;
    private var savedSpeakerVolume:Number;
    private var fullscreenAllowed:Boolean = true;
    private var currentState:String = "stopped";
    private var streamHasAudio:Boolean = false;
    private var streamHasVideo:Boolean = false;
    private var newPlaylistItem:Boolean = false;

    public function Player() {
      var self:Player = this;

      Security.allowDomain("*");
      Security.allowInsecureDomain("*");

      trace('Loaded Locomote, version ' + StringUtil.trim(new Version().toString()));

      if (ExternalInterface.available) {
          setupAPICallbacks();
      } else {
        trace("External interface is not available for this container.");
      }

      /* Set default speaker volume */
      this.speakerVolume(50);

      /* Stage setup */
      this.stage.align = StageAlign.TOP_LEFT;
      this.stage.scaleMode = StageScaleMode.NO_SCALE;
      addEventListener(Event.ADDED_TO_STAGE, onStageAdded);

      /* Fullscreen support setup */
      this.stage.doubleClickEnabled = true;
      this.stage.addEventListener(MouseEvent.DOUBLE_CLICK, fullscreen);
      this.stage.addEventListener(Event.FULLSCREEN, function(event:Event):void {
        (StageDisplayState.NORMAL === stage.displayState) ? callAPI(EVENT_FULLSCREEN_EXITED) : callAPI(EVENT_FULLSCREEN_ENTERED);
      });
      this.stage.addEventListener(Event.RESIZE, function(event:Event):void {
        videoResize();
      });

      this.setConfig(Player.config);
    }

    /**
     * Registers the appropriate API functions with the container, so that
     * they can be called, and triggers the apiReady event
     * which tells the container that the Player is ready to receive API calls.
     */
    public function setupAPICallbacks():void {
      ExternalInterface.marshallExceptions = true;

      /* Media player API */
      ExternalInterface.addCallback("play", play);
      ExternalInterface.addCallback("pause", pause);
      ExternalInterface.addCallback("resume", resume);
      ExternalInterface.addCallback("stop", stop);
      ExternalInterface.addCallback("seek", seek);
      ExternalInterface.addCallback("streamStatus", streamStatus);
      ExternalInterface.addCallback("playerStatus", playerStatus);
      ExternalInterface.addCallback("speakerVolume", speakerVolume);
      ExternalInterface.addCallback("muteSpeaker", muteSpeaker);
      ExternalInterface.addCallback("unmuteSpeaker", unmuteSpeaker);
      ExternalInterface.addCallback("microphoneVolume", microphoneVolume);
      ExternalInterface.addCallback("muteMicrophone", muteMicrophone);
      ExternalInterface.addCallback("unmuteMicrophone", unmuteMicrophone);
      ExternalInterface.addCallback("setConfig", setConfig);

      /* Audio Transmission API */
      ExternalInterface.addCallback("startAudioTransmit", startAudioTransmit);
      ExternalInterface.addCallback("stopAudioTransmit", stopAudioTransmit);
    }

    public function fullscreen(event:MouseEvent):void {
      if (config.allowFullscreen) {
        this.stage.displayState = (StageDisplayState.NORMAL === stage.displayState) ?
          StageDisplayState.FULL_SCREEN : StageDisplayState.NORMAL;
      }
    }

    public function videoResize():void {
      var stagewidth:uint = (StageDisplayState.NORMAL === stage.displayState) ?
        stage.stageWidth : stage.fullScreenWidth;
      var stageheight:uint = (StageDisplayState.NORMAL === stage.displayState) ?
        stage.stageHeight : stage.fullScreenHeight;

      var video:DisplayObject = this.client.getDisplayObject();

      var scale:Number = ((stagewidth / meta.width) > (stageheight / meta.height)) ?
        (stageheight / meta.height) : (stagewidth / meta.width);

      video.width = meta.width;
      video.height = meta.height;
      if ((scale < 1.0) || (scale > 1.0 && true === config.scaleUp)) {
        Logger.log('scaling video, scale:' + scale.toFixed(2) + ' (aspect ratio: ' +  (video.width / video.height).toFixed(2) + ')');
        video.width = meta.width * scale;
        video.height = meta.height * scale;
      }

      video.x = (stagewidth - video.width) / 2;
      video.y = (stageheight - video.height) / 2;
    }

    public function setConfig(iconfig:Object):void {
      if (iconfig.buffer !== undefined) {
        if (this.client) {
          if (false === this.client.setBuffer(config.buffer)) {
            ErrorManager.dispatchError(830);
          } else {
            config.buffer = iconfig.buffer;
          }
        }
      }

      if (iconfig.scaleUp !== undefined) {
        var scaleUpChanged:Boolean = (config.scaleUp !== iconfig.scaleUp);
        config.scaleUp = iconfig.scaleUp;
        if (scaleUpChanged && this.client)
          this.videoResize();
      }

      if (iconfig.allowFullscreen !== undefined) {
        config.allowFullscreen = iconfig.allowFullscreen;

        if (!config.allowFullscreen)
          this.stage.displayState = StageDisplayState.NORMAL;
      }

      if (iconfig.debugLogger !== undefined) {
        config.debugLogger = iconfig.debugLogger;
      }

      if (iconfig.connectionTimeout !== undefined) {
        config.connectionTimeout = iconfig.connectionTimeout;
      }
    }

    public function play(param:* = null):void {
      this.streamHasAudio = false;
      this.streamHasVideo = false;

      if (param is String) {
        urlParsed = url.parse(String(param));
      } else {
        urlParsed = url.parse(param.url);
        urlParsed.connect = param.url;
        urlParsed.streamName = param.streamName;
      }

      this.newPlaylistItem = true;

      if (client) {
        /* Stop the client, and 'onStopped' will start the new stream. */
        client.stop();
        return;
      }

      start();
    }

    private function start():void {
      switch (urlParsed.protocol) {
      case 'rtsph':
        /* RTSP over HTTP */
        client = new RTSPClient(urlParsed, new RTSPoverHTTPHandle(urlParsed));
        break;

      case 'rtsp':
        /* RTSP over TCP */
        client = new RTSPClient(urlParsed, new RTSPoverTCPHandle(urlParsed));
        break;

      case 'http':
      case 'https':
        /* Progressive download over HTTP */
        client = new HTTPClient(urlParsed);
        break;

      case 'httpm':
        /* Progressive mjpg download over HTTP (x-mixed-replace) */
        client = new MJPEGClient(urlParsed);
        break;

      case 'rtmp':
      case 'rtmps':
      case 'rtmpt':
        /* RTMP */
        client = new RTMPClient(urlParsed);
        break;

      default:
        ErrorManager.dispatchError(811, [urlParsed.protocol])
        return;
      }

      addChild(this.client.getDisplayObject());

      client.addEventListener(ClientEvent.STOPPED, onStopped);
      client.addEventListener(ClientEvent.START_PLAY, onStartPlay);
      client.addEventListener(ClientEvent.PAUSED, onPaused);
      client.addEventListener(ClientEvent.META, onMeta);
      client.start();
      this.newPlaylistItem = false;
    }

    public function seek(position:String):void{
      if (!client || !client.seek(Number(position))) {
        ErrorManager.dispatchError(828);
      }
    }

    public function pause():void {
      try {
        client.pause()
      } catch (err:Error) {
        ErrorManager.dispatchError(808);
      }
    }

    public function resume():void {
      if (!client || !client.resume()) {
        ErrorManager.dispatchError(809);
      }
    }

    public function stop():void {
      if (!client || !client.stop()) {
        ErrorManager.dispatchError(810);
        return;
      }

      this.currentState = "stopped";
      this.streamHasAudio = false;
      this.streamHasVideo = false;
    }

    public function onMeta(event:ClientEvent):void {
      this.meta = event.data;
      this.videoResize();
    }

    public function streamStatus():Object {
      if (this.currentState === 'playing') {
        this.streamHasAudio = (this.streamHasAudio || this.client.hasAudio());
        this.streamHasVideo = (this.streamHasVideo || this.client.hasVideo());
      }
      var status:Object = {
        'fps': (this.client) ? this.client.currentFPS() : null,
        'resolution': (this.client) ? { width: meta.width, height: meta.height } : null,
        'playbackSpeed': (this.client) ? 1.0 : null,
        'protocol': (this.urlParsed) ? this.urlParsed.protocol : null,
        'audio': (this.client) ? this.streamHasAudio : null,
        'video': (this.client) ? this.streamHasVideo : null,
        'state': this.currentState,
        'streamURL': (this.urlParsed) ? this.urlParsed.full : null,
        'duration': meta.duration ? meta.duration : null
      };

      return status;
    }

    public function playerStatus():Object {
      var mic:Microphone = Microphone.getMicrophone();

      var status:Object = {
        'version': StringUtil.trim(new Version().toString()),
        'microphoneVolume': audioTransmit.microphoneVolume,
        'speakerVolume': this.savedSpeakerVolume,
        'microphoneMuted': (mic.gain === 0),
        'speakerMuted': (flash.media.SoundMixer.soundTransform.volume === 0),
        'fullscreen': (StageDisplayState.FULL_SCREEN === stage.displayState),
        'buffer': (client === null) ? 0 : Player.config.buffer
      };

      return status;
    }

    public function speakerVolume(volume:Number):void {
      this.savedSpeakerVolume = volume;
      var transform:SoundTransform = new SoundTransform(volume / 100.0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function muteSpeaker():void {
      var transform:SoundTransform = new SoundTransform(0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function unmuteSpeaker():void {
      if (flash.media.SoundMixer.soundTransform.volume !== 0)
        return;

      var transform:SoundTransform = new SoundTransform(this.savedSpeakerVolume / 100.0);
      flash.media.SoundMixer.soundTransform = transform;
    }

    public function microphoneVolume(volume:Number):void {
      audioTransmit.microphoneVolume = volume;
    }

    public function muteMicrophone():void {
      audioTransmit.muteMicrophone();
    }

    public function unmuteMicrophone():void {
      audioTransmit.unmuteMicrophone();
    }

    public function startAudioTransmit(url:String = null, type:String = 'axis'):void {
      if (type === 'axis') {
        audioTransmit.start(url);
      } else {
        ErrorManager.dispatchError(812);
      }
    }

    public function stopAudioTransmit():void {
      audioTransmit.stop();
    }

    public function allowFullscreen(state:Boolean):void {
      this.fullscreenAllowed = state;

      if (!state)
        this.stage.displayState = StageDisplayState.NORMAL;
    }

    private function onStageAdded(e:Event):void {
      Player.locomoteID = LoaderInfo(this.root.loaderInfo).parameters.locomoteID.toString();
      ExternalInterface.call("LocomoteMap['" + Player.locomoteID + "'].__swfReady");
    }

    private function onStartPlay(event:ClientEvent):void {
      this.currentState = "playing";
      this.callAPI(EVENT_STREAM_STARTED);
    }

    private function onPaused(event:ClientEvent):void {
      this.currentState = "paused";
      this.callAPI(EVENT_STREAM_PAUSED, event.data);
    }

    private function onStopped(event:ClientEvent):void {
      this.removeChild(this.client.getDisplayObject());
      this.client = null;
      this.callAPI(EVENT_STREAM_STOPPED);

      /* If a new `play` has been queued, fire it */
      if (this.newPlaylistItem) {
        start();
      }
    }

    private function callAPI(eventName:String, data:Object = null):void {
      var functionName:String = "LocomoteMap['" + Player.locomoteID + "'].__playerEvent";
      if (data) {
        ExternalInterface.call(functionName, eventName, data);
      } else {
        ExternalInterface.call(functionName, eventName);
      }
    }
  }
}

remove unused commented out variable Player::debugLogger


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package mx.binding
{

import mx.collections.errors.ItemPendingError;
import mx.core.mx_internal;
import flash.utils.Dictionary;

use namespace mx_internal;

[ExcludeClass]

/**
 *  @private
 */
public class Binding
{
    include "../core/Version.as";

    // Certain errors are normal during binding execution, so we swallow them.
    // 1507 - invalid null argument
    // 2005 - argument error (null gets converted to 0)
    mx_internal static var allowedErrors:Object = generateAllowedErrors();
    mx_internal static function generateAllowedErrors():Object
    {
        var o:Object = {};
        o[1507] = 1;
        o[2005] = 1;
        return o;
    }

    //--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

    /**
     *  Create a Binding object
	 *
     *  @param document The document that is the target of all of this work.
	 *
     *  @param srcFunc The function that returns us the value
	 *  to use in this Binding.
	 *
     *  @param destFunc The function that will take a value
	 *  and assign it to the destination.
	 *
     *  @param destString The destination represented as a String.
	 *  We can then tell the ValidationManager to validate this field.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function Binding(document:Object, srcFunc:Function,
						    destFunc:Function, destString:String,
							srcString:String = null)
    {
		super();

        this.document = document;
        this.srcFunc = srcFunc;
        this.destFunc = destFunc;
        this.destString = destString;
        this.srcString = srcString;
        this.destFuncFailed = false;

        if (this.srcFunc == null)
        {
            this.srcFunc = defaultSrcFunc;
        }

        if (this.destFunc == null)
        {
            this.destFunc = defaultDestFunc;
        }

        _isEnabled = true;
        isExecuting = false;
        isHandlingEvent = false;
        hasHadValue = false;
        uiComponentWatcher = -1;

        BindingManager.addBinding(document, destString, this);
    }

 	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     *  Internal storage for isEnabled property.
     */
    mx_internal var _isEnabled:Boolean;

    /**
     *  @private
     *  Indicates that a Binding is enabled.
     *  Used to disable bindings.
     */
    mx_internal function get isEnabled():Boolean
    {
        return _isEnabled;
    }

    /**
     *  @private
     */
    mx_internal function set isEnabled(value:Boolean):void
    {
        _isEnabled = value;

        if (value)
        {
            processDisabledRequests();
        }
    }

	/**
 	 *  @private
     *  Indicates that a Binding is executing.
	 *  Used to prevent circular bindings from causing infinite loops.
     */
    mx_internal var isExecuting:Boolean;

	/**
 	 *  @private
     *  Indicates that the binding is currently handling an event.
     *  Used to prevent us from infinitely causing an event
	 *  that re-executes the the binding.
     */
    mx_internal var isHandlingEvent:Boolean;

    /**
     *  @private
     *  Queue of watchers that fired while we were disabled.
     *  We will resynch with our binding if isEnabled is set to true
     *  and one or more of our watchers fired while we were disabled.
     */
    mx_internal var disabledRequests:Dictionary;

	/**
 	 *  @private
     *  True as soon as a non-null or non-empty-string value has been used.
     *  We don't auto-validate until this is true
     */
    private var hasHadValue:Boolean;

	/**
 	 *  @private
     *  This is no longer used in Flex 3.0, but it is required to load
     *  Flex 2.0.0 and Flex 2.0.1 modules.
     */
    public var uiComponentWatcher:int;

	/**
 	 *  @private
     *  It's possible that there is a two-way binding set up, in which case
     *  we'll do a rudimentary optimization by not executing ourselves
     *  if our counterpart is already executing.
     */
    public var twoWayCounterpart:Binding;

    /**
     *  @private
     *  If there is a twoWayCounterpart, hasHadValue is false, and
     *  isTwoWayPrimary is true, then the twoWayCounterpart will be
     *  executed first.
     */
    public var isTwoWayPrimary:Boolean;

    /**
     *  @private
     *  True if a wrapped function call does not throw an error.  This is used by
     *  innerExecute() to tell if the srcFunc completed successfully.
     */
    private var wrappedFunctionSuccessful:Boolean;

 	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

    /**
     *  All Bindings hang off of a document for now,
	 *  but really it's just the root of where these functions live.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var document:Object;

	/**
     *  The function that will return us the value.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var srcFunc:Function;

	/**
     *  The function that takes the value and assigns it.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var destFunc:Function;

    /**
     * @private
     */
    mx_internal var destFuncFailed:Boolean;

	/**
     *  The destination represented as a String.
	 *  This will be used so we can signal validation on a field.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var destString:String;

	/**
     *  The source represented as a String.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 4
     */
    mx_internal var srcString:String;

	/**
	 * 	@private
	 *  Used to suppress calls to destFunc when incoming value is either
	 *	a) an XML node identical to the previously assigned XML node, or
	 *  b) an XMLList containing the identical node sequence as the previously assigned XMLList
	 */
	private var lastValue:Object;

 	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

    private function defaultDestFunc(value:Object):void
    {
        var chain:Array = destString.split(".");
        var element:Object = document;
        var i:uint = 0;

        if (chain[0] == "this")
        {
            i++;
        }

        while (i < (chain.length - 1))
        {
            element = element[chain[i++]];
            //if the element does not exist : avoid exception as it's heavy on memory allocations
            if(element == null || element == undefined) {
                destFuncFailed = true;
                if (BindingManager.debugDestinationStrings[destString])
                {
                    trace("Binding: destString = " + destString + ", error = 1009");
                }
                return;
            }
        }

        element[chain[i]] = value;
    }

    private function defaultSrcFunc():Object
    {
        return document[srcString];
    }

    /**
     *  Execute the binding.
	 *  Call the source function and get the value we'll use.
	 *  Then call the destination function passing the value as an argument.
	 *  Finally try to validate the destination.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function execute(o:Object = null):void
    {
        if (!isEnabled)
        {
            if (o != null)
            {
                registerDisabledExecute(o);
            }
            return;
        }

        if (twoWayCounterpart && !twoWayCounterpart.hasHadValue && twoWayCounterpart.isTwoWayPrimary)
        {
            twoWayCounterpart.execute();
            hasHadValue = true;
            return;
        }

        if (isExecuting || (twoWayCounterpart && twoWayCounterpart.isExecuting))
        {
            // If there is a twoWayCounterpart already executing, that means that it is
            // assigning something of value so even though we won't execute we should be
            // sure to mark ourselves as having had a value so that future executions will
            // be correct.  If isExecuting is true but we re-entered, that means we
            // clearly had a value so setting hasHadValue is safe.
            hasHadValue = true;
            return;
        }

        try
        {
            isExecuting = true;
            wrapFunctionCall(this, innerExecute, o);
        }
        catch(error:Error)
        {
            if (allowedErrors[error.errorID] == null)
                throw error;
        }
        finally
        {
            isExecuting = false;
        }
    }

    /**
     * @private
     * Take note of any execute request that occur when we are disabled.
     */
    private function registerDisabledExecute(o:Object):void
    {
        if (o != null)
        {
            disabledRequests = (disabledRequests != null) ? disabledRequests :
                new Dictionary(true);

            disabledRequests[o] = true;
        }
    }

    /**
     * @private
     * Resynch with any watchers that may have updated while we were disabled.
     */
    private function processDisabledRequests():void
    {
        if (disabledRequests != null)
        {
            for (var key:Object in disabledRequests)
            {
                execute(key);
            }

            disabledRequests = null;
        }
    }

    /**
	 *  @private
	 *  Note: use of this wrapper needs to be reexamined. Currently there's at least one situation where a
	 *	wrapped function invokes another wrapped function, which is unnecessary (i.e., only the inner function
	 *  will throw), and also risks future errors due to the 'wrappedFunctionSuccessful' member variable
	 *  being stepped on. Leaving alone for now to minimize pre-GMC volatility, but should be revisited for
	 *  an early dot release.
	 *  Also note that the set of suppressed error codes below is repeated verbatim in Watcher.wrapUpdate.
	 *  These need to be consolidated and the motivations for each need to be documented.
     */
    protected function wrapFunctionCall(thisArg:Object, wrappedFunction:Function, object:Object = null, ...args):Object
    {
        wrappedFunctionSuccessful = false;

        try
        {
            var result:Object = wrappedFunction.apply(thisArg, args);
            if(destFuncFailed == true) {
                destFuncFailed = false;
                return null;
            }
            wrappedFunctionSuccessful = true;
            return result;
        }
        catch(itemPendingError:ItemPendingError)
        {
            itemPendingError.addResponder(new EvalBindingResponder(this, object));
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + itemPendingError);
            }
        }
        catch(rangeError:RangeError)
        {
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + rangeError);
            }
        }
        catch(error:Error)
        {
            // Certain errors are normal when executing a srcFunc or destFunc,
            // so we swallow them:
            //   Error #1006: Call attempted on an object that is not a function.
            //   Error #1009: null has no properties.
            //   Error #1010: undefined has no properties.
            //   Error #1055: - has no properties.
            //   Error #1069: Property - not found on - and there is no default value
            // We allow any other errors to be thrown.
            if ((error.errorID != 1006) &&
                (error.errorID != 1009) &&
                (error.errorID != 1010) &&
                (error.errorID != 1055) &&
                (error.errorID != 1069))
            {
                throw error;
            }
            else
            {
                if (BindingManager.debugDestinationStrings[destString])
                {
                    trace("Binding: destString = " + destString + ", error = " + error);
                }
            }
        }

        return null;
    }

	/**
	 *	@private
	 *  true iff XMLLists x and y contain the same node sequence.
	 */
	private function nodeSeqEqual(x:XMLList, y:XMLList):Boolean
	{
		var n:uint = x.length();
		if (n == y.length())
		{
			for (var i:uint = 0; i < n && x[i] === y[i]; i++)
			{
			}
			return i == n;
		}
		else
		{
			return false;
		}
	}

    /**
	 *  @private
     */
    private function innerExecute():void
    {
        destFuncFailed = false;
        var value:Object = wrapFunctionCall(document, srcFunc);

        if (BindingManager.debugDestinationStrings[destString])
        {
            trace("Binding: destString = " + destString + ", srcFunc result = " + value);
        }

        if (hasHadValue || wrappedFunctionSuccessful)
        {
        	//	Suppress binding assignments on non-simple XML: identical single nodes, or
        	//	lists over identical node sequences.
			//	Note: outer tests are inline for efficiency
        	if (!(lastValue is XML && lastValue.hasComplexContent() && lastValue === value) &&
        		!(lastValue is XMLList && lastValue.hasComplexContent() && value is XMLList &&
        			nodeSeqEqual(lastValue as XMLList, value as XMLList)))
        	{
	            destFunc.call(document, value);

                if(destFuncFailed == false) {
                    //	Note: state is not updated if destFunc throws
                    lastValue = value;
                    hasHadValue = true;
	            }
	        }
        }
    }

    /**
	 *  This function is called when one of this binding's watchers
	 *  detects a property change.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
    public function watcherFired(commitEvent:Boolean, cloneIndex:int):void
    {
        if (isHandlingEvent)
            return;

        try
        {
	        isHandlingEvent = true;
			execute(cloneIndex);
		}
		finally
		{
        	isHandlingEvent = false;
        }
    }
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package mx.binding
{

import mx.collections.errors.ItemPendingError;
import mx.core.mx_internal;
import flash.utils.Dictionary;

use namespace mx_internal;

[ExcludeClass]

/**
 *  @private
 */
public class Binding
{
    include "../core/Version.as";

    // Certain errors are normal during binding execution, so we swallow them.
    // 1507 - invalid null argument
    // 2005 - argument error (null gets converted to 0)
    mx_internal static var allowedErrors:Object = generateAllowedErrors();
    mx_internal static function generateAllowedErrors():Object
    {
        var o:Object = {};
        o[1507] = 1;
        o[2005] = 1;
        return o;
    }

    //--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

    /**
     *  Create a Binding object
	 *
     *  @param document The document that is the target of all of this work.
	 *
     *  @param srcFunc The function that returns us the value
	 *  to use in this Binding.
	 *
     *  @param destFunc The function that will take a value
	 *  and assign it to the destination.
	 *
     *  @param destString The destination represented as a String.
	 *  We can then tell the ValidationManager to validate this field.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function Binding(document:Object, srcFunc:Function,
						    destFunc:Function, destString:String,
							srcString:String = null)
    {
		super();

        this.document = document;
        this.srcFunc = srcFunc;
        this.destFunc = destFunc;
        this.destString = destString;
        this.srcString = srcString;
        this.destFuncFailed = false;

        if (this.srcFunc == null)
        {
            this.srcFunc = defaultSrcFunc;
        }

        if (this.destFunc == null)
        {
            this.destFunc = defaultDestFunc;
        }

        _isEnabled = true;
        isExecuting = false;
        isHandlingEvent = false;
        hasHadValue = false;
        uiComponentWatcher = -1;

        BindingManager.addBinding(document, destString, this);
    }

 	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

    /**
     *  @private
     *  Internal storage for isEnabled property.
     */
    mx_internal var _isEnabled:Boolean;

    /**
     *  @private
     *  Indicates that a Binding is enabled.
     *  Used to disable bindings.
     */
    mx_internal function get isEnabled():Boolean
    {
        return _isEnabled;
    }

    /**
     *  @private
     */
    mx_internal function set isEnabled(value:Boolean):void
    {
        _isEnabled = value;

        if (value)
        {
            processDisabledRequests();
        }
    }

	/**
 	 *  @private
     *  Indicates that a Binding is executing.
	 *  Used to prevent circular bindings from causing infinite loops.
     */
    mx_internal var isExecuting:Boolean;

	/**
 	 *  @private
     *  Indicates that the binding is currently handling an event.
     *  Used to prevent us from infinitely causing an event
	 *  that re-executes the the binding.
     */
    mx_internal var isHandlingEvent:Boolean;

    /**
     *  @private
     *  Queue of watchers that fired while we were disabled.
     *  We will resynch with our binding if isEnabled is set to true
     *  and one or more of our watchers fired while we were disabled.
     */
    mx_internal var disabledRequests:Dictionary;

	/**
 	 *  @private
     *  True as soon as a non-null or non-empty-string value has been used.
     *  We don't auto-validate until this is true
     */
    private var hasHadValue:Boolean;

	/**
 	 *  @private
     *  This is no longer used in Flex 3.0, but it is required to load
     *  Flex 2.0.0 and Flex 2.0.1 modules.
     */
    public var uiComponentWatcher:int;

	/**
 	 *  @private
     *  It's possible that there is a two-way binding set up, in which case
     *  we'll do a rudimentary optimization by not executing ourselves
     *  if our counterpart is already executing.
     */
    public var twoWayCounterpart:Binding;

    /**
     *  @private
     *  If there is a twoWayCounterpart, hasHadValue is false, and
     *  isTwoWayPrimary is true, then the twoWayCounterpart will be
     *  executed first.
     */
    public var isTwoWayPrimary:Boolean;

    /**
     *  @private
     *  True if a wrapped function call does not throw an error.  This is used by
     *  innerExecute() to tell if the srcFunc completed successfully.
     */
    private var wrappedFunctionSuccessful:Boolean;

 	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

    /**
     *  All Bindings hang off of a document for now,
	 *  but really it's just the root of where these functions live.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var document:Object;

	/**
     *  The function that will return us the value.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var srcFunc:Function;

	/**
     *  The function that takes the value and assigns it.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var destFunc:Function;

    /**
     * @private
     */
    mx_internal var destFuncFailed:Boolean;

	/**
     *  The destination represented as a String.
	 *  This will be used so we can signal validation on a field.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    mx_internal var destString:String;

	/**
     *  The source represented as a String.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 4
     */
    mx_internal var srcString:String;

	/**
	 * 	@private
	 *  Used to suppress calls to destFunc when incoming value is either
	 *	a) an XML node identical to the previously assigned XML node, or
	 *  b) an XMLList containing the identical node sequence as the previously assigned XMLList
	 */
	private var lastValue:Object;

 	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

    private function defaultDestFunc(value:Object):void
    {
        var chain:Array = destString.split(".");
        var element:Object = document;
        var i:uint = 0;

        if (chain[0] == "this")
        {
            i++;
        }

        while (i < (chain.length - 1))
        {
            element = element[chain[i++]];
            //if the element does not exist : avoid exception as it's heavy on memory allocations
            if (element == null) {
                destFuncFailed = true;
                if (BindingManager.debugDestinationStrings[destString])
                {
                    trace("Binding: destString = " + destString + ", error = 1009");
                }
                return;
            }
        }

        element[chain[i]] = value;
    }

    private function defaultSrcFunc():Object
    {
        return document[srcString];
    }

    /**
     *  Execute the binding.
	 *  Call the source function and get the value we'll use.
	 *  Then call the destination function passing the value as an argument.
	 *  Finally try to validate the destination.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function execute(o:Object = null):void
    {
        if (!isEnabled)
        {
            if (o != null)
            {
                registerDisabledExecute(o);
            }
            return;
        }

        if (twoWayCounterpart && !twoWayCounterpart.hasHadValue && twoWayCounterpart.isTwoWayPrimary)
        {
            twoWayCounterpart.execute();
            hasHadValue = true;
            return;
        }

        if (isExecuting || (twoWayCounterpart && twoWayCounterpart.isExecuting))
        {
            // If there is a twoWayCounterpart already executing, that means that it is
            // assigning something of value so even though we won't execute we should be
            // sure to mark ourselves as having had a value so that future executions will
            // be correct.  If isExecuting is true but we re-entered, that means we
            // clearly had a value so setting hasHadValue is safe.
            hasHadValue = true;
            return;
        }

        try
        {
            isExecuting = true;
            wrapFunctionCall(this, innerExecute, o);
        }
        catch(error:Error)
        {
            if (allowedErrors[error.errorID] == null)
                throw error;
        }
        finally
        {
            isExecuting = false;
        }
    }

    /**
     * @private
     * Take note of any execute request that occur when we are disabled.
     */
    private function registerDisabledExecute(o:Object):void
    {
        if (o != null)
        {
            disabledRequests = (disabledRequests != null) ? disabledRequests :
                new Dictionary(true);

            disabledRequests[o] = true;
        }
    }

    /**
     * @private
     * Resynch with any watchers that may have updated while we were disabled.
     */
    private function processDisabledRequests():void
    {
        if (disabledRequests != null)
        {
            for (var key:Object in disabledRequests)
            {
                execute(key);
            }

            disabledRequests = null;
        }
    }

    /**
	 *  @private
	 *  Note: use of this wrapper needs to be reexamined. Currently there's at least one situation where a
	 *	wrapped function invokes another wrapped function, which is unnecessary (i.e., only the inner function
	 *  will throw), and also risks future errors due to the 'wrappedFunctionSuccessful' member variable
	 *  being stepped on. Leaving alone for now to minimize pre-GMC volatility, but should be revisited for
	 *  an early dot release.
	 *  Also note that the set of suppressed error codes below is repeated verbatim in Watcher.wrapUpdate.
	 *  These need to be consolidated and the motivations for each need to be documented.
     */
    protected function wrapFunctionCall(thisArg:Object, wrappedFunction:Function, object:Object = null, ...args):Object
    {
        wrappedFunctionSuccessful = false;

        try
        {
            var result:Object = wrappedFunction.apply(thisArg, args);
            if(destFuncFailed == true) {
                destFuncFailed = false;
                return null;
            }
            wrappedFunctionSuccessful = true;
            return result;
        }
        catch(itemPendingError:ItemPendingError)
        {
            itemPendingError.addResponder(new EvalBindingResponder(this, object));
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + itemPendingError);
            }
        }
        catch(rangeError:RangeError)
        {
            if (BindingManager.debugDestinationStrings[destString])
            {
                trace("Binding: destString = " + destString + ", error = " + rangeError);
            }
        }
        catch(error:Error)
        {
            // Certain errors are normal when executing a srcFunc or destFunc,
            // so we swallow them:
            //   Error #1006: Call attempted on an object that is not a function.
            //   Error #1009: null has no properties.
            //   Error #1010: undefined has no properties.
            //   Error #1055: - has no properties.
            //   Error #1069: Property - not found on - and there is no default value
            // We allow any other errors to be thrown.
            if ((error.errorID != 1006) &&
                (error.errorID != 1009) &&
                (error.errorID != 1010) &&
                (error.errorID != 1055) &&
                (error.errorID != 1069))
            {
                throw error;
            }
            else
            {
                if (BindingManager.debugDestinationStrings[destString])
                {
                    trace("Binding: destString = " + destString + ", error = " + error);
                }
            }
        }

        return null;
    }

	/**
	 *	@private
	 *  true iff XMLLists x and y contain the same node sequence.
	 */
	private function nodeSeqEqual(x:XMLList, y:XMLList):Boolean
	{
		var n:uint = x.length();
		if (n == y.length())
		{
			for (var i:uint = 0; i < n && x[i] === y[i]; i++)
			{
			}
			return i == n;
		}
		else
		{
			return false;
		}
	}

    /**
	 *  @private
     */
    private function innerExecute():void
    {
        destFuncFailed = false;
        var value:Object = wrapFunctionCall(document, srcFunc);

        if (BindingManager.debugDestinationStrings[destString])
        {
            trace("Binding: destString = " + destString + ", srcFunc result = " + value);
        }

        if (hasHadValue || wrappedFunctionSuccessful)
        {
        	//	Suppress binding assignments on non-simple XML: identical single nodes, or
        	//	lists over identical node sequences.
			//	Note: outer tests are inline for efficiency
        	if (!(lastValue is XML && lastValue.hasComplexContent() && lastValue === value) &&
        		!(lastValue is XMLList && lastValue.hasComplexContent() && value is XMLList &&
        			nodeSeqEqual(lastValue as XMLList, value as XMLList)))
        	{
	            destFunc.call(document, value);

                if(destFuncFailed == false) {
                    //	Note: state is not updated if destFunc throws
                    lastValue = value;
                    hasHadValue = true;
	            }
	        }
        }
    }

    /**
	 *  This function is called when one of this binding's watchers
	 *  detects a property change.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
    public function watcherFired(commitEvent:Boolean, cloneIndex:int):void
    {
        if (isHandlingEvent)
            return;

        try
        {
	        isHandlingEvent = true;
			execute(cloneIndex);
		}
		finally
		{
        	isHandlingEvent = false;
        }
    }
}

}

Remove compiler warning

Remove compiler warning


package aerys.minko.type.interpolation
{
	import aerys.minko.type.math.Vector4;

	import mx.validators.ValidationResult;

	public class BezierCubicSegment extends AbstractBezierSegment
	{
		protected var _control1	: Vector4;
		protected var _control2	: Vector4;

		private var _diff10 : Vector4 = null;
		private var _diff21 : Vector4 = null;
		private var _diff30 : Vector4 = null;

		override public function get firstControl() : Vector4
		{
			return _control1;
		}

		override public function get lastControl() : Vector4
		{
			return _control2;
		}

		override public function set start(value : Vector4) : void
		{
			super._start	= value;
			_length			= NaN;
			_dirty			= true;
		}

		override public function set end(value : Vector4) : void
		{
			super._end	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function set control1(value : Vector4) : void
		{
			_control1	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function set control2(value : Vector4) : void
		{
			_control2	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function BezierCubicSegment(start	: Vector4,
										   control1	: Vector4,
										   control2	: Vector4,
										   end		: Vector4,
										   at		: Vector4 = null,
										   up		: Vector4 = null)
		{
			super(start, end, at, up);

			_control1 = control1;
			_control2 = control2;

			_diff10 = new Vector4();
			_diff21 = new Vector4();
			_diff30 = new Vector4();

			_dirty = true;
		}

		override public function clone() : IInterpolation
		{
			return new BezierCubicSegment(
				_start.clone(),
				_control1.clone(),
				_control2.clone(),
				_end.clone(),
				_at.clone(),
				_up.clone()
			);
		}

		override protected function updatePrecomputedTerms() : void
		{
			Vector4.subtract(_control1,	_start,		_diff10);
			Vector4.subtract(_control2,	_control1,	_diff21);
			Vector4.subtract(_end,		_start,		_diff30);

			_dirty = false;
			_tmpPosT		= -1;
			_tmpPointAtT	= -1;
			_tmpTangentT	= -1;
			_length			= NaN;
		}

		override protected function updatePosition(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			if (Math.abs(t - _tmpPosT) < 1e-6)
				return;

			_tmpPos.set(
				_start.x + t * ( 3 * _diff10.x + t * ( 3 * (_diff21.x - _diff10.x) + t * ( _diff30.x - 3 * _diff21.x ) ) ),
				_start.y + t * ( 3 * _diff10.y + t * ( 3 * (_diff21.y - _diff10.y) + t * ( _diff30.y - 3 * _diff21.y ) ) ),
				_start.z + t * ( 3 * _diff10.z + t * ( 3 * (_diff21.z - _diff10.z) + t * ( _diff30.z - 3 * _diff21.z ) ) ),
				_start.w + t * ( 3 * _diff10.w + t * ( 3 * (_diff21.w - _diff10.w) + t * ( _diff30.w - 3 * _diff21.w ) ) )
			);

			/*
			var term1	: Number = (1 - t) * (1 - t) * (1 - t);
			var term2	: Number = 3 * t * (1 - t) * (1 - t);
			var term3	: Number = 3 * t * t * (1 - t);
			var term4	: Number = t * t * t * t;

			_tmpPos.set(
				term1 * _start.x + term2 * _control1.x + term3 * _control2.x + term4 * _end.x,
				term1 * _start.y + term2 * _control1.y + term3 * _control2.y + term4 * _end.y,
				term1 * _start.z + term2 * _control1.z + term3 * _control2.z + term4 * _end.z,
				term1 * _start.w + term2 * _control1.w + term3 * _control2.w + term4 * _end.w
			);
			*/

			_tmpPosT = t;
		}

		override protected function updateTangent(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			if (Math.abs(t - _tmpTangentT) < 1e-6)
				return;

			_tmpTangent.set(
				3 * ( _diff10.x + t * ( 2 * (_diff21.x - _diff10.x) + t * (_diff30.x - 3 * _diff21.x) ) ),
				3 * ( _diff10.y + t * ( 2 * (_diff21.y - _diff10.y) + t * (_diff30.y - 3 * _diff21.y) ) ),
				3 * ( _diff10.z + t * ( 2 * (_diff21.z - _diff10.z) + t * (_diff30.z - 3 * _diff21.z) ) ),
				3 * ( _diff10.w + t * ( 2 * (_diff21.w - _diff10.w) + t * (_diff30.w - 3 * _diff21.w) ) )
			).normalize();

			/*
			var dTerm1_dT : Number = - 3 * (1 - t) * (1 - t);
			var dTerm2_dT : Number = 6 * (1 - t);
			var dTerm3_dT : Number = 3 * t * (2 - 3 * t * t);
			var dTerm4_dT : Number = 3 * t * t;

			_tmpTangent.set(
				dTerm1_dT * _start.x + dTerm2_dT * _control1.x + dTerm3_dT * _control2.x + dTerm4_dT * _end.x,
				dTerm1_dT * _start.y + dTerm2_dT * _control1.y + dTerm3_dT * _control2.y + dTerm4_dT * _end.y,
				dTerm1_dT * _start.z + dTerm2_dT * _control1.z + dTerm3_dT * _control2.z + dTerm4_dT * _end.z,
				dTerm1_dT * _start.w + dTerm2_dT * _control1.w + dTerm3_dT * _control2.w + dTerm4_dT * _end.w
			).normalize();
			*/

			_tmpTangentT = t;
		}

		override protected function updatePointAt(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			super.updatePointAt(t);
		}
	}
}

package aerys.minko.type.interpolation
{
	import aerys.minko.type.math.Vector4;

	public class BezierCubicSegment extends AbstractBezierSegment
	{
		protected var _control1	: Vector4;
		protected var _control2	: Vector4;

		private var _diff10 : Vector4 = null;
		private var _diff21 : Vector4 = null;
		private var _diff30 : Vector4 = null;

		override public function get firstControl() : Vector4
		{
			return _control1;
		}

		override public function get lastControl() : Vector4
		{
			return _control2;
		}

		override public function set start(value : Vector4) : void
		{
			super._start	= value;
			_length			= NaN;
			_dirty			= true;
		}

		override public function set end(value : Vector4) : void
		{
			super._end	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function set control1(value : Vector4) : void
		{
			_control1	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function set control2(value : Vector4) : void
		{
			_control2	= value;
			_length		= NaN;
			_dirty		= true;
		}

		public function BezierCubicSegment(start	: Vector4,
										   control1	: Vector4,
										   control2	: Vector4,
										   end		: Vector4,
										   at		: Vector4 = null,
										   up		: Vector4 = null)
		{
			super(start, end, at, up);

			_control1 = control1;
			_control2 = control2;

			_diff10 = new Vector4();
			_diff21 = new Vector4();
			_diff30 = new Vector4();

			_dirty = true;
		}

		override public function clone() : IInterpolation
		{
			return new BezierCubicSegment(
				_start.clone(),
				_control1.clone(),
				_control2.clone(),
				_end.clone(),
				_at.clone(),
				_up.clone()
			);
		}

		override protected function updatePrecomputedTerms() : void
		{
			Vector4.subtract(_control1,	_start,		_diff10);
			Vector4.subtract(_control2,	_control1,	_diff21);
			Vector4.subtract(_end,		_start,		_diff30);

			_dirty = false;
			_tmpPosT		= -1;
			_tmpPointAtT	= -1;
			_tmpTangentT	= -1;
			_length			= NaN;
		}

		override protected function updatePosition(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			if (Math.abs(t - _tmpPosT) < 1e-6)
				return;

			_tmpPos.set(
				_start.x + t * ( 3 * _diff10.x + t * ( 3 * (_diff21.x - _diff10.x) + t * ( _diff30.x - 3 * _diff21.x ) ) ),
				_start.y + t * ( 3 * _diff10.y + t * ( 3 * (_diff21.y - _diff10.y) + t * ( _diff30.y - 3 * _diff21.y ) ) ),
				_start.z + t * ( 3 * _diff10.z + t * ( 3 * (_diff21.z - _diff10.z) + t * ( _diff30.z - 3 * _diff21.z ) ) ),
				_start.w + t * ( 3 * _diff10.w + t * ( 3 * (_diff21.w - _diff10.w) + t * ( _diff30.w - 3 * _diff21.w ) ) )
			);

			/*
			var term1	: Number = (1 - t) * (1 - t) * (1 - t);
			var term2	: Number = 3 * t * (1 - t) * (1 - t);
			var term3	: Number = 3 * t * t * (1 - t);
			var term4	: Number = t * t * t * t;

			_tmpPos.set(
				term1 * _start.x + term2 * _control1.x + term3 * _control2.x + term4 * _end.x,
				term1 * _start.y + term2 * _control1.y + term3 * _control2.y + term4 * _end.y,
				term1 * _start.z + term2 * _control1.z + term3 * _control2.z + term4 * _end.z,
				term1 * _start.w + term2 * _control1.w + term3 * _control2.w + term4 * _end.w
			);
			*/

			_tmpPosT = t;
		}

		override protected function updateTangent(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			if (Math.abs(t - _tmpTangentT) < 1e-6)
				return;

			_tmpTangent.set(
				3 * ( _diff10.x + t * ( 2 * (_diff21.x - _diff10.x) + t * (_diff30.x - 3 * _diff21.x) ) ),
				3 * ( _diff10.y + t * ( 2 * (_diff21.y - _diff10.y) + t * (_diff30.y - 3 * _diff21.y) ) ),
				3 * ( _diff10.z + t * ( 2 * (_diff21.z - _diff10.z) + t * (_diff30.z - 3 * _diff21.z) ) ),
				3 * ( _diff10.w + t * ( 2 * (_diff21.w - _diff10.w) + t * (_diff30.w - 3 * _diff21.w) ) )
			).normalize();

			/*
			var dTerm1_dT : Number = - 3 * (1 - t) * (1 - t);
			var dTerm2_dT : Number = 6 * (1 - t);
			var dTerm3_dT : Number = 3 * t * (2 - 3 * t * t);
			var dTerm4_dT : Number = 3 * t * t;

			_tmpTangent.set(
				dTerm1_dT * _start.x + dTerm2_dT * _control1.x + dTerm3_dT * _control2.x + dTerm4_dT * _end.x,
				dTerm1_dT * _start.y + dTerm2_dT * _control1.y + dTerm3_dT * _control2.y + dTerm4_dT * _end.y,
				dTerm1_dT * _start.z + dTerm2_dT * _control1.z + dTerm3_dT * _control2.z + dTerm4_dT * _end.z,
				dTerm1_dT * _start.w + dTerm2_dT * _control1.w + dTerm3_dT * _control2.w + dTerm4_dT * _end.w
			).normalize();
			*/

			_tmpTangentT = t;
		}

		override protected function updatePointAt(t : Number) : void
		{
			if (_dirty)
				updatePrecomputedTerms();

			super.updatePointAt(t);
		}
	}
}

Remove invalid import.

Remove invalid import.


/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.data.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableHashMap;
	import weavejs.api.core.ILinkableVariable;
	import weavejs.api.data.IKeySet;
	import weavejs.api.data.Aggregation;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IAttributeColumn;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IQualifiedKey;
	import weavejs.api.data.ISelectableAttributes;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableHashMap;
	import weavejs.core.LinkableString;
	import weavejs.core.LinkableVariable;
	import weavejs.core.LinkableBoolean;
	import weavejs.core.LinkableNumber;
	import weavejs.data.column.DynamicColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.data.hierarchy.ColumnTreeNode;
	import weavejs.data.ColumnUtils;
	import weavejs.data.DataSourceUtils;
	import weavejs.data.key.DynamicKeyFilter;
	import weavejs.util.JS;
	import weavejs.util.StandardLib;

	public class WebSocketDataSource extends AbstractDataSource
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, WebSocketDataSource, "WebSocket Data Source");

		public static const DATA_COLUMNNAME_META:String = "__WebSocketJsonPropertyName__";

		public function WebSocketDataSource()
		{
			this.selectionFilter.targetPath = ["defaultSelectionKeySet"];
		}

		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keepLast:LinkableNumber = Weave.linkableChild(this, LinkableNumber, onKeepLastChange);
		public const selectionFilter:DynamicKeyFilter = Weave.linkableChild(this, DynamicKeyFilter, onSelectionFilterChange);
		//public const selectionFeedback:LinkableBoolean = Weave.linkableChild(this, LinkableBoolean);
		public const url:LinkableString = Weave.linkableChild(this, LinkableString, onUrlChange);

		private function onSelectionFilterChange():void
		{
			if (_socket && _socket.readyState == 1)
			{
				var ks:IKeySet = selectionFilter.getInternalKeyFilter() as IKeySet;
				_socket.send(JSON.stringify(ks.keys.map(function (key:IQualifiedKey, idx:int, a:Array):String {
					return key.localName;
				})));
			}
		}

		private var _socket:Object = null;
		private function onUrlChange():void
		{
			reconnect();

			records = [];
			propertyNames.clear();
		}

		public function reconnect():void /* To be called from UI reconnect button. */
		{
			if (_socket && _socket.readyState < 2)
			{
				_socket.close(1000, "No longer needed.");
				_socket = null;
			}

			_socket = new JS.global.WebSocket(url.value);

			_socket.onmessage = onMessage;
			_socket.onclose = onClose;
			_socket.onerror = onError;
		}

		private function onClose(event:Object):void
		{
			_socket = null;
		}

		private function onError(event:Object):void
		{
			JS.error(event);
			/* Should we automatically retry here? */
			_socket = null;
		}

		private function onMessage(event:/*/MessageEvent/*/Object):void
		{
			var str:String = event.data;
			var record:Object = JSON.parse(str);

			if ((records.length == keepLast.value) && (keepLast.value > 0))
			{
				records.shift();
			}

			records.push(record);

			/* Update set of property names */
			for each (var key:String in JS.objectKeys(record))
			{
				propertyNames.add(key);
			}

			refreshAllProxyColumns();
		}

		public function getPropertyNames():Array/*/<string>/*/
		{
			return JS.mapKeys(propertyNames)
		}

		private function onKeepLastChange():void
		{
			var diff:int = records.length - keepLast.value;
			if (diff > 0)
			{
				records = records.slice(diff);
			}
		}

		/**
		 * The session state maps a column name in dataColumns hash map to a value for its "aggregation" metadata.
		 */
		public const aggregationModes:ILinkableVariable = Weave.linkableChild(this, new LinkableVariable(null, typeofIsObject));
		private function typeofIsObject(value:Object):Boolean
		{
			return typeof value == 'object';
		}

		private var records:Array = [];
		private var propertyNames:Object = new JS.global.Set();

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
				_rootNode = new ColumnTreeNode({
					cacheSettings: {"label": false},
					dataSource: this,
					dependency: this,
					data: this,
					"label": getLabel,
					hasChildBranches: false,
					children: function():Array {
						return JS.mapValues(propertyNames).map(
							function (columnName:String, ..._):* {
								var meta:Object = {};
								meta[DATA_COLUMNNAME_META] = columnName;
								return generateHierarchyNode(meta);
							}
						);
					}
				});
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			metadata = getColumnMetadata(metadata[DATA_COLUMNNAME_META]);

			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [DATA_COLUMNNAME_META],
				data: metadata
			});
		}

		private function getColumnMetadata(dataColumnName:String):Object
		{
			var metadata:Object = {};
			metadata[ColumnMetadata.KEY_TYPE] = keyType.value || DataType.STRING;
			metadata[DATA_COLUMNNAME_META] = dataColumnName;
			metadata[ColumnMetadata.TITLE] = dataColumnName;

			var aggState:Object = aggregationModes.getSessionState();
			var aggregation:String = aggState ? aggState[dataColumnName] : null;
			aggregation = aggregation || Aggregation.LAST; /* Keep the last value by default, this makes more sense in streaming applications. */
			metadata[ColumnMetadata.AGGREGATION] = aggregation;

			if (aggregation != Aggregation.SAME)
				metadata[ColumnMetadata.TITLE] = Weave.lang("{0} ({1})", metadata[ColumnMetadata.TITLE], aggregation);

			return metadata;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var columnName:String = proxyColumn.getMetadata(DATA_COLUMNNAME_META);

			var keys:Array;
			var data:Array = StandardLib.lodash.map(records, columnName);/*.map(
				function (d:*,i:int,a:Array):*
				{
					return d !== undefined ? d : null;
				}
			);*/

			if (keyProperty.value)
			{
				keys = StandardLib.lodash.map(records, keyProperty.value);
			}
			else
			{
				keys = StandardLib.lodash.range(data.length);
			}

			var metadata:Object = getColumnMetadata(columnName);

			var dataType:String = DataSourceUtils.guessDataType(data);
			metadata[ColumnMetadata.DATA_TYPE] = dataType;
			proxyColumn.setMetadata(metadata);

			DataSourceUtils.initColumn(proxyColumn, keys, data);
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.data.source
{
	import weavejs.WeaveAPI;
	import weavejs.api.core.ILinkableHashMap;
	import weavejs.api.core.ILinkableVariable;
	import weavejs.api.data.IKeySet;
	import weavejs.api.data.Aggregation;
	import weavejs.api.data.ColumnMetadata;
	import weavejs.api.data.DataType;
	import weavejs.api.data.IAttributeColumn;
	import weavejs.api.data.IDataSource;
	import weavejs.api.data.IQualifiedKey;
	import weavejs.api.data.ISelectableAttributes;
	import weavejs.api.data.IWeaveTreeNode;
	import weavejs.core.LinkableHashMap;
	import weavejs.core.LinkableString;
	import weavejs.core.LinkableVariable;
	import weavejs.core.LinkableBoolean;
	import weavejs.core.LinkableNumber;
	import weavejs.data.column.DynamicColumn;
	import weavejs.data.column.ProxyColumn;
	import weavejs.data.column.StringColumn;
	import weavejs.data.hierarchy.ColumnTreeNode;
	import weavejs.data.ColumnUtils;
	import weavejs.data.DataSourceUtils;
	import weavejs.data.key.DynamicKeyFilter;
	import weavejs.util.JS;
	import weavejs.util.StandardLib;

	public class WebSocketDataSource extends AbstractDataSource
	{
		WeaveAPI.ClassRegistry.registerImplementation(IDataSource, WebSocketDataSource, "WebSocket Data Source");

		public static const DATA_COLUMNNAME_META:String = "__WebSocketJsonPropertyName__";

		public function WebSocketDataSource()
		{
			this.selectionFilter.targetPath = ["defaultSelectionKeySet"];
		}

		public const keyType:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keyProperty:LinkableString = Weave.linkableChild(this, LinkableString);
		public const keepLast:LinkableNumber = Weave.linkableChild(this, LinkableNumber, onKeepLastChange);
		public const selectionFilter:DynamicKeyFilter = Weave.linkableChild(this, DynamicKeyFilter, onSelectionFilterChange);
		//public const selectionFeedback:LinkableBoolean = Weave.linkableChild(this, LinkableBoolean);
		public const url:LinkableString = Weave.linkableChild(this, LinkableString, onUrlChange);

		private function onSelectionFilterChange():void
		{
			if (_socket && _socket.readyState == 1)
			{
				var ks:IKeySet = selectionFilter.getInternalKeyFilter() as IKeySet;
				_socket.send(JSON.stringify(ks.keys.map(function (key:IQualifiedKey, idx:int, a:Array):String {
					return key.localName;
				})));
			}
		}

		private var _socket:Object = null;
		private function onUrlChange():void
		{
			reconnect();

			records = [];
			propertyNames.clear();
		}

		public function reconnect():void /* To be called from UI reconnect button. */
		{
			JS.global.clearTimeout(timeoutId);
			if (_socket && _socket.readyState < 2)
			{
				_socket.close(1000, "No longer needed.");
				_socket = null;
			}

			_socket = new JS.global.WebSocket(url.value);

			_socket.onmessage = onMessage;
			_socket.onclose = onClose;
			_socket.onerror = onError;
		}

		private var timeoutId:int = 0;
		private function onClose(event:Object):void
		{
			_socket = null;
			if (url.value && event.code == 1006 /* CLOSE_ABNORMAL */)
			{
				timeoutId = JS.global.setTimeout(reconnect, 1500)
			}
		}

		private function onError(event:Object):void
		{
			JS.error(event);
			/* Should we automatically retry here? */
			_socket = null;
		}

		private function onMessage(event:/*/MessageEvent/*/Object):void
		{
			var str:String = event.data;
			var record:Object = JSON.parse(str);

			if ((records.length == keepLast.value) && (keepLast.value > 0))
			{
				records.shift();
			}

			records.push(record);

			/* Update set of property names */
			for each (var key:String in JS.objectKeys(record))
			{
				propertyNames.add(key);
			}

			refreshAllProxyColumns();
		}

		public function getPropertyNames():Array/*/<string>/*/
		{
			return JS.mapKeys(propertyNames)
		}

		private function onKeepLastChange():void
		{
			var diff:int = records.length - keepLast.value;
			if (diff > 0)
			{
				records = records.slice(diff);
			}
		}

		/**
		 * The session state maps a column name in dataColumns hash map to a value for its "aggregation" metadata.
		 */
		public const aggregationModes:ILinkableVariable = Weave.linkableChild(this, new LinkableVariable(null, typeofIsObject));
		private function typeofIsObject(value:Object):Boolean
		{
			return typeof value == 'object';
		}

		private var records:Array = [];
		private var propertyNames:Object = new JS.global.Set();

		override public function getHierarchyRoot():IWeaveTreeNode
		{
			if (!_rootNode)
				_rootNode = new ColumnTreeNode({
					cacheSettings: {"label": false},
					dataSource: this,
					dependency: this,
					data: this,
					"label": getLabel,
					hasChildBranches: false,
					children: function():Array {
						return JS.mapValues(propertyNames).map(
							function (columnName:String, ..._):* {
								var meta:Object = {};
								meta[DATA_COLUMNNAME_META] = columnName;
								return generateHierarchyNode(meta);
							}
						);
					}
				});
			return _rootNode;
		}

		override protected function generateHierarchyNode(metadata:Object):IWeaveTreeNode
		{
			if (!metadata)
				return null;

			metadata = getColumnMetadata(metadata[DATA_COLUMNNAME_META]);

			if (!metadata)
				return null;

			return new ColumnTreeNode({
				dataSource: this,
				idFields: [DATA_COLUMNNAME_META],
				data: metadata
			});
		}

		private function getColumnMetadata(dataColumnName:String):Object
		{
			var metadata:Object = {};
			metadata[ColumnMetadata.KEY_TYPE] = keyType.value || DataType.STRING;
			metadata[DATA_COLUMNNAME_META] = dataColumnName;
			metadata[ColumnMetadata.TITLE] = dataColumnName;

			var aggState:Object = aggregationModes.getSessionState();
			var aggregation:String = aggState ? aggState[dataColumnName] : null;
			aggregation = aggregation || Aggregation.LAST; /* Keep the last value by default, this makes more sense in streaming applications. */
			metadata[ColumnMetadata.AGGREGATION] = aggregation;

			if (aggregation != Aggregation.SAME)
				metadata[ColumnMetadata.TITLE] = Weave.lang("{0} ({1})", metadata[ColumnMetadata.TITLE], aggregation);

			return metadata;
		}

		override protected function requestColumnFromSource(proxyColumn:ProxyColumn):void
		{
			var columnName:String = proxyColumn.getMetadata(DATA_COLUMNNAME_META);

			var keys:Array;
			var data:Array = StandardLib.lodash.map(records, columnName);/*.map(
				function (d:*,i:int,a:Array):*
				{
					return d !== undefined ? d : null;
				}
			);*/

			if (keyProperty.value)
			{
				keys = StandardLib.lodash.map(records, keyProperty.value);
			}
			else
			{
				keys = StandardLib.lodash.range(data.length);
			}

			var metadata:Object = getColumnMetadata(columnName);

			var dataType:String = DataSourceUtils.guessDataType(data);
			metadata[ColumnMetadata.DATA_TYPE] = dataType;
			proxyColumn.setMetadata(metadata);

			DataSourceUtils.initColumn(proxyColumn, keys, data);
		}
	}
}

Reconnect on abnormal connection abort.

WebSocketDataSource: Reconnect on abnormal connection abort.


//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.display.Sprite;
import flash.filesystem.File;
import flash.geom.Rectangle;

import flump.SwfTexture;

public class Atlas
{
    public var name :String;
    public var w :int, h :int, id :int;

    public function Atlas(name :String, w :int, h :int) {
        this.name = name;
        this.w = w;
        this.h = h;

        _root = new Node();
        _root.bounds = new Rectangle(0, 0, w, h);
    }

    // Try to place a texture in this atlas, return true if it fit
    public function place (texture :SwfTexture) :Boolean {
        var node :Node = _root.search(new Rectangle(0, 0, texture.w, texture.h));
        if (node == null) {
            return false;
        }

        node.texture = texture;
        return true;
    }

    public function publish (dir :File) :void {
        var constructed :Sprite = new Sprite();
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            constructed.addChild(tex.holder);
            tex.holder.x = node.bounds.x;
            tex.holder.y = node.bounds.y;
        });
        PngPublisher.publish(dir.resolvePath(name + ".png"), w, h, constructed);
    }

    public function toXml () :String {
        var xml :String = '<atlas name="' + name + '" filename="' + name + '.png">\n';
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            xml += '  <texture name="' + tex.libraryItem + '" xOffset="' + tex.offset.x +
                '" yOffset="' + tex.offset.y + '" md5="' + tex.md5 +
                '" xAtlas="' + node.bounds.x + '" yAtlas="' + node.bounds.y +
                '" wAtlas="' + tex.w + '" hAtlas="' + tex.h + '"/>\n';
        });
        return xml + '</atlas>\n';
    }

    public function toJSON (_:*) :Object {
        var json :Object = {
            file: name + ".png",
            textures: []
        };
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            json.textures.push({
                name: tex.symbol,
                offset: [ tex.offset.x, tex.offset.y ],
                rect: [ node.bounds.x, node.bounds.y, tex.w, tex.h ]
            });
        });
        return json;
    }

    protected var _root :Node;
}
}

import flash.geom.Rectangle;

import flump.SwfTexture;

// A node in a k-d tree
class Node
{
    // The bounds of this node (and its children)
    public var bounds :Rectangle;

    // The texture that is placed here, if any
    public var texture :SwfTexture;

    // This node's two children
    public var left :Node;
    public var right :Node;

    // Find for free node in this tree big enough to fit a rect, or null
    public function search (rect :Rectangle) :Node
    {
        // There's already a texture here, terminate
        if (texture != null) {
            return null;
        }

        if (left != null && right != null) {
            // Try to fit it into this node's children
            var descendent :Node = left.search(rect);
            if (descendent == null) {
                descendent = right.search(rect);
            }
            return descendent;

        } else {
            if (bounds.width == rect.width && bounds.height == rect.height) {
                return this;
            }
            if (bounds.width < rect.width || bounds.height < rect.height) {
                return null;
            }

            left = new Node();
            right = new Node();

            var dw :Number = bounds.width - rect.width;
            var dh :Number = bounds.height - rect.height;

            if (dw > dh) {
                left.bounds = new Rectangle(
                    bounds.x,
                    bounds.y,
                    rect.width,
                    bounds.height);

                right.bounds = new Rectangle(
                    bounds.x + rect.width,
                    bounds.y,
                    bounds.width - rect.width,
                    bounds.height);

            } else {
                left.bounds = new Rectangle(
                    bounds.x,
                    bounds.y,
                    bounds.width,
                    rect.height);

                right.bounds = new Rectangle(
                    bounds.x,
                    bounds.y + rect.height,
                    bounds.width,
                    bounds.height - rect.height);
            }

            return left.search(rect);
        }
    }

    // Iterate over all nodes with textures in this tree
    public function forEach (fn :Function /* Node -> void */) :void {
        if (texture != null) {
            fn(this);
        }

        if (left != null && right != null) {
            left.forEach(fn);
            right.forEach(fn);
        }
    }
}

//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.display.Sprite;
import flash.filesystem.File;
import flash.geom.Rectangle;

import flump.SwfTexture;

public class Atlas
{
    public var name :String;
    public var w :int, h :int, id :int;

    public function Atlas(name :String, w :int, h :int) {
        this.name = name;
        this.w = w;
        this.h = h;

        _root = new Node();
        _root.bounds = new Rectangle(0, 0, w, h);
    }

    // Try to place a texture in this atlas, return true if it fit
    public function place (texture :SwfTexture) :Boolean {
        var node :Node = _root.search(
            new Rectangle(0, 0, texture.w + 2*PADDING, texture.h + 2*PADDING));
        if (node == null) {
            return false;
        }

        node.texture = texture;
        return true;
    }

    public function publish (dir :File) :void {
        var constructed :Sprite = new Sprite();
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            constructed.addChild(tex.holder);
            tex.holder.x = node.bounds.x;
            tex.holder.y = node.bounds.y;
        });
        PngPublisher.publish(dir.resolvePath(name + ".png"), w, h, constructed);
    }

    public function toXml () :String {
        var xml :String = '<atlas name="' + name + '" filename="' + name + '.png">\n';
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            xml += '  <texture name="' + tex.libraryItem +
                '" xOffset="' + tex.offset.x +
                '" yOffset="' + tex.offset.y +
                '" md5="' + tex.md5 +
                '" xAtlas="' + (node.bounds.x + PADDING)
                '" yAtlas="' + (node.bounds.y + PADDING)
                '" wAtlas="' + tex.w + '" hAtlas="' + tex.h + '"/>\n';
        });
        return xml + '</atlas>\n';
    }

    public function toJSON (_:*) :Object {
        var json :Object = {
            file: name + ".png",
            textures: []
        };
        _root.forEach(function (node :Node) :void {
            var tex :SwfTexture = node.texture;
            json.textures.push({
                name: tex.symbol,
                offset: [ tex.offset.x, tex.offset.y ],
                rect: [ node.bounds.x + PADDING, node.bounds.y + PADDING, tex.w, tex.h ]
            });
        });
        return json;
    }

    // Empty pixels to border around each texture to prevent bleeding
    protected static const PADDING :int = 1;

    protected var _root :Node;
}
}

import flash.geom.Rectangle;

import flump.SwfTexture;

// A node in a k-d tree
class Node
{
    // The bounds of this node (and its children)
    public var bounds :Rectangle;

    // The texture that is placed here, if any
    public var texture :SwfTexture;

    // This node's two children
    public var left :Node;
    public var right :Node;

    // Find for free node in this tree big enough to fit a rect, or null
    public function search (rect :Rectangle) :Node
    {
        // There's already a texture here, terminate
        if (texture != null) {
            return null;
        }

        if (left != null && right != null) {
            // Try to fit it into this node's children
            var descendent :Node = left.search(rect);
            if (descendent == null) {
                descendent = right.search(rect);
            }
            return descendent;

        } else {
            if (bounds.width == rect.width && bounds.height == rect.height) {
                return this;
            }
            if (bounds.width < rect.width || bounds.height < rect.height) {
                return null;
            }

            left = new Node();
            right = new Node();

            var dw :Number = bounds.width - rect.width;
            var dh :Number = bounds.height - rect.height;

            if (dw > dh) {
                left.bounds = new Rectangle(
                    bounds.x,
                    bounds.y,
                    rect.width,
                    bounds.height);

                right.bounds = new Rectangle(
                    bounds.x + rect.width,
                    bounds.y,
                    bounds.width - rect.width,
                    bounds.height);

            } else {
                left.bounds = new Rectangle(
                    bounds.x,
                    bounds.y,
                    bounds.width,
                    rect.height);

                right.bounds = new Rectangle(
                    bounds.x,
                    bounds.y + rect.height,
                    bounds.width,
                    bounds.height - rect.height);
            }

            return left.search(rect);
        }
    }

    // Iterate over all nodes with textures in this tree
    public function forEach (fn :Function /* Node -> void */) :void {
        if (texture != null) {
            fn(this);
        }

        if (left != null && right != null) {
            left.forEach(fn);
            right.forEach(fn);
        }
    }
}

Put tiny gaps around textures to prevent atlas artifacts.


package
{
	import org.flixel.*;
	[SWF(width="320", height="240", backgroundColor="#000000")]
	[Frame(factoryClass="Preloader")]

	public class Main extends FlxGame
	{
		public function Main()
		{
			super(320, 240, PlayState, 1, 20, 20);
		}
	}
}

package
{
	import org.flixel.*;
	[SWF(width="320", height="240", backgroundColor="#000000")]
	[Frame(factoryClass="Preloader")]

	public class Main extends FlxGame
	{
		public function Main()
		{
			//test
			super(320, 240, PlayState, 1, 20, 20);
		}
	}
}

test office commit

test office commit