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";
    private static const EVENT_FRAME_READY:String  = "frameReady";

    public static var config:Object = {
      'buffer': 3,
      'connectionTimeout': 10,
      'scaleUp': false,
      'allowFullscreen': true,
      'debugLogger': false,
      'frameByFrame': 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;
    private var startOptions:Object = null;

    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("playFrames", playFrames);
      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 {
      if (!this.client) {
        return;
      }

      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;
          }
        } else {
            config.buffer = iconfig.buffer;
        }
      }

      if (iconfig.frameByFrame !== undefined) {
        if (this.client) {
          if (false === this.client.setFrameByFrame(iconfig.frameByFrame)) {
            ErrorManager.dispatchError(832);
          } else {
            config.frameByFrame = iconfig.frameByFrame;
          }
        } else {
          config.frameByFrame = iconfig.frameByFrame;
        }
      }

      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, options:Object = 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;
      this.startOptions = options;

      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.ENDED, onEnded);
      client.addEventListener(ClientEvent.META, onMeta);
      client.addEventListener(ClientEvent.FRAME, onFrame);
      client.start(this.startOptions);
      this.newPlaylistItem = false;
    }

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

    public function playFrames(timestamp:Number):void {
      client && client.playFrames(timestamp);
    }

    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 causes a crash in some situations */
        //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,
        'currentTime': (this.client) ? this.client.getCurrentTime() : -1,
        'bufferedTime': (this.client) ? this.client.bufferedTime() : -1
      };

      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 onEnded(event:ClientEvent):void {
      this.currentState = "ended";
      this.callAPI(EVENT_STREAM_ENDED, event.data);
    }

    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, event.data);

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

    private function onFrame(event:ClientEvent):void {
      this.callAPI(EVENT_FRAME_READY, { timestamp: event.data });
    }

    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";
    private static const EVENT_FRAME_READY:String  = "frameReady";

    public static var config:Object = {
      'buffer': 3,
      'connectionTimeout': 10,
      'scaleUp': false,
      'allowFullscreen': true,
      'debugLogger': false,
      'frameByFrame': 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;
    private var startOptions:Object = null;

    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("playFrames", playFrames);
      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 {
      if (!this.client) {
        return;
      }

      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;
          }
        } else {
            config.buffer = iconfig.buffer;
        }
      }

      if (iconfig.frameByFrame !== undefined) {
        if (this.client) {
          if (false === this.client.setFrameByFrame(iconfig.frameByFrame)) {
            ErrorManager.dispatchError(832);
          } else {
            config.frameByFrame = iconfig.frameByFrame;
          }
        } else {
          config.frameByFrame = iconfig.frameByFrame;
        }
      }

      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, options:Object = 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;
      this.startOptions = options;

      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.ENDED, onEnded);
      client.addEventListener(ClientEvent.META, onMeta);
      client.addEventListener(ClientEvent.FRAME, onFrame);
      client.start(this.startOptions);
      this.newPlaylistItem = false;
    }

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

    public function playFrames(timestamp:Number):void {
      client && client.playFrames(timestamp);
    }

    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 causes a crash in some situations */
        //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,
        'currentTime': (this.client) ? this.client.getCurrentTime() : -1,
        'bufferedTime': (this.client) ? this.client.bufferedTime() : -1
      };

      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 onEnded(event:ClientEvent):void {
      this.currentState = "ended";
      this.callAPI(EVENT_STREAM_ENDED, event.data);
    }

    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.removeEventListener(ClientEvent.STOPPED, onStopped);
      this.client.removeEventListener(ClientEvent.START_PLAY, onStartPlay);
      this.client.removeEventListener(ClientEvent.PAUSED, onPaused);
      this.client.removeEventListener(ClientEvent.ENDED, onEnded);
      this.client.removeEventListener(ClientEvent.META, onMeta);
      this.client.removeEventListener(ClientEvent.FRAME, onFrame);
      this.client = null;
      this.callAPI(EVENT_STREAM_STOPPED, event.data);

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

    private function onFrame(event:ClientEvent):void {
      this.callAPI(EVENT_FRAME_READY, { timestamp: event.data });
    }

    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);
      }
    }
  }
}

Handle multiple stop events from client.

Handled by removing eventListeners, i.e. not handled.


package Testing {
	import flash.utils.Dictionary;
	import Modules.Module;
	import org.flixel.FlxG;
	import Testing.Abstractions.InstructionAbstraction;
	import Testing.Abstractions.SaveAbstraction;
	import Testing.Abstractions.SetAbstraction;
	import Testing.Instructions.Instruction;
	import Values.BooleanValue;
	import Testing.Types.InstructionType;
	import Values.Value;
	/**
	 * ...
	 * @author Nicholas "PleasingFungus" Feinberg
	 */
	public class Test {

		public var seed:Number;
		public var memAddressToSet:int;
		public var memValueToSet:int
		public var instructions:Vector.<Instruction>;

		public function Test(seed:Number = NaN) {
			if (isNaN(seed))
				seed = FlxG.random();
			FlxG.globalSeed = this.seed = seed;

			memAddressToSet = C.randomRange(0, U.MAX_INT - U.MIN_INT);
			memValueToSet = C.randomRange(U.MIN_INT, U.MAX_INT);
			var values:Vector.<int> = C.buildIntVector(memAddressToSet, memValueToSet);

			var abstractions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			abstractions.push(new SaveAbstraction( -1, memValueToSet, memAddressToSet));

			var minInstructions:int = 10;

			for (var depth:int = 0; abstractions.length + values.length < minInstructions; depth++)
				genAbstractions(abstractions, values, depth);

			genInitializationAbstractions(abstractions, values, depth);

			var orderedAbstractions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			while (abstractions.length)
				orderedAbstractions.push(abstractions.pop());

			log("\n\nSEED: " + seed);
			log("PROGRAM START");
			for (var i:int = 0; i < orderedAbstractions.length; i++)
				log(i + ": " + orderedAbstractions[i]);
			log("PROGRAM END\n\n");

			var virtualRegisters:Vector.<int> = new Vector.<int>;
			instructions = new Vector.<Instruction>;
			for (var line:int = 0; line < orderedAbstractions.length; line++) {
				var abstraction:InstructionAbstraction = orderedAbstractions[line];
				log(line, abstraction);
				instructions.push(genInstruction(line, abstraction, virtualRegisters, orderedAbstractions));
				log("Virtual registers: " + virtualRegisters);
			}

			instructions = postProcess(instructions);

			//TODO: scramble registers

			log("\n\nSEED: " + seed);
			log("PROGRAM START");
			for (i = 0; i < instructions.length; i++)
				log(i + ": " + instructions[i]);
			log("PROGRAM END\n\n");

			var regCount:int = 8;
			var memory:Dictionary = new Dictionary;
			var registers:Dictionary = new Dictionary;
			executeInEnvironment(memory, registers, instructions);

			var mem:String = "Memory: ";
			for (var memAddrStr:String in memory)
				mem += memAddrStr + ":" + memory[memAddrStr] + ", ";
			log(mem);
			if (memory[memAddressToSet+""] != memValueToSet)
				throw new Error("Memory at " + memAddressToSet + " is " + memory[memAddressToSet+""] + " at end of run, not " + memValueToSet + " as expected!");
			else
				log("Mem test success");
		}

		protected function postProcess(instructions:Vector.<Instruction>):Vector.<Instruction> {
			return instructions;
		}

		protected function executeInEnvironment(memory:Dictionary, registers:Dictionary, instructions:Vector.<Instruction>):void {
			for (var line:int = 0; line < instructions.length; line ++) {
				var instruction:Instruction = instructions[line];
				var jump:int = instruction.execute(memory, registers);
				if (jump != C.INT_NULL)
					line = jump;
			}
		}

		protected function genAbstractions(abstractions:Vector.<InstructionAbstraction>, values:Vector.<int>, depth:int):void {
			log("Instructions: " + abstractions);
			log("Values: " + values);

			while (values.length)
				abstractions.push(genAbstraction(values.pop(), depth, getArgs(abstractions, depth)));

			for each (var abstraction:InstructionAbstraction in abstractions)
				if (abstraction.depth == depth)
					for each (var arg:int in abstraction.args)
						if (values.indexOf(arg) == -1)
							values.push(arg);
		}

		protected function genAbstraction(value:int, depth:int, args:Vector.<int>):InstructionAbstraction {
			var type:InstructionType;
			var abstraction:InstructionAbstraction;
			var arg:int;

			log("Attempting to produce " +value);
			log("Args: " + args);
			var fullReuseInstrs:Vector.<InstructionType> = new Vector.<InstructionType>;
			for each (type in InstructionType.TYPES)
				if (type.can_produce_with(value, args))
					fullReuseInstrs.push(type);
			if (fullReuseInstrs.length) {
				log("Full re-use instrs: " + fullReuseInstrs);
				abstraction = randomTypeChoice(fullReuseInstrs).produce_with(value, depth, args);
				log("Added " + abstraction + " with full re-use");
				return abstraction;
			}
			log("No full re-use instrs")

			var partialReuseInstrs:Vector.<InstructionType> = new Vector.<InstructionType>;
			for each (type in InstructionType.TYPES)
				if (type.can_produce_with_one_of(value, args))
					partialReuseInstrs.push(type);
			if (partialReuseInstrs.length) {
				log("Partial re-use instrs: " + partialReuseInstrs);

				type = randomTypeChoice(partialReuseInstrs);
				var validArgs:Vector.<int> = new Vector.<int>;
				for each (arg in args)
					if (type.can_produce_with_one(value, arg))
						validArgs.push(arg);
				log("Valid args for " + type.name + ": " + validArgs);

				arg = C.randomIntChoice(validArgs);
				abstraction = type.produce_with_one(value, depth, arg);
				log("Added " + abstraction + ", re-using " + arg);
				return abstraction;
			}
			log("No partial re-use instrs")

			type = C.randomChoice(InstructionType.TYPES);
			abstraction = type.produce_unrestrained(value, depth);
			log("Added " + abstraction);
			return abstraction;
		}

		protected function genInitializationAbstractions(abstractions:Vector.<InstructionAbstraction>, values:Vector.<int>, depth:int):void {
			for each (var value:int in values)
				abstractions.push(new SetAbstraction(depth, value));
		}

		protected function genInstruction(line:int, abstraction:InstructionAbstraction, virtualRegisters:Vector.<int>,
										   abstractions:Vector.<InstructionAbstraction>):Instruction {
			var registers:Vector.<int> = new Vector.<int>;
			for each (var arg:int in abstraction.args)
				registers.push(virtualRegisters.indexOf(arg));

			if (abstraction.value != C.INT_NULL) {
				var destination:int = findRegisterFor(abstraction.depth, line, abstraction.value, virtualRegisters, abstractions);
				var noop:Boolean = destination < virtualRegisters.length && virtualRegisters[destination] == abstraction.value;
				registers.splice(0, 0, destination);
				if (destination < virtualRegisters.length)
					virtualRegisters[destination] = abstraction.value;
				else
					virtualRegisters.push(abstraction.value);
			}

			var instructionClass:Class = Instruction.mapByType(abstraction.type);
			return new instructionClass(registers, abstraction, noop);
		}

		protected function findRegisterFor(depth:int, line:int, value:int,
										   virtualRegisters:Vector.<int>,
										   abstractions:Vector.<InstructionAbstraction>):int {
			if (virtualRegisters.indexOf(value) != -1) {
				log("Storing " + value + " is a no-op");
				return virtualRegisters.indexOf(value);
			}

			var abstraction:InstructionAbstraction;

			var successors:Vector.<InstructionAbstraction> = abstractions.slice(line + 1);
			var predependents:Vector.<InstructionAbstraction> = abstractions.slice(0, line + 1);

			var blockingInstructions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			for each (abstraction in successors)
				if (abstraction.depth <= depth + 1)
					blockingInstructions.push(abstraction);

			var freeRegValues:Vector.<int> = new Vector.<int>;
			for each (var rv:int in virtualRegisters) {
				var blocked:Boolean = false;
				for each (abstraction in blockingInstructions)
					if (abstraction.args.indexOf(rv) != -1) {
						blocked = true;
						break;
					}

				if (!blocked)
					freeRegValues.push(rv);
			}

			//var allArgs:Vector.<int> = getArgs(predependents, C.INT_NULL);
			//free_reg_values.sort(key=lambda rv: -all_args.index(rv)) #TODO
			log("Free register values: " + freeRegValues);

			if (freeRegValues.length) {
				log("Storing " + value + " in existing slot " + virtualRegisters.indexOf(freeRegValues[0])+" currently holding "+freeRegValues[0]);
				return virtualRegisters.indexOf(freeRegValues[0]);
			}
			log("Storing " + value + " in previously unallocated register " + virtualRegisters.length);
			return virtualRegisters.length;
		}

		protected function getArgs(abstractions:Vector.<InstructionAbstraction>, depth:int):Vector.<int> {
			var args:Vector.<int> = new Vector.<int>;

			for each (var abstraction:InstructionAbstraction in abstractions)
				if (abstraction.depth == depth || depth == C.INT_NULL)
					for each (var arg:int in abstraction.args)
						if (args.indexOf(arg) == -1)
							args.push(arg);

			return args;
		}

		protected function randomTypeChoice(options:Vector.<InstructionType>):InstructionType {
			return options[int(FlxG.random() * options.length)];
		}

		protected function log(...args):void {
			if (U.DEBUG && U.DEBUG_PRINT_TESTS)
				C.log(args);
		}

		public function initialMemory():Vector.<Value> {
			var mem:Vector.<Value> = new Vector.<Value>;
			for each (var instr:Instruction in instructions)
				mem.push(instr.toMemValue());
			return mem;
		}

		public function validate(_:Module):Boolean {
			if (U.state.memory[memAddressToSet].toNumber() == memValueToSet)
				return true;
			return false;
		}
	}

}

package Testing {
	import flash.utils.Dictionary;
	import Modules.Module;
	import org.flixel.FlxG;
	import Testing.Abstractions.InstructionAbstraction;
	import Testing.Abstractions.SaveAbstraction;
	import Testing.Abstractions.SetAbstraction;
	import Testing.Instructions.Instruction;
	import Values.BooleanValue;
	import Testing.Types.InstructionType;
	import Values.Value;
	/**
	 * ...
	 * @author Nicholas "PleasingFungus" Feinberg
	 */
	public class Test {

		public var seed:Number;
		public var memAddressToSet:int;
		public var memValueToSet:int
		public var instructions:Vector.<Instruction>;

		public function Test(seed:Number = NaN) {
			if (isNaN(seed))
				seed = FlxG.random();
			FlxG.globalSeed = this.seed = seed;

			memAddressToSet = C.randomRange(U.MAX_INT, U.MAX_INT - U.MIN_INT);
			memValueToSet = C.randomRange(U.MIN_INT, U.MAX_INT+1);
			var values:Vector.<int> = C.buildIntVector(memAddressToSet, memValueToSet);

			var abstractions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			abstractions.push(new SaveAbstraction( -1, memValueToSet, memAddressToSet));

			var minInstructions:int = 10;

			for (var depth:int = 0; abstractions.length + values.length < minInstructions; depth++)
				genAbstractions(abstractions, values, depth);

			genInitializationAbstractions(abstractions, values, depth);

			var orderedAbstractions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			while (abstractions.length)
				orderedAbstractions.push(abstractions.pop());

			log("\n\nSEED: " + seed);
			log("PROGRAM START");
			for (var i:int = 0; i < orderedAbstractions.length; i++)
				log(i + ": " + orderedAbstractions[i]);
			log("PROGRAM END\n\n");

			var virtualRegisters:Vector.<int> = new Vector.<int>;
			instructions = new Vector.<Instruction>;
			for (var line:int = 0; line < orderedAbstractions.length; line++) {
				var abstraction:InstructionAbstraction = orderedAbstractions[line];
				log(line, abstraction);
				instructions.push(genInstruction(line, abstraction, virtualRegisters, orderedAbstractions));
				log("Virtual registers: " + virtualRegisters);
			}

			instructions = postProcess(instructions);

			//TODO: scramble registers

			log("\n\nSEED: " + seed);
			log("PROGRAM START");
			for (i = 0; i < instructions.length; i++)
				log(i + ": " + instructions[i]);
			log("PROGRAM END\n\n");

			var regCount:int = 8;
			var memory:Dictionary = new Dictionary;
			var registers:Dictionary = new Dictionary;
			executeInEnvironment(memory, registers, instructions);

			var mem:String = "Memory: ";
			for (var memAddrStr:String in memory)
				mem += memAddrStr + ":" + memory[memAddrStr] + ", ";
			log(mem);
			if (memory[memAddressToSet+""] != memValueToSet)
				throw new Error("Memory at " + memAddressToSet + " is " + memory[memAddressToSet+""] + " at end of run, not " + memValueToSet + " as expected!");
			else
				log("Mem test success");
		}

		protected function postProcess(instructions:Vector.<Instruction>):Vector.<Instruction> {
			return instructions;
		}

		protected function executeInEnvironment(memory:Dictionary, registers:Dictionary, instructions:Vector.<Instruction>):void {
			for (var line:int = 0; line < instructions.length; line ++) {
				var instruction:Instruction = instructions[line];
				var jump:int = instruction.execute(memory, registers);
				if (jump != C.INT_NULL)
					line = jump;
			}
		}

		protected function genAbstractions(abstractions:Vector.<InstructionAbstraction>, values:Vector.<int>, depth:int):void {
			log("Instructions: " + abstractions);
			log("Values: " + values);

			while (values.length)
				abstractions.push(genAbstraction(values.pop(), depth, getArgs(abstractions, depth)));

			for each (var abstraction:InstructionAbstraction in abstractions)
				if (abstraction.depth == depth)
					for each (var arg:int in abstraction.args)
						if (values.indexOf(arg) == -1)
							values.push(arg);
		}

		protected function genAbstraction(value:int, depth:int, args:Vector.<int>):InstructionAbstraction {
			var type:InstructionType;
			var abstraction:InstructionAbstraction;
			var arg:int;

			log("Attempting to produce " +value);
			log("Args: " + args);
			var fullReuseInstrs:Vector.<InstructionType> = new Vector.<InstructionType>;
			for each (type in InstructionType.TYPES)
				if (type.can_produce_with(value, args))
					fullReuseInstrs.push(type);
			if (fullReuseInstrs.length) {
				log("Full re-use instrs: " + fullReuseInstrs);
				abstraction = randomTypeChoice(fullReuseInstrs).produce_with(value, depth, args);
				log("Added " + abstraction + " with full re-use");
				return abstraction;
			}
			log("No full re-use instrs")

			var partialReuseInstrs:Vector.<InstructionType> = new Vector.<InstructionType>;
			for each (type in InstructionType.TYPES)
				if (type.can_produce_with_one_of(value, args))
					partialReuseInstrs.push(type);
			if (partialReuseInstrs.length) {
				log("Partial re-use instrs: " + partialReuseInstrs);

				type = randomTypeChoice(partialReuseInstrs);
				var validArgs:Vector.<int> = new Vector.<int>;
				for each (arg in args)
					if (type.can_produce_with_one(value, arg))
						validArgs.push(arg);
				log("Valid args for " + type.name + ": " + validArgs);

				arg = C.randomIntChoice(validArgs);
				abstraction = type.produce_with_one(value, depth, arg);
				log("Added " + abstraction + ", re-using " + arg);
				return abstraction;
			}
			log("No partial re-use instrs")

			type = C.randomChoice(InstructionType.TYPES);
			abstraction = type.produce_unrestrained(value, depth);
			log("Added " + abstraction);
			return abstraction;
		}

		protected function genInitializationAbstractions(abstractions:Vector.<InstructionAbstraction>, values:Vector.<int>, depth:int):void {
			for each (var value:int in values)
				abstractions.push(new SetAbstraction(depth, value));
		}

		protected function genInstruction(line:int, abstraction:InstructionAbstraction, virtualRegisters:Vector.<int>,
										   abstractions:Vector.<InstructionAbstraction>):Instruction {
			var registers:Vector.<int> = new Vector.<int>;
			for each (var arg:int in abstraction.args)
				registers.push(virtualRegisters.indexOf(arg));

			if (abstraction.value != C.INT_NULL) {
				var destination:int = findRegisterFor(abstraction.depth, line, abstraction.value, virtualRegisters, abstractions);
				var noop:Boolean = destination < virtualRegisters.length && virtualRegisters[destination] == abstraction.value;
				registers.splice(0, 0, destination);
				if (destination < virtualRegisters.length)
					virtualRegisters[destination] = abstraction.value;
				else
					virtualRegisters.push(abstraction.value);
			}

			var instructionClass:Class = Instruction.mapByType(abstraction.type);
			return new instructionClass(registers, abstraction, noop);
		}

		protected function findRegisterFor(depth:int, line:int, value:int,
										   virtualRegisters:Vector.<int>,
										   abstractions:Vector.<InstructionAbstraction>):int {
			if (virtualRegisters.indexOf(value) != -1) {
				log("Storing " + value + " is a no-op");
				return virtualRegisters.indexOf(value);
			}

			var abstraction:InstructionAbstraction;

			var successors:Vector.<InstructionAbstraction> = abstractions.slice(line + 1);
			var predependents:Vector.<InstructionAbstraction> = abstractions.slice(0, line + 1);

			var blockingInstructions:Vector.<InstructionAbstraction> = new Vector.<InstructionAbstraction>;
			for each (abstraction in successors)
				if (abstraction.depth <= depth + 1)
					blockingInstructions.push(abstraction);

			var freeRegValues:Vector.<int> = new Vector.<int>;
			for each (var rv:int in virtualRegisters) {
				var blocked:Boolean = false;
				for each (abstraction in blockingInstructions)
					if (abstraction.args.indexOf(rv) != -1) {
						blocked = true;
						break;
					}

				if (!blocked)
					freeRegValues.push(rv);
			}

			//var allArgs:Vector.<int> = getArgs(predependents, C.INT_NULL);
			//free_reg_values.sort(key=lambda rv: -all_args.index(rv)) #TODO
			log("Free register values: " + freeRegValues);

			if (freeRegValues.length) {
				log("Storing " + value + " in existing slot " + virtualRegisters.indexOf(freeRegValues[0])+" currently holding "+freeRegValues[0]);
				return virtualRegisters.indexOf(freeRegValues[0]);
			}
			log("Storing " + value + " in previously unallocated register " + virtualRegisters.length);
			return virtualRegisters.length;
		}

		protected function getArgs(abstractions:Vector.<InstructionAbstraction>, depth:int):Vector.<int> {
			var args:Vector.<int> = new Vector.<int>;

			for each (var abstraction:InstructionAbstraction in abstractions)
				if (abstraction.depth == depth || depth == C.INT_NULL)
					for each (var arg:int in abstraction.args)
						if (args.indexOf(arg) == -1)
							args.push(arg);

			return args;
		}

		protected function randomTypeChoice(options:Vector.<InstructionType>):InstructionType {
			return options[int(FlxG.random() * options.length)];
		}

		protected function log(...args):void {
			if (U.DEBUG && U.DEBUG_PRINT_TESTS)
				C.log(args);
		}

		public function initialMemory():Vector.<Value> {
			var mem:Vector.<Value> = new Vector.<Value>;
			for each (var instr:Instruction in instructions)
				mem.push(instr.toMemValue());
			return mem;
		}

		public function validate(_:Module):Boolean {
			if (U.state.memory[memAddressToSet].toNumber() == memValueToSet)
				return true;
			return false;
		}
	}

}

Tweak values for memset range for tests

Tweak values for memset range for tests


package aerys.minko.scene.node
{
	import aerys.minko.ns.minko_scene;
	import aerys.minko.render.Effect;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.scene.RenderingController;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.Signal;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.enum.DataProviderUsage;
	import aerys.minko.type.loader.AssetsLibrary;

	import flash.display.BitmapData;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	/**
	 * Scene objects are the root of any 3D scene.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class Scene extends Group
	{
		use namespace minko_scene;

		minko_scene var _camera		: AbstractCamera;

		private var _renderingCtrl	: RenderingController;

		private var _properties		: DataProvider;
		private var _bindings		: DataBindings;

		private var _numTriangles	: uint;

		private var _enterFrame		: Signal;
		private var _renderingBegin	: Signal;
		private var _renderingEnd	: Signal;
		private var _exitFrame		: Signal;

		private var _assets				: AssetsLibrary;

		public function get assets() : AssetsLibrary
		{
			return _assets;
		}

		public function get activeCamera() : AbstractCamera
		{
			return _camera;
		}

		public function get numPasses() : uint
		{
			return _renderingCtrl.numPasses;
		}

		public function get numEnabledPasses() : uint
		{
			return _renderingCtrl.numEnabledPasses;
		}

		public function get numTriangles() : uint
		{
			return _numTriangles;
		}

		public function get postProcessingEffect() : Effect
		{
			return _renderingCtrl.postProcessingEffect;
		}
		public function set postProcessingEffect(value : Effect) : void
		{
			_renderingCtrl.postProcessingEffect = value;
		}

		public function get postProcessingProperties() : DataProvider
		{
			return _renderingCtrl.postProcessingProperties;
		}

		public function get properties() : DataProvider
		{
			return _properties;
		}
		public function set properties(value : DataProvider) : void
		{
			if (_properties != value)
			{
				if (_properties)
					_bindings.removeProvider(_properties);

				_properties = value;

				if (value)
					_bindings.addProvider(value);
			}
		}

		public function get bindings() : DataBindings
		{
			return _bindings;
		}

		/**
		 * The signal executed when the viewport is about to start rendering a frame.
		 * Callback functions for this signal should accept the following arguments:
		 *

		 *
viewport : Viewport, the viewport who starts rendering the frame

		 *

		 * @return
		 *
		 */
		public function get enterFrame() : Signal
		{
			return _enterFrame;
		}

		/**
		 * The signal executed when the viewport is done rendering a frame.
		 * Callback functions for this signal should accept the following arguments:
		 *

		 *
viewport : Viewport, the viewport who just finished rendering the frame

		 *

		 * @return
		 *
		 */
		public function get exitFrame() : Signal
		{
			return _exitFrame;
		}

		public function get renderingBegin() : Signal
		{
			return _renderingBegin;
		}

		public function get renderingEnd() : Signal
		{
			return _renderingEnd;
		}

		public function Scene(...children)
		{
			super(children);
		}

		override protected function initialize() : void
		{
			_bindings 					= new DataBindings(this);
			_assets						= new AssetsLibrary();
			this.properties 			= new DataProvider(DataProviderUsage.EXCLUSIVE);

			super.initialize();
		}

		override protected function initializeSignals() : void
		{
			super.initializeSignals();

			_enterFrame			= new Signal('Scene.enterFrame');
			_renderingBegin 	= new Signal('Scene.renderingBegin');
			_renderingEnd 		= new Signal('Scene.renderingEnd');
			_exitFrame 			= new Signal('Scene.exitFrame');
		}

		override protected function initializeSignalHandlers() : void
		{
			super.initializeSignalHandlers();

			added.add(addedHandler);
		}

		override protected function initializeContollers() : void
		{
			_renderingCtrl = new RenderingController();
			addController(_renderingCtrl);

			super.initializeContollers();
		}

		public function render(viewport : Viewport, destination : BitmapData = null) : void
		{
			_enterFrame.execute(this, viewport, destination, getTimer());
			if (viewport.ready && viewport.visible)
			{
				_renderingBegin.execute(this, viewport, destination, getTimer());
				_numTriangles = _renderingCtrl.render(viewport, destination);
				_renderingEnd.execute(this, viewport, destination, getTimer());
			}
			_exitFrame.execute(this, viewport, destination, getTimer());
		}

		private function addedHandler(child : ISceneNode, parent : Group) : void
		{
			throw new Error();
		}
	}
}

fix coding style

fix coding style


package aerys.minko.type.animation.timeline
{
	import aerys.minko.ns.minko_animation;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.math.Matrix4x4;

	public class MatrixTimeline extends AbstractTimeline
	{
		use namespace minko_animation;

		private var _timeTable			: Vector.<uint>
		private var _values				: Vector.<Matrix4x4>;
        private var _interpolate        : Boolean;
		private var _interpolateScale	: Boolean;
        private var _interpolateW       : Boolean;
		private var _timeTableLength	: uint;

		minko_animation function get timeTable() : Vector.<uint>
		{
			return _timeTable;
		}

		minko_animation function get matrices() : Vector.<Matrix4x4>
		{
			return _values;
		}

		public function MatrixTimeline(propertyPath		: String,
									   timeTable		: Vector.<uint>,
									   matrices			: Vector.<Matrix4x4>,
                                       interpolate      : Boolean   = false,
									   interpolateScale	: Boolean	= false,
                                       interpolateW     : Boolean   = false)
		{
			super(propertyPath, timeTable[uint(timeTable.length - 1)]);

			_timeTableLength	= timeTable.length;
			_timeTable 			= timeTable;
			_values 			= matrices;
            _interpolate 		= interpolate;
			_interpolateScale 	= interpolateScale;
            _interpolateW 		= interpolateW;
		}

		override public function updateAt(t : int, target : Object) : void
		{
			super.updateAt(t, target);

			var time		: uint	= t < 0 ? duration + t : t;
			var timeId		: uint 	= getIndexForTime(time);
			var timeCount	: uint 	= _timeTableLength;

			if (timeId >= timeCount)
				timeId = timeCount - 1;

			// change matrix value.
			var out : Matrix4x4 = _currentTarget[_propertyName];

			if (!out)
			{
				throw new Error(
                    "'" + _propertyName + "' could not be found in '" + _currentTarget + "'."
				);
			}

            if (_interpolate)
            {
                var previousTime		: Number	= _timeTable[int(timeId - 1)];
                var nextTime			: Number	= _timeTable[timeId];

                out.interpolateBetween(
                    _values[int(timeId - 1)],
                    _values[timeId],
                    (time - previousTime) / (nextTime - previousTime),
                    _interpolateScale,
                    _interpolateW
                );
            }
            else
            {
                if (timeId == 0)
                    out.copyFrom(_values[0]);
                else
                    out.copyFrom(_values[uint(timeId - 1)]);
            }

		}

		private function getIndexForTime(t : uint) : uint
		{
			// use a dichotomy to find the current frame in the time table.
			var timeCount 		: uint = _timeTableLength;
			var bottomTimeId	: uint = 0;
			var upperTimeId		: uint = timeCount;
			var timeId			: uint;

			while (upperTimeId - bottomTimeId > 1)
			{
				timeId = (bottomTimeId + upperTimeId) >> 1;

				if (_timeTable[timeId] > t)
					upperTimeId = timeId;
				else
					bottomTimeId = timeId;
			}

			return upperTimeId;
		}

		override public function clone() : ITimeline
		{
			return new MatrixTimeline(
				propertyPath,
				_timeTable.slice(),
				_values.slice()
			);
		}
	}
}

package aerys.minko.type.animation.timeline
{
	import aerys.minko.ns.minko_animation;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.math.Matrix4x4;

	public class MatrixTimeline extends AbstractTimeline
	{
		use namespace minko_animation;

		private var _timeTable			: Vector.<uint>
		private var _values				: Vector.<Matrix4x4>;
        private var _interpolate        : Boolean;
		private var _interpolateScale	: Boolean;
        private var _interpolateW       : Boolean;
		private var _timeTableLength	: uint;

		minko_animation function get interpolate() : Boolean
		{
			return _interpolate;
		}

		minko_animation function get interpolateScale() : Boolean
		{
			return _interpolateScale;
		}

		minko_animation function get interpolateW() : Boolean
		{
			return _interpolateW;
		}

		minko_animation function get timeTable() : Vector.<uint>
		{
			return _timeTable;
		}

		minko_animation function get matrices() : Vector.<Matrix4x4>
		{
			return _values;
		}

		public function MatrixTimeline(propertyPath		: String,
									   timeTable		: Vector.<uint>,
									   matrices			: Vector.<Matrix4x4>,
                                       interpolate      : Boolean   = false,
									   interpolateScale	: Boolean	= false,
                                       interpolateW     : Boolean   = false)
		{
			super(propertyPath, timeTable[uint(timeTable.length - 1)]);

			_timeTableLength	= timeTable.length;
			_timeTable 			= timeTable;
			_values 			= matrices;
            _interpolate 		= interpolate;
			_interpolateScale 	= interpolateScale;
            _interpolateW 		= interpolateW;
		}

		override public function updateAt(t : int, target : Object) : void
		{
			super.updateAt(t, target);

			var time		: uint	= t < 0 ? duration + t : t;
			var timeId		: uint 	= getIndexForTime(time);
			var timeCount	: uint 	= _timeTableLength;

			if (timeId >= timeCount)
				timeId = timeCount - 1;

			// change matrix value.
			var out : Matrix4x4 = _currentTarget[_propertyName];

			if (!out)
			{
				throw new Error(
                    "'" + _propertyName + "' could not be found in '" + _currentTarget + "'."
				);
			}

            if (_interpolate)
            {
                var previousTime		: Number	= _timeTable[int(timeId - 1)];
                var nextTime			: Number	= _timeTable[timeId];

                out.interpolateBetween(
                    _values[int(timeId - 1)],
                    _values[timeId],
                    (time - previousTime) / (nextTime - previousTime),
                    _interpolateScale,
                    _interpolateW
                );
            }
            else
            {
                if (timeId == 0)
                    out.copyFrom(_values[0]);
                else
                    out.copyFrom(_values[uint(timeId - 1)]);
            }

		}

		private function getIndexForTime(t : uint) : uint
		{
			// use a dichotomy to find the current frame in the time table.
			var timeCount 		: uint = _timeTableLength;
			var bottomTimeId	: uint = 0;
			var upperTimeId		: uint = timeCount;
			var timeId			: uint;

			while (upperTimeId - bottomTimeId > 1)
			{
				timeId = (bottomTimeId + upperTimeId) >> 1;

				if (_timeTable[timeId] > t)
					upperTimeId = timeId;
				else
					bottomTimeId = timeId;
			}

			return upperTimeId;
		}

		override public function clone() : ITimeline
		{
			return new MatrixTimeline(
				propertyPath,
				_timeTable.slice(),
				_values.slice(),
				_interpolate,
				_interpolateScale,
				_interpolateW
			);
		}
	}
}

Fix clone function

Fix clone function


package goplayer
{
  import flash.utils.getTimer

  public class Player implements
    FlashNetConnectionListener,
    FlashNetStreamListener,
    PlayerQueueListener
  {
    private const DEFAULT_VOLUME : Number = .8

    private const START_BUFFER : Duration = Duration.seconds(3)
    private const SMALL_BUFFER : Duration = Duration.seconds(5)
    private const LARGE_BUFFER : Duration = Duration.seconds(60)
    private const SEEK_GRACE_TIME : Duration = Duration.seconds(2)

    private const finishingListeners : Array = []

    private var connection : FlashNetConnection
    private var _movie : Movie
    private var bitratePolicy : BitratePolicy
    private var enableRTMP : Boolean
    private var reporter : MovieEventReporter
    private var queue : PlayerQueue
    private var sharedVolumeVariable : SharedVariable

    private var _started : Boolean = false
    private var _finished : Boolean = false
    private var triedStandardRTMP : Boolean = false
    private var _usingRTMP : Boolean = false

    private var measuredBandwidth : Bitrate = null
    private var measuredLatency : Duration = null

    private var stream : FlashNetStream = null
    private var metadata : Object = null

    private var _volume : Number = NaN
    private var savedVolume : Number = 0
    private var _paused : Boolean = false
    private var _buffering : Boolean = false
    private var seekStopwatch : Stopwatch = new Stopwatch

    public function Player
      (connection : FlashNetConnection,
       movie : Movie,
       bitratePolicy : BitratePolicy,
       enableRTMP : Boolean,
       reporter : MovieEventReporter,
       queue : PlayerQueue,
       sharedVolumeVariable : SharedVariable)
    {
      this.connection = connection
      _movie = movie
      this.bitratePolicy = bitratePolicy
      this.enableRTMP = enableRTMP
      this.reporter = reporter
      this.queue = queue
      this.sharedVolumeVariable = sharedVolumeVariable

      connection.listener = this
      queue.listener = this

      reporter.reportMovieViewed(movie.id)

      if (sharedVolumeVariable.hasValue)
        volume = sharedVolumeVariable.value
      else
        volume = DEFAULT_VOLUME
    }

    public function get movie() : Movie
    { return _movie }

    public function addFinishingListener
      (value : PlayerFinishingListener) : void
    { finishingListeners.push(value) }

    public function get started() : Boolean
    { return _started }

    public function get running() : Boolean
    { return started && !finished }

    public function get finished() : Boolean
    { return _finished }

    // -----------------------------------------------------

    public function start() : void
    {
      _started = true

      if (enableRTMP && movie.rtmpURL != null)
        connectUsingRTMP()
      else
        connectUsingHTTP()

      reporter.reportMoviePlayed(movie.id)
    }

    public function handleConnectionFailed() : void
    {
      if (movie.rtmpURL.hasPort && !triedStandardRTMP)
        handleCustomRTMPUnavailable()
      else
        handleRTMPUnavailable()
    }

    private function handleCustomRTMPUnavailable() : void
    {
      debug("Trying to connect using default RTMP ports.")
      connectUsingStandardRTMP()
    }

    private function handleRTMPUnavailable() : void
    {
      debug("Could not connect using RTMP; trying plain HTTP.")
      connectUsingHTTP()
    }

    public function get usingRTMP() : Boolean
    { return _usingRTMP }

    private function connectUsingRTMP() : void
    { connection.connect(movie.rtmpURL) }

    private function connectUsingStandardRTMP() : void
    {
      triedStandardRTMP = true
      connection.connect(movie.rtmpURL.withoutPort)
    }

    private function connectUsingHTTP() : void
    {
      _usingRTMP = false
      connection.dontConnect()
      startPlaying()
    }

    public function handleConnectionClosed() : void
    {}

    // -----------------------------------------------------

    public function handleConnectionEstablished() : void
    {
      _usingRTMP = true

      if (bandwidthDeterminationNeeded)
        connection.determineBandwidth()
      else
        startPlaying()
    }

    private function get bandwidthDeterminationNeeded() : Boolean
    { return bitratePolicy == BitratePolicy.BEST }

    public function handleBandwidthDetermined
      (bandwidth : Bitrate, latency : Duration) : void
    {
      measuredBandwidth = bandwidth
      measuredLatency = latency

      startPlaying()
    }

    private function get bandwidthDetermined() : Boolean
    { return measuredBandwidth != null }

    private function startPlaying() : void
    {
      stream = connection.getNetStream()
      stream.listener = this
      stream.volume = volume

      useStartBuffer()

      if (usingRTMP)
        playRTMPStream()
      else
        playHTTPStream()

      if (paused)
        stream.paused = true
    }

    private function playRTMPStream() : void
    { stream.playRTMP(streamPicker.first, streamPicker.all) }

    private function playHTTPStream() : void
    { stream.playHTTP(movie.httpURL) }

    private function get streamPicker() : RTMPStreamPicker
    {
      return new RTMPStreamPicker
        (movie.rtmpStreams, bitratePolicy, measuredBandwidth)
    }

    // -----------------------------------------------------

    public function handleNetStreamMetadata(data : Object) : void
    {
      metadata = data

      if (!usingRTMP)
        debug("Video file size: " + stream.httpFileSize)

      maybeProcessCommands()
    }

    public function handleStreamingStarted() : void
    {
      useStartBuffer()
      _buffering = true
      _finished = false
    }

    public function handleBufferFilled() : void
    { handleBufferingFinished() }

    private function handleBufferingFinished() : void
    {
      _buffering = false

      // Give the backend a chance to resume playback before we go
      // ahead and raise the buffer size further.
      later($handleBufferingFinished)
    }

    private function $handleBufferingFinished() : void
    {
      // Make sure playback was not interrupted.
      if (!buffering && !finished)
        useLargeBuffer()
    }

    public function handleBufferEmptied() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
      else
        handleBufferingStarted()
    }

    private function handleBufferingStarted() : void
    {
      useSmallBuffer()
      _buffering = true
      _finished = false
    }

    private function useStartBuffer() : void
    { stream.bufferTime = START_BUFFER }

    private function useSmallBuffer() : void
    { stream.bufferTime = SMALL_BUFFER }

    private function useLargeBuffer() : void
    { stream.bufferTime = LARGE_BUFFER }

    public function get buffering() : Boolean
    { return _buffering }

    public function get bufferingUnexpectedly() : Boolean
    { return buffering && !justSeeked }

    private function get justSeeked() : Boolean
    { return seekStopwatch.within(SEEK_GRACE_TIME) }

    public function handleStreamingStopped() : void
    {
      _buffering = false

      if (finishedPlaying)
        handleFinishedPlaying()
    }

    private function handleFinishedPlaying() : void
    {
      debug("Finished playing.")
      _finished = true

      for each (var listener : PlayerFinishingListener in finishingListeners)
        listener.handleMovieFinishedPlaying()
    }

    public function stop() : void
    {
      debug("Stopping.")
      stream.close()
      _started = false
      stream = null
      metadata = null
    }

    private function get finishedPlaying() : Boolean
    { return timeRemaining.seconds < 1 }

    private function get timeRemaining() : Duration
    { return streamLength.minus(playheadPosition) }

    // -----------------------------------------------------

    public function get paused() : Boolean
    { return _paused }

    public function set paused(value : Boolean) : void
    {
      // Allow pausing before the stream has been created.
      _paused = value

      if (stream)
        stream.paused = value
    }

    public function togglePaused() : void
    {
      // Forbid pausing before the stream has been created.
      if (stream)
        paused = !paused
    }

    // -----------------------------------------------------

    public function get playheadPosition() : Duration
    {
      return stream != null && stream.playheadPosition != null
        ? stream.playheadPosition : Duration.ZERO
    }

    public function get playheadRatio() : Number
    { return getRatio(playheadPosition.seconds, streamLength.seconds) }

    public function get bufferRatio() : Number
    { return getRatio(bufferPosition.seconds, streamLength.seconds) }

    private function getRatio
      (numerator : Number, denominator : Number) : Number
    { return Math.min(1, numerator / denominator) }

    private function get bufferPosition() : Duration
    { return playheadPosition.plus(bufferLength) }

    public function set playheadPosition(value : Duration) : void
    {
      if (stream)
        $playheadPosition = value
    }

    private function set $playheadPosition(value : Duration) : void
    {
      _finished = false
      seekStopwatch.start()
      useStartBuffer()
      stream.playheadPosition = value
    }

    public function set playheadRatio(value : Number) : void
    { playheadPosition = streamLength.scaledBy(value) }

    public function seekBy(delta : Duration) : void
    { playheadPosition = playheadPosition.plus(delta) }

    public function rewind() : void
    { playheadPosition = Duration.ZERO }

    public function get playing() : Boolean
    { return started && stream != null && !paused && !finished }

    // -----------------------------------------------------

    public function get volume() : Number
    { return _volume }

    public function set volume(value : Number) : void
    {
      _volume = Math.round(clamp(value, 0, 1) * 100) / 100

      if (sharedVolumeVariable.available)
        sharedVolumeVariable.value = volume

      if (stream)
        stream.volume = volume
    }

    public function changeVolumeBy(delta : Number) : void
    { volume = volume + delta }

    public function mute() : void
    {
      savedVolume = volume
      volume = 0
    }

    public function unmute() : void
    { volume = savedVolume == 0 ? DEFAULT_VOLUME : savedVolume }

    public function get muted() : Boolean
    { return volume == 0 }

    // -----------------------------------------------------

    public function get aspectRatio() : Number
    { return movie.aspectRatio }

    public function get bufferTime() : Duration
    {
      return stream != null && stream.bufferTime != null
        ? stream.bufferTime : START_BUFFER
    }

    public function get bufferLength() : Duration
    {
      return stream != null && stream.bufferLength != null
        ? stream.bufferLength : Duration.ZERO
    }

    public function get bufferFillRatio() : Number
    { return getRatio(bufferLength.seconds, bufferTime.seconds) }

    public function get streamLength() : Duration
    {
      return metadata != null
        ? Duration.seconds(metadata.duration)
        : movie.duration
    }

    public function get currentBitrate() : Bitrate
    {
      return stream != null && stream.bitrate != null
        ? stream.bitrate : Bitrate.ZERO
    }

    public function get currentBandwidth() : Bitrate
    {
      return stream != null && stream.bandwidth != null
        ? stream.bandwidth : Bitrate.ZERO
    }

    public function get highQualityDimensions() : Dimensions
    {
      var result : Dimensions = Dimensions.ZERO

      for each (var stream : RTMPStream in movie.rtmpStreams)
        if (stream.dimensions.isGreaterThan(result))
          result = stream.dimensions

      return result
    }

    // -----------------------------------------------------
    // XXX: Refactor this and remove queue.

    public function handleCommandEnqueued() : void
    { maybeProcessCommands() }

    private function maybeProcessCommands() : void
    { processCommands() }

    private function processCommands() : void
    {
      while (!queue.empty)
        processCommand(queue.dequeue())
    }

    private function processCommand(command : PlayerCommand) : void
    {
      if (command == PlayerCommand.PLAY)
        started ? metadata != null && (paused = false) : start()
      else if (command == PlayerCommand.PAUSE)
        metadata != null && (paused = true)
      else if (command == PlayerCommand.STOP)
        stop()
      else
        throw new Error
    }
  }
}

package goplayer
{
  import flash.utils.getTimer

  public class Player implements
    FlashNetConnectionListener,
    FlashNetStreamListener,
    PlayerQueueListener
  {
    private const DEFAULT_VOLUME : Number = .8

    private const START_BUFFER : Duration = Duration.seconds(3)
    private const SMALL_BUFFER : Duration = Duration.seconds(5)
    private const LARGE_BUFFER : Duration = Duration.seconds(60)
    private const SEEK_GRACE_TIME : Duration = Duration.seconds(2)

    private const finishingListeners : Array = []

    private var connection : FlashNetConnection
    private var _movie : Movie
    private var bitratePolicy : BitratePolicy
    private var enableRTMP : Boolean
    private var reporter : MovieEventReporter
    private var queue : PlayerQueue
    private var sharedVolumeVariable : SharedVariable

    private var _started : Boolean = false
    private var _finished : Boolean = false
    private var triedStandardRTMP : Boolean = false
    private var _usingRTMP : Boolean = false

    private var measuredBandwidth : Bitrate = null
    private var measuredLatency : Duration = null

    private var stream : FlashNetStream = null
    private var metadata : Object = null

    private var _volume : Number = NaN
    private var savedVolume : Number = 0
    private var _paused : Boolean = false
    private var _buffering : Boolean = false
    private var seekStopwatch : Stopwatch = new Stopwatch

    public function Player
      (connection : FlashNetConnection,
       movie : Movie,
       bitratePolicy : BitratePolicy,
       enableRTMP : Boolean,
       reporter : MovieEventReporter,
       queue : PlayerQueue,
       sharedVolumeVariable : SharedVariable)
    {
      this.connection = connection
      _movie = movie
      this.bitratePolicy = bitratePolicy
      this.enableRTMP = enableRTMP
      this.reporter = reporter
      this.queue = queue
      this.sharedVolumeVariable = sharedVolumeVariable

      connection.listener = this
      queue.listener = this

      reporter.reportMovieViewed(movie.id)

      if (sharedVolumeVariable.hasValue)
        volume = sharedVolumeVariable.value
      else
        volume = DEFAULT_VOLUME
    }

    public function get movie() : Movie
    { return _movie }

    public function addFinishingListener
      (value : PlayerFinishingListener) : void
    { finishingListeners.push(value) }

    public function get started() : Boolean
    { return _started }

    public function get running() : Boolean
    { return started && !finished }

    public function get finished() : Boolean
    { return _finished }

    // -----------------------------------------------------

    public function start() : void
    {
      _started = true

      if (enableRTMP && movie.rtmpURL != null)
        connectUsingRTMP()
      else
        connectUsingHTTP()

      reporter.reportMoviePlayed(movie.id)
    }

    public function handleConnectionFailed() : void
    {
      if (movie.rtmpURL.hasPort && !triedStandardRTMP)
        handleCustomRTMPUnavailable()
      else
        handleRTMPUnavailable()
    }

    private function handleCustomRTMPUnavailable() : void
    {
      debug("Trying to connect using default RTMP ports.")
      connectUsingStandardRTMP()
    }

    private function handleRTMPUnavailable() : void
    {
      debug("Could not connect using RTMP; trying plain HTTP.")
      connectUsingHTTP()
    }

    public function get usingRTMP() : Boolean
    { return _usingRTMP }

    private function connectUsingRTMP() : void
    { connection.connect(movie.rtmpURL) }

    private function connectUsingStandardRTMP() : void
    {
      triedStandardRTMP = true
      connection.connect(movie.rtmpURL.withoutPort)
    }

    private function connectUsingHTTP() : void
    {
      _usingRTMP = false
      connection.dontConnect()
      startPlaying()
    }

    public function handleConnectionClosed() : void
    {}

    // -----------------------------------------------------

    public function handleConnectionEstablished() : void
    {
      _usingRTMP = true

      if (bandwidthDeterminationNeeded)
        connection.determineBandwidth()
      else
        startPlaying()
    }

    private function get bandwidthDeterminationNeeded() : Boolean
    { return bitratePolicy == BitratePolicy.BEST }

    public function handleBandwidthDetermined
      (bandwidth : Bitrate, latency : Duration) : void
    {
      measuredBandwidth = bandwidth
      measuredLatency = latency

      startPlaying()
    }

    private function get bandwidthDetermined() : Boolean
    { return measuredBandwidth != null }

    private function startPlaying() : void
    {
      stream = connection.getNetStream()
      stream.listener = this
      stream.volume = volume

      useStartBuffer()

      if (usingRTMP)
        playRTMPStream()
      else
        playHTTPStream()

      if (paused)
        stream.paused = true
    }

    private function playRTMPStream() : void
    { stream.playRTMP(streamPicker.first, streamPicker.all) }

    private function playHTTPStream() : void
    { stream.playHTTP(movie.httpURL) }

    private function get streamPicker() : RTMPStreamPicker
    {
      return new RTMPStreamPicker
        (movie.rtmpStreams, bitratePolicy, measuredBandwidth)
    }

    // -----------------------------------------------------

    public function handleNetStreamMetadata(data : Object) : void
    {
      metadata = data

      if (!usingRTMP)
        debug("Video file size: " + stream.httpFileSize)

      maybeProcessCommands()
    }

    public function handleStreamingStarted() : void
    {
      useStartBuffer()
      _buffering = true
      _finished = false
    }

    public function handleBufferFilled() : void
    { handleBufferingFinished() }

    private function handleBufferingFinished() : void
    {
      _buffering = false

      // Give the backend a chance to resume playback before we go
      // ahead and raise the buffer size further.
      later($handleBufferingFinished)
    }

    private function $handleBufferingFinished() : void
    {
      // Make sure playback was not interrupted.
      if (!buffering && !finished)
        useLargeBuffer()
    }

    public function handleBufferEmptied() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
      else
        handleBufferingStarted()
    }

    private function handleBufferingStarted() : void
    {
      useSmallBuffer()
      _buffering = true
      _finished = false
    }

    private function useStartBuffer() : void
    { stream.bufferTime = START_BUFFER }

    private function useSmallBuffer() : void
    { stream.bufferTime = SMALL_BUFFER }

    private function useLargeBuffer() : void
    { stream.bufferTime = LARGE_BUFFER }

    public function get buffering() : Boolean
    { return _buffering }

    public function get bufferingUnexpectedly() : Boolean
    { return buffering && !justSeeked }

    private function get justSeeked() : Boolean
    { return seekStopwatch.within(SEEK_GRACE_TIME) }

    public function handleStreamingStopped() : void
    {
      _buffering = false

      if (finishedPlaying)
        handleFinishedPlaying()
    }

    private function handleFinishedPlaying() : void
    {
      debug("Finished playing.")
      _finished = true

      for each (var listener : PlayerFinishingListener in finishingListeners)
        listener.handleMovieFinishedPlaying()
    }

    public function stop() : void
    {
      debug("Stopping.")
      stream.close()
      _started = false
      stream = null
      metadata = null
    }

    private function get finishedPlaying() : Boolean
    { return timeRemaining.seconds < 1 }

    private function get timeRemaining() : Duration
    { return streamLength.minus(playheadPosition) }

    // -----------------------------------------------------

    public function get paused() : Boolean
    { return _paused }

    public function set paused(value : Boolean) : void
    {
      if (stream != null)
        _paused = value, stream.paused = value
    }

    public function togglePaused() : void
    {
      if (stream != null)
        paused = !paused
    }

    // -----------------------------------------------------

    public function get playheadPosition() : Duration
    {
      return stream != null && stream.playheadPosition != null
        ? stream.playheadPosition : Duration.ZERO
    }

    public function get playheadRatio() : Number
    { return getRatio(playheadPosition.seconds, streamLength.seconds) }

    public function get bufferRatio() : Number
    { return getRatio(bufferPosition.seconds, streamLength.seconds) }

    private function getRatio
      (numerator : Number, denominator : Number) : Number
    { return Math.min(1, numerator / denominator) }

    private function get bufferPosition() : Duration
    { return playheadPosition.plus(bufferLength) }

    public function set playheadPosition(value : Duration) : void
    {
      if (stream)
        $playheadPosition = value
    }

    private function set $playheadPosition(value : Duration) : void
    {
      _finished = false
      seekStopwatch.start()
      useStartBuffer()
      stream.playheadPosition = value
    }

    public function set playheadRatio(value : Number) : void
    { playheadPosition = streamLength.scaledBy(value) }

    public function seekBy(delta : Duration) : void
    { playheadPosition = playheadPosition.plus(delta) }

    public function rewind() : void
    { playheadPosition = Duration.ZERO }

    public function get playing() : Boolean
    { return started && stream != null && !paused && !finished }

    // -----------------------------------------------------

    public function get volume() : Number
    { return _volume }

    public function set volume(value : Number) : void
    {
      _volume = Math.round(clamp(value, 0, 1) * 100) / 100

      if (sharedVolumeVariable.available)
        sharedVolumeVariable.value = volume

      if (stream)
        stream.volume = volume
    }

    public function changeVolumeBy(delta : Number) : void
    { volume = volume + delta }

    public function mute() : void
    {
      savedVolume = volume
      volume = 0
    }

    public function unmute() : void
    { volume = savedVolume == 0 ? DEFAULT_VOLUME : savedVolume }

    public function get muted() : Boolean
    { return volume == 0 }

    // -----------------------------------------------------

    public function get aspectRatio() : Number
    { return movie.aspectRatio }

    public function get bufferTime() : Duration
    {
      return stream != null && stream.bufferTime != null
        ? stream.bufferTime : START_BUFFER
    }

    public function get bufferLength() : Duration
    {
      return stream != null && stream.bufferLength != null
        ? stream.bufferLength : Duration.ZERO
    }

    public function get bufferFillRatio() : Number
    { return getRatio(bufferLength.seconds, bufferTime.seconds) }

    public function get streamLength() : Duration
    {
      return metadata != null
        ? Duration.seconds(metadata.duration)
        : movie.duration
    }

    public function get currentBitrate() : Bitrate
    {
      return stream != null && stream.bitrate != null
        ? stream.bitrate : Bitrate.ZERO
    }

    public function get currentBandwidth() : Bitrate
    {
      return stream != null && stream.bandwidth != null
        ? stream.bandwidth : Bitrate.ZERO
    }

    public function get highQualityDimensions() : Dimensions
    {
      var result : Dimensions = Dimensions.ZERO

      for each (var stream : RTMPStream in movie.rtmpStreams)
        if (stream.dimensions.isGreaterThan(result))
          result = stream.dimensions

      return result
    }

    // -----------------------------------------------------
    // XXX: Refactor this and remove queue.

    public function handleCommandEnqueued() : void
    { maybeProcessCommands() }

    private function maybeProcessCommands() : void
    { processCommands() }

    private function processCommands() : void
    {
      while (!queue.empty)
        processCommand(queue.dequeue())
    }

    private function processCommand(command : PlayerCommand) : void
    {
      if (command == PlayerCommand.PLAY)
        started ? metadata != null && (paused = false) : start()
      else if (command == PlayerCommand.PAUSE)
        metadata != null && (paused = true)
      else if (command == PlayerCommand.STOP)
        stop()
      else
        throw new Error
    }
  }
}

Remove the feature where you could pause the player in advance.


package nt.lib.util
{
	import flash.system.Capabilities;

	/**
	 * debug 异常抛出类
	 * @param b 值为flase则抛出异常
	 * @param m 如果错误类为空则抛出该错误信息
	 * @param errorClass 错误类
	 */
	[Inline]
	public function assert(b:Boolean, m:String = "", errorClass:Class = null):void
	{
		if (Capabilities.isDebugger)
		{
			if (!b)
			{
				throw new (errorClass ||= Error)(m);
			}
		}
	}
}

package nt.lib.util
{
	import flash.system.Capabilities;

	/**
	 * debug 异常抛出类
	 * @param b 值为 false 则抛出异常
	 * @param m 如果错误类为空则抛出该错误信息
	 * @param errorClass 错误类
	 */
	[Inline]
	public function assert(b:Boolean, m:String = "", errorClass:Class = null):void
	{
		if (Capabilities.isDebugger)
		{
			if (!b)
			{
				throw new (errorClass ||= Error)(m);
			}
		}
	}
}

Fix typo

Fix typo


package com.threerings.flash {

import flash.display.DisplayObject;
import flash.display.Sprite;

import flash.events.Event;

import flash.utils.getTimer; // func import

/**
 * Manages animations.
 */
public class AnimationManager
{
    public function AnimationManager ()
    {
        throw new Error("Static only");
    }

    /**
     * Start (or restart) the specified animation.
     */
    public static function start (anim :Animation) :void
    {
        var dex :int = _anims.indexOf(anim);
        if (dex == -1) {
            _anims.push(anim);
            _anims.push(NaN);

        } else {
            _anims[dex + 1] = NaN; // mark it as starting
        }

        if (!_framer) {
            _framer = new Sprite();
            _framer.addEventListener(Event.ENTER_FRAME, frameHandler);
        }
    }

    /**
     * Stop the specified animation.
     */
    public static function stop (anim :Animation) :void
    {
        var dex :int = _anims.indexOf(anim);
        if (dex == -1) {
            Log.getLog(AnimationManager).warning("Stopping unknown Animation: " + anim);
            return;
        }

        // remove it
        _anims.splice(dex, 2);

        // See if we should clean up a bit
        if (_anims.length == 0) {
            _framer.removeEventListener(Event.ENTER_FRAME, frameHandler);
            _framer = null;
        }
    }

    /**
     * Track a DisplayObject that is also an Animation- it will
     * automatically be started when added to the stage and
     * stopped when removed.
     */
    public static function addDisplayAnimation (disp :DisplayObject) :void
    {
        if (!(disp is Animation)) {
            throw new ArgumentError("Must be an Animation");
        }

        disp.addEventListener(Event.ADDED_TO_STAGE, startDisplayAnim);
        disp.addEventListener(Event.REMOVED_FROM_STAGE, stopDisplayAnim);
        if (disp.stage != null) {
            // it's on the stage now!
            start(Animation(disp));
        }
    }

    /**
     * Stop tracking the specified DisplayObject animation.
     */
    public static function removeDisplayAnimation (disp :DisplayObject) :void
    {
        disp.removeEventListener(Event.ADDED_TO_STAGE, startDisplayAnim);
        disp.removeEventListener(Event.REMOVED_FROM_STAGE, stopDisplayAnim);
        if (disp.stage != null) {
            stop(Animation(disp));
        }
    }

    protected static function startDisplayAnim (event :Event) :void
    {
        start(event.currentTarget as Animation);
    }

    protected static function stopDisplayAnim (event :Event) :void
    {
        stop(event.currentTarget as Animation);
    }

    /**
     * Handle the ENTER_FRAME event.
     */
    protected static function frameHandler (event :Event) :void
    {
        var now :Number = getTimer();
        var anim :Animation;
        var startStamp :Number;
        for (var ii :int = _anims.length - 2; ii >= 0; ii -= 2) {
            anim = Animation(_anims[ii]);
            startStamp = Number(_anims[ii + 1]);
            if (isNaN(startStamp)) {
                _anims[ii + 1] = startStamp = now;
            }
            anim.updateAnimation(now - startStamp);
        }
    }

    /** The current timestamp, accessable to all animations. */
    protected static var _now :Number;

    /** All the currently running animations. */
    protected static var _anims :Array = [];

    protected static var _framer :Sprite;
}
}

package com.threerings.flash {

import flash.display.DisplayObject;
import flash.display.Sprite;

import flash.events.Event;

import flash.utils.getTimer; // func import

/**
 * Manages animations.
 */
public class AnimationManager
{
    public function AnimationManager ()
    {
        throw new Error("Static only");
    }

    /**
     * Start (or restart) the specified animation.
     */
    public static function start (anim :Animation) :void
    {
        var dex :int = _anims.indexOf(anim);
        if (dex == -1) {
            dex = _anims.length;
            _anims.push(anim);
        }
        _anims[dex + 1] = getTimer(); // mark it as starting

        // and update it immediately
        anim.updateAnimation(0);

        if (!_framer) {
            _framer = new Sprite();
            _framer.addEventListener(Event.ENTER_FRAME, frameHandler);
        }
    }

    /**
     * Stop the specified animation.
     */
    public static function stop (anim :Animation) :void
    {
        var dex :int = _anims.indexOf(anim);
        if (dex == -1) {
            Log.getLog(AnimationManager).warning("Stopping unknown Animation: " + anim);
            return;
        }

        // remove it
        _anims.splice(dex, 2);

        // See if we should clean up a bit
        if (_anims.length == 0) {
            _framer.removeEventListener(Event.ENTER_FRAME, frameHandler);
            _framer = null;
        }
    }

    /**
     * Track a DisplayObject that is also an Animation- it will
     * automatically be started when added to the stage and
     * stopped when removed.
     */
    public static function addDisplayAnimation (disp :DisplayObject) :void
    {
        if (!(disp is Animation)) {
            throw new ArgumentError("Must be an Animation");
        }

        disp.addEventListener(Event.ADDED_TO_STAGE, startDisplayAnim);
        disp.addEventListener(Event.REMOVED_FROM_STAGE, stopDisplayAnim);
        if (disp.stage != null) {
            // it's on the stage now!
            start(Animation(disp));
        }
    }

    /**
     * Stop tracking the specified DisplayObject animation.
     */
    public static function removeDisplayAnimation (disp :DisplayObject) :void
    {
        disp.removeEventListener(Event.ADDED_TO_STAGE, startDisplayAnim);
        disp.removeEventListener(Event.REMOVED_FROM_STAGE, stopDisplayAnim);
        if (disp.stage != null) {
            stop(Animation(disp));
        }
    }

    protected static function startDisplayAnim (event :Event) :void
    {
        start(event.currentTarget as Animation);
    }

    protected static function stopDisplayAnim (event :Event) :void
    {
        stop(event.currentTarget as Animation);
    }

    /**
     * Handle the ENTER_FRAME event.
     */
    protected static function frameHandler (event :Event) :void
    {
        var now :Number = getTimer();
        var anim :Animation;
        var startStamp :Number;
        for (var ii :int = _anims.length - 2; ii >= 0; ii -= 2) {
            Animation(_anims[ii]).updateAnimation(now - Number(_anims[ii + 1]));
        }
    }

    /** The current timestamp, accessable to all animations. */
    protected static var _now :Number;

    /** All the currently running animations. */
    protected static var _anims :Array = [];

    protected static var _framer :Sprite;
}
}

Call updateAnimation() immediately on any starting animation.

git-svn-id: b675b909355d5cf946977f44a8ec5a6ceb3782e4@212 ed5b42cb-e716-0410-a449-f6a68f950b19