/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.pivotshare.hls.loader {

    import flash.display.DisplayObject;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    import flash.utils.Timer;
    import com.pivotshare.hls.loader.FragmentStream;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.demux.Demuxer;
    import org.mangui.hls.demux.DemuxHelper;
    import org.mangui.hls.demux.ID3Tag;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.model.AudioTrack;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.FragmentData;
    import org.mangui.hls.utils.AES;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
        import org.mangui.hls.utils.Hex;
    }

    /**
     * HLS Fragment Demuxed Streamer.
     * Tries to parallel URLStream design pattern, but is incomplete.
     *
     * This class encapsulates Demuxing, but in an inefficient way. This is not
     * meant to be performant for sequential Fragments in play.
     *
     * @class    FragmentDemuxedStream
     * @extends  EventDispatcher
     * @author   HGPA
     */
    public class FragmentDemuxedStream extends EventDispatcher {

        /*
         * DisplayObject needed by AES decrypter.
         */
        private var _displayObject : DisplayObject;

        /*
         * Fragment being loaded.
         */
        private var _fragment : Fragment;

        /*
         * URLStream used to download current Fragment.
         */
        private var _fragmentStream : FragmentStream;

        /*
         * Metrics for this stream.
         */
        private var _metrics : HLSLoadMetrics;

        /*
         * Demuxer needed for this Fragment.
         */
        private var _demux : Demuxer;

        /*
         * Options for streaming and demuxing.
         */
        private var _options : Object;

        //
        //
        //
        // Public Methods
        //
        //
        //

        /**
         * Create a FragmentStream.
         *
         * This constructor takes a reference to main DisplayObject, e.g. stage,
         * necessary for AES decryption control.
         *
         * TODO: It would be more appropriate to create a factory that itself
         * takes the DisplayObject (or a more flexible version of AES).
         *
         * @constructor
         * @param  {DisplayObject}  displayObject
         */
        public function FragmentDemuxedStream(displayObject : DisplayObject) : void {
            _displayObject = displayObject;
            _fragment = null;
            _fragmentStream = null;
            _metrics = null;
            _demux = null;
            _options = new Object();
        };

        /*
         * Return FragmentData of Fragment currently being downloaded.
         * Of immediate interest is the `bytes` field.
         *
         * @method getFragment
         * @return  {Fragment}
         */
        public function getFragment() : Fragment {
            return _fragment;
        }

        /*
         * Close the stream.
         *
         * @method  close
         */
        public function close() : void {
            if (_fragmentStream) {
                _fragmentStream.close();
            }

            if (_demux) {
                _demux.cancel();
                _demux = null;
            }
        }

        /**
         * Load a Fragment.
         *
         * This class/methods DOES NOT user reference of parameter. Instead it
         * clones the Fragment and manipulates this internally.
         *
         * @method  load
         * @param   {Fragment}   fragment  -  Fragment with details (cloned)
         * @param   {ByteArray}  key       -  Encryption Key
         * @return  {HLSLoadMetrics}
         */
        public function load(fragment : Fragment, key : ByteArray, options : Object) : HLSLoadMetrics {

            _options = options;

            // Clone Fragment, with new initilizations of deep fields
            // Passing around references is what is causing problems.
            // FragmentData is initialized as part of construction
            _fragment = new Fragment(
                fragment.url,
                fragment.duration,
                fragment.level,
                fragment.seqnum,
                fragment.start_time,
                fragment.continuity,
                fragment.program_date,
                fragment.decrypt_url,
                fragment.decrypt_iv, // We need this reference
                fragment.byterange_start_offset,
                fragment.byterange_end_offset,
                new Vector.<String>()
            )

            _fragmentStream = new FragmentStream(_displayObject);

            _fragmentStream.addEventListener(IOErrorEvent.IO_ERROR, onStreamError);
            _fragmentStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onStreamError);
            _fragmentStream.addEventListener(ProgressEvent.PROGRESS, onStreamProgress);
            _fragmentStream.addEventListener(Event.COMPLETE, onStreamComplete);

            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                Log.debug("FragmentDemuxedStream#load: " + fragmentString);
            }

            _metrics = _fragmentStream.load(_fragment, key);

            return _metrics;
        }

        //
        //
        //
        // FragmentStream Event Listeners
        //
        //
        //

        /**
         *
         * @method  onFragmentStreamProgress
         * @param   {ProgressEvent}  evt
         */
        private function onStreamProgress(evt : ProgressEvent) : void {

            _fragment = _fragmentStream.getFragment();

            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                Log.debug("FragmentDemuxedStream#onStreamProgress: " +
                    fragmentString + " Progress - " + evt.bytesLoaded + " of " + evt.bytesTotal);

                Log.debug("FragmentDemuxedStream#onStreamProgress: " +
                    fragmentString + " Fragment status - bytes.position / bytes.length / bytesLoaded " +
                    _fragment.data.bytes.position + " / " +
                    _fragment.data.bytes.length + " / " +
                    _fragment.data.bytesLoaded);
            }

            // If we are loading a partial Fragment then only parse when it has
            // completed loading to desired portion (See onFragmentStreamComplete)
            /*
            if (fragment.byterange_start_offset != -1) {
                return;
            }
            */

            // START PARSING METRICS
            if (_metrics.parsing_begin_time == 0) {
                _metrics.parsing_begin_time = getTimer();
            }

            // Demuxer has not yet been initialized as this is probably first
            // call to onFragmentStreamProgress, but demuxer may also be/remain
            // null due to unknown Fragment type. Probe is synchronous.
            if (_demux == null) {

                // It is possible we have run onFragmentStreamProgress before
                // without having sufficient data to probe
                //bytes.position = bytes.length;
                //bytes.writeBytes(byteArray);
                //byteArray = bytes;
                CONFIG::LOGGING {
                    var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                    Log.debug("FragmentDemuxedStream#onStreamProgress: Need a Demuxer for " + fragmentString);
                }

                _demux = DemuxHelper.probe(
                    _fragment.data.bytes,
                    null,
                    _onDemuxAudioTrackRequested,
                    _onDemuxProgress,
                    _onDemuxComplete,
                    _onDemuxVideoMetadata,
                    _onDemuxID3TagFound,
                    false
                    );
            }

            if (_demux) {

                // Demuxer expects the ByteArray delta
                var byteArray : ByteArray = new ByteArray();
                _fragment.data.bytes.readBytes(byteArray, 0, _fragment.data.bytes.length - _fragment.data.bytes.position);
                byteArray.position = 0;

                _demux.append(byteArray);
            }
        }

        /**
         * Called when FragmentStream completes.
         *
         * @method  onStreamComplete
         * @param   {Event}  evt
         */
        private function onStreamComplete(evt : Event) : void {

            // If demuxer is still null, then the Fragment type was invalid
            if (_demux == null) {
                CONFIG::LOGGING {
                    Log.error("FragmentDemuxedStream#onStreamComplete: unknown fragment type");
                    _fragment.data.bytes.position = 0;
                    var bytes2 : ByteArray = new ByteArray();
                    _fragment.data.bytes.readBytes(bytes2, 0, 512);
                    Log.debug2("FragmentDemuxedStream#onStreamComplete: frag dump(512 bytes)");
                    Log.debug2(Hex.fromArray(bytes2));
                }

                var err : ErrorEvent = new ErrorEvent(
                    ErrorEvent.ERROR,
                    false,
                    false,
                    "Unknown Fragment Type"
                    );

                dispatchEvent(err);

            } else {
                _demux.notifycomplete();
            }
        }

        /**
         * Called when FragmentStream has errored.
         *
         * @method  onStreamError
         * @param   {ProgressEvent}  evt
         */
        private function onStreamError(evt : ErrorEvent) : void {
            CONFIG::LOGGING {
                Log.error("FragmentDemuxedStream#onStreamError: " + evt.text);
            }
            dispatchEvent(evt);
        }

        //
        //
        //
        // Demuxer Callbacks
        //
        //
        //

        /**
         * Called when Demuxer needs to know which AudioTrack to parse for.
         *
         * FIXME: This callback simply returns the first audio track!
         * We need to pass this class the appropriate callback propogated from
         * UI layer. Yuck.
         *
         * @method  _onDemuxAudioTrackRequested
         * @param   {Vector<AudioTrack}  audioTrackList  -  List of AudioTracks
         * @return  {AudioTrack}  -  AudioTrack to parse
         */
        private function _onDemuxAudioTrackRequested(audioTrackList : Vector.<AudioTrack>) : AudioTrack {
            if (audioTrackList.length > 0) {
                return audioTrackList[0];
            } else {
                return null;
            }
        }

        /**
         * Called when Demuxer parsed portion of Fragment.
         *
         * @method  _onDemuxProgress
         * @param   {Vector<FLVTag}  tags
         */
        private function _onDemuxProgress(tags : Vector.<FLVTag>) : void {
            CONFIG::LOGGING {
                Log.debug("FragmentDemuxedStream#_onDemuxProgress");
            }

            _fragment.data.appendTags(tags);

            // TODO: Options to parse only portion of Fragment

            // FIXME: bytesLoaded and bytesTotal represent stats of the current
            // FragmentStream, not Demuxer, which is no longer bytes-relative.
            // What should we define here?
            var progressEvent : ProgressEvent = new ProgressEvent(
                ProgressEvent.PROGRESS,
                false,
                false,
                _fragment.data.bytes.length, // bytesLoaded
                _fragment.data.bytesTotal    // bytesTotal
                );

            dispatchEvent(progressEvent);
        };

        /**
         * Called when Demuxer has finished parsing Fragment.
         *
         * @method  _onDemuxComplete
         */
        private function _onDemuxComplete() : void {
            CONFIG::LOGGING {
                Log.debug("FragmentDemuxedStream#_onDemuxComplete");
            }

            _metrics.parsing_end_time = getTimer();

            var completeEvent : Event = new ProgressEvent(Event.COMPLETE);
            dispatchEvent(completeEvent);
        };

        /**
         * Called when Video metadata is parsed.
         *
         * Specifically, when Sequence Parameter Set (SPS) is found.
         *
         * @method  _onDemuxVideoMetadata
         * @param   {uint}  width
         * @param   {uint}  height
         */
        private function _onDemuxVideoMetadata(width : uint, height : uint) : void {
            if (_fragment.data.video_width == 0) {
                CONFIG::LOGGING {
                    Log.debug("FragmentDemuxedStream#_onDemuxVideoMetadata: AVC SPS = " + width + "x" + height);
                }
                _fragment.data.video_width = width;
                _fragment.data.video_height = height;
            }
        }

        /**
         * Called when ID3 tags are found.
         *
         * @method  _onDemuxID3TagFound
         * @param   {Vector.<ID3Tag>}  id3_tags  -  ID3 Tags
         */
        private function _onDemuxID3TagFound(id3_tags : Vector.<ID3Tag>) : void {
            _fragment.data.id3_tags = id3_tags;
        }

    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.pivotshare.hls.loader {

    import flash.display.DisplayObject;
    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    import flash.utils.Timer;
    import com.pivotshare.hls.loader.FragmentStream;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.demux.Demuxer;
    import org.mangui.hls.demux.DemuxHelper;
    import org.mangui.hls.demux.ID3Tag;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.model.AudioTrack;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.FragmentData;
    import org.mangui.hls.utils.AES;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
        import org.mangui.hls.utils.Hex;
    }

    /**
     * HLS Fragment Demuxed Streamer.
     * Tries to parallel URLStream design pattern, but is incomplete.
     *
     * This class encapsulates Demuxing, but in an inefficient way. This is not
     * meant to be performant for sequential Fragments in play.
     *
     * @class    FragmentDemuxedStream
     * @extends  EventDispatcher
     * @author   HGPA
     */
    public class FragmentDemuxedStream extends EventDispatcher {

        /*
         * DisplayObject needed by AES decrypter.
         */
        private var _displayObject : DisplayObject;

        /*
         * Fragment being loaded.
         */
        private var _fragment : Fragment;

        /*
         * URLStream used to download current Fragment.
         */
        private var _fragmentStream : FragmentStream;

        /*
         * Metrics for this stream.
         */
        private var _metrics : HLSLoadMetrics;

        /*
         * Demuxer needed for this Fragment.
         */
        private var _demux : Demuxer;

        /*
         * Options for streaming and demuxing.
         */
        private var _options : Object;

        //
        //
        //
        // Public Methods
        //
        //
        //

        /**
         * Create a FragmentStream.
         *
         * This constructor takes a reference to main DisplayObject, e.g. stage,
         * necessary for AES decryption control.
         *
         * TODO: It would be more appropriate to create a factory that itself
         * takes the DisplayObject (or a more flexible version of AES).
         *
         * @constructor
         * @param  {DisplayObject}  displayObject
         */
        public function FragmentDemuxedStream(displayObject : DisplayObject) : void {
            _displayObject = displayObject;
            _fragment = null;
            _fragmentStream = null;
            _metrics = null;
            _demux = null;
            _options = new Object();
        };

        /*
         * Return FragmentData of Fragment currently being downloaded.
         * Of immediate interest is the `bytes` field.
         *
         * @method getFragment
         * @return  {Fragment}
         */
        public function getFragment() : Fragment {
            return _fragment;
        }

        /*
         * Close the stream.
         *
         * @method  close
         */
        public function close() : void {
            if (_fragmentStream) {
                _fragmentStream.close();
            }

            if (_demux) {
                _demux.cancel();
                _demux = null;
            }
        }

        /**
         * Load a Fragment.
         *
         * This class/methods DOES NOT user reference of parameter. Instead it
         * clones the Fragment and manipulates this internally.
         *
         * @method  load
         * @param   {Fragment}   fragment  -  Fragment with details (cloned)
         * @param   {ByteArray}  key       -  Encryption Key
         * @return  {HLSLoadMetrics}
         */
        public function load(fragment : Fragment, key : ByteArray, options : Object) : HLSLoadMetrics {

            _options = options;

            // Clone Fragment, with new initilizations of deep fields
            // Passing around references is what is causing problems.
            // FragmentData is initialized as part of construction
            _fragment = new Fragment(
                fragment.url,
                fragment.duration,
                fragment.level,
                fragment.seqnum,
                fragment.start_time,
                fragment.continuity,
                fragment.program_date,
                fragment.decrypt_url,
                fragment.decrypt_iv, // We need this reference
                fragment.byterange_start_offset,
                fragment.byterange_end_offset,
                new Vector.<String>()
            )

            _fragmentStream = new FragmentStream(_displayObject);

            _fragmentStream.addEventListener(IOErrorEvent.IO_ERROR, onStreamError);
            _fragmentStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onStreamError);
            _fragmentStream.addEventListener(ProgressEvent.PROGRESS, onStreamProgress);
            _fragmentStream.addEventListener(Event.COMPLETE, onStreamComplete);

            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                Log.debug("FragmentDemuxedStream#load: " + fragmentString);
            }

            _metrics = _fragmentStream.load(_fragment, key);

            return _metrics;
        }

        //
        //
        //
        // FragmentStream Event Listeners
        //
        //
        //

        /**
         *
         * @method  onFragmentStreamProgress
         * @param   {ProgressEvent}  evt
         */
        private function onStreamProgress(evt : ProgressEvent) : void {

            _fragment = _fragmentStream.getFragment();

            CONFIG::LOGGING {
                var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                Log.debug2("FragmentDemuxedStream#onStreamProgress: " +
                    fragmentString + " Progress - " + evt.bytesLoaded + " of " + evt.bytesTotal);

                Log.debug2("FragmentDemuxedStream#onStreamProgress: " +
                    fragmentString + " Fragment status - bytes.position / bytes.length / bytesLoaded " +
                    _fragment.data.bytes.position + " / " +
                    _fragment.data.bytes.length + " / " +
                    _fragment.data.bytesLoaded);
            }

            // If we are loading a partial Fragment then only parse when it has
            // completed loading to desired portion (See onFragmentStreamComplete)
            /*
            if (fragment.byterange_start_offset != -1) {
                return;
            }
            */

            // START PARSING METRICS
            if (_metrics.parsing_begin_time == 0) {
                _metrics.parsing_begin_time = getTimer();
            }

            // Demuxer has not yet been initialized as this is probably first
            // call to onFragmentStreamProgress, but demuxer may also be/remain
            // null due to unknown Fragment type. Probe is synchronous.
            if (_demux == null) {

                // It is possible we have run onFragmentStreamProgress before
                // without having sufficient data to probe
                //bytes.position = bytes.length;
                //bytes.writeBytes(byteArray);
                //byteArray = bytes;
                CONFIG::LOGGING {
                    var fragmentString : String = "Fragment[" + _fragment.level + "][" + _fragment.seqnum + "]";
                    Log.debug2("FragmentDemuxedStream#onStreamProgress: Need a Demuxer for " + fragmentString);
                }

                _demux = DemuxHelper.probe(
                    _fragment.data.bytes,
                    null,
                    _onDemuxAudioTrackRequested,
                    _onDemuxProgress,
                    _onDemuxComplete,
                    _onDemuxVideoMetadata,
                    _onDemuxID3TagFound,
                    false
                    );
            }

            if (_demux) {

                // Demuxer expects the ByteArray delta
                var byteArray : ByteArray = new ByteArray();
                _fragment.data.bytes.readBytes(byteArray, 0, _fragment.data.bytes.length - _fragment.data.bytes.position);
                byteArray.position = 0;

                _demux.append(byteArray);
            }
        }

        /**
         * Called when FragmentStream completes.
         *
         * @method  onStreamComplete
         * @param   {Event}  evt
         */
        private function onStreamComplete(evt : Event) : void {

            // If demuxer is still null, then the Fragment type was invalid
            if (_demux == null) {
                CONFIG::LOGGING {
                    Log.error("FragmentDemuxedStream#onStreamComplete: unknown fragment type");
                    _fragment.data.bytes.position = 0;
                    var bytes2 : ByteArray = new ByteArray();
                    _fragment.data.bytes.readBytes(bytes2, 0, 512);
                    Log.debug2("FragmentDemuxedStream#onStreamComplete: frag dump(512 bytes)");
                    Log.debug2(Hex.fromArray(bytes2));
                }

                var err : ErrorEvent = new ErrorEvent(
                    ErrorEvent.ERROR,
                    false,
                    false,
                    "Unknown Fragment Type"
                    );

                dispatchEvent(err);

            } else {
                _demux.notifycomplete();
            }
        }

        /**
         * Called when FragmentStream has errored.
         *
         * @method  onStreamError
         * @param   {ProgressEvent}  evt
         */
        private function onStreamError(evt : ErrorEvent) : void {
            CONFIG::LOGGING {
                Log.error("FragmentDemuxedStream#onStreamError: " + evt.text);
            }
            dispatchEvent(evt);
        }

        //
        //
        //
        // Demuxer Callbacks
        //
        //
        //

        /**
         * Called when Demuxer needs to know which AudioTrack to parse for.
         *
         * FIXME: This callback simply returns the first audio track!
         * We need to pass this class the appropriate callback propogated from
         * UI layer. Yuck.
         *
         * @method  _onDemuxAudioTrackRequested
         * @param   {Vector<AudioTrack}  audioTrackList  -  List of AudioTracks
         * @return  {AudioTrack}  -  AudioTrack to parse
         */
        private function _onDemuxAudioTrackRequested(audioTrackList : Vector.<AudioTrack>) : AudioTrack {
            if (audioTrackList.length > 0) {
                return audioTrackList[0];
            } else {
                return null;
            }
        }

        /**
         * Called when Demuxer parsed portion of Fragment.
         *
         * @method  _onDemuxProgress
         * @param   {Vector<FLVTag}  tags
         */
        private function _onDemuxProgress(tags : Vector.<FLVTag>) : void {
            CONFIG::LOGGING {
                Log.debug2("FragmentDemuxedStream#_onDemuxProgress");
            }

            _fragment.data.appendTags(tags);

            // TODO: Options to parse only portion of Fragment

            // FIXME: bytesLoaded and bytesTotal represent stats of the current
            // FragmentStream, not Demuxer, which is no longer bytes-relative.
            // What should we define here?
            var progressEvent : ProgressEvent = new ProgressEvent(
                ProgressEvent.PROGRESS,
                false,
                false,
                _fragment.data.bytes.length, // bytesLoaded
                _fragment.data.bytesTotal    // bytesTotal
                );

            dispatchEvent(progressEvent);
        };

        /**
         * Called when Demuxer has finished parsing Fragment.
         *
         * @method  _onDemuxComplete
         */
        private function _onDemuxComplete() : void {
            CONFIG::LOGGING {
                Log.debug2("FragmentDemuxedStream#_onDemuxComplete");
            }

            _metrics.parsing_end_time = getTimer();

            var completeEvent : Event = new ProgressEvent(Event.COMPLETE);
            dispatchEvent(completeEvent);
        };

        /**
         * Called when Video metadata is parsed.
         *
         * Specifically, when Sequence Parameter Set (SPS) is found.
         *
         * @method  _onDemuxVideoMetadata
         * @param   {uint}  width
         * @param   {uint}  height
         */
        private function _onDemuxVideoMetadata(width : uint, height : uint) : void {
            if (_fragment.data.video_width == 0) {
                CONFIG::LOGGING {
                    Log.debug2("FragmentDemuxedStream#_onDemuxVideoMetadata: AVC SPS = " + width + "x" + height);
                }
                _fragment.data.video_width = width;
                _fragment.data.video_height = height;
            }
        }

        /**
         * Called when ID3 tags are found.
         *
         * @method  _onDemuxID3TagFound
         * @param   {Vector.<ID3Tag>}  id3_tags  -  ID3 Tags
         */
        private function _onDemuxID3TagFound(id3_tags : Vector.<ID3Tag>) : void {
            _fragment.data.id3_tags = id3_tags;
        }

    }
}

Make all logging debug2

[FragmentDemuxedStream] Make all logging debug2


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleAnimation;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.geom.Color;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;
		private var _current:MiracleAnimation;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose animation");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					_window.setSize(_window.height, list.height + 20);
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
			var w2:Window = new Window(this, _window.x, _window.y + _window.height + 10, "FPS");
			list = new List(w2, 0, 0, [1, 5, 16, 24, 30, 35, 40, 60]);
			w2.height = 120;
			list.addEventListener(Event.SELECT, this.selectFpsHandler);
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes, 1);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//\TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			var mesh:String = animation.substr(0, animation.lastIndexOf("."));
			animation = animation.substr(animation.lastIndexOf(".") + 1);
			if(_current == null){
				_current = Miracle.currentScene.createAnimation(mesh, animation, 60);
				_current.moveTO(this.stage.stageWidth >> 1, this.stage.stageHeight >> 1);
				_current.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
			}else{
				_current.mesh = mesh;
				_current.animation = animation;
				_current.play();
			}
		}

		private function imageAddedToStage(event:Event):void {
			trace("Added to stage");
		}

		private function selectFpsHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			if(_current != null){
				_current.fps = int(list.selectedItem);
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.animations.AnimationHelper;
	import com.merlinds.miracle.display.MiracleAnimation;
	import com.merlinds.miracle.display.MiracleDisplayObject;
	import com.merlinds.miracle.geom.Color;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MafReader;
	import com.merlinds.miracle_tool.models.AppModel;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;

	[SWF(backgroundColor="0x333333", frameRate=60)]
	public class ViewerView extends Sprite {

		private var _model:AppModel;
		private var _assets:Vector.<Asset>;
		private var _window:Window;
		private var _name:String;
		private var _current:MiracleAnimation;

		public function ViewerView(model:AppModel = null) {
			super();
			_model = model;
			if(_model == null){
				_model = new AppModel();
			}

			this.addEventListener(Event.ADDED_TO_STAGE, this.initialize);
		}
		//==============================================================================
		//{region							PUBLIC METHODS
		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function initialize(event:Event):void {
			this.removeEventListener(event.type, this.initialize);
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
			Miracle.start(this.stage, this.createHandler, true);
		}

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose animation");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					_window.setSize(_window.height, list.height + 20);
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
			var w2:Window = new Window(this, _window.x, _window.y + _window.height + 10, "FPS");
			list = new List(w2, 0, 0, [1, 5, 16, 24, 30, 35, 40, 60]);
			w2.height = 120;
			list.addEventListener(Event.SELECT, this.selectFpsHandler);
		}

		[Inline]
		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];
			var reader:MafReader = new MafReader();
			reader.execute(bytes, 1);
			var animations:Vector.<AnimationHelper> = reader.animations;
			for each(var animation:AnimationHelper in animations){
				result.push(animation.name);
			}
			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		private function createHandler(animation:Boolean = false):void {
			//\TODO: Show view if it exit
			if(animation || _model.viewerInput == null){
				_model.viewerInput = _model.lastFileDirection;
				_model.viewerInput.addEventListener(Event.SELECT, this.selectFileHandler);
				_model.viewerInput.browseForOpen("Open " + animation ? "Animation" : "Sprite"
						+ "file that you want to view");
			}
		}

		private function selectFileHandler(event:Event):void {
			_model.viewerInput.removeEventListener(event.type, this.selectFileHandler);
			_model.lastFileDirection = _model.viewerInput.parent;
			var byteArray:ByteArray = new ByteArray();
			var stream:FileStream = new FileStream();
			stream.open(_model.viewerInput, FileMode.READ);
			stream.readBytes(byteArray);
			stream.close();
			//parse
			if(_assets == null)_assets = new <Asset>[];
			var asset:Asset = new Asset(_model.viewerInput.name, byteArray);
			if(asset.type == Asset.TIMELINE_TYPE){
				_name = asset.name;
			}
			_assets.push(asset);
			if(_assets.length > 1){
				this.choseAnimation();
				Miracle.createScene(_assets,null, 1);
				Miracle.resume();
			}else{
				this.createHandler(true);
			}
		}

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			var animation:String = list.selectedItem.toString();
			log(this, "selectAnimationHandler", animation);
			var mesh:String = animation.substr(0, animation.lastIndexOf("."));
			animation = animation.substr(animation.lastIndexOf(".") + 1);
			if(_current == null){
				_current = Miracle.currentScene.createAnimation(mesh, animation, 60);
				_current.moveTO(this.stage.stageWidth >> 1, this.stage.stageHeight >> 1);
				_current.addEventListener(Event.ADDED_TO_STAGE, this.imageAddedToStage);
			}else{
				_current.mesh = mesh;
				_current.animation = animation;
				_current.play();
			}
		}

		private function imageAddedToStage(event:Event):void {
			trace("Added to stage");
		}

		private function selectFpsHandler(event:Event):void {
			var list:List = event.target as List;
			//add animation to miracle
			if(_current != null){
				_current.fps = int(list.selectedItem);
			}
		}
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}

change initialization

change initialization


package battlecode.client.viewer.render {
    import battlecode.common.ActionType;
    import battlecode.common.Direction;
    import battlecode.common.MapLocation;
    import battlecode.common.RobotType;
    import battlecode.common.Team;
    import battlecode.events.RobotEvent;

    import mx.containers.Canvas;
    import mx.controls.Image;
    import mx.core.UIComponent;

    [Event(name="indicatorStringChange", type="battlecode.events.RobotEvent")]
    public class DrawRobot extends Canvas implements DrawObject {
        private var actions:Vector.<DrawAction>;

        private var broadcastAnimation:BroadcastAnimation;
        private var explosionAnimation:ExplosionAnimation;

        private var overlayCanvas:UIComponent;
        private var imageCanvas:UIComponent;
        private var image:Image;

        // size
        private var overrideSize:Number;

        // indicator strings
        private var indicatorStrings:Vector.<String> = new Vector.<String>(3, true);

        // movement animation
        private var drawX:Number, drawY:Number;
        private var movementDelay:uint;

        // attack animation
        private var targetLocation:MapLocation;

        private var selected:Boolean = false;

        private var robotID:uint;
        private var type:String;
        private var team:String;
        private var location:MapLocation;
        private var direction:String;
        private var energon:Number = 0;
        private var maxEnergon:Number = 0;
        private var flux:Number = 0;
        private var maxFlux:Number = 0;
        private var aura:String;
        private var alive:Boolean = true;

        public function DrawRobot(robotID:uint, type:String, team:String, overrideSize:Number = 0) {
            this.robotID = robotID;
            this.type = type;
            this.team = team;
            this.maxEnergon = RobotType.maxEnergon(type);
            this.maxFlux = RobotType.maxFlux(type);

            this.actions = new Vector.<DrawAction>();

            this.overrideSize = overrideSize;

            // set the unit avatar image
            var avatarClass:Class = getUnitAvatar(type, team);
            this.imageCanvas = new UIComponent();
            this.image = new Image();
            this.image.source = avatarClass;
            this.image.width = getImageSize(true);
            this.image.height = getImageSize(true);
            this.image.x = -this.image.width / 2;
            this.image.y = -this.image.height / 2;
            this.imageCanvas.addChild(image);
            this.addChild(imageCanvas);

            this.width = this.image.width;
            this.height = this.image.height;

            this.overlayCanvas = new UIComponent();
            this.addChild(overlayCanvas);

            // set the hit area for click selection
            this.hitArea = imageCanvas;

            // animations
            this.broadcastAnimation = new BroadcastAnimation(0, team);
            this.addChild(broadcastAnimation);

            this.explosionAnimation = new ExplosionAnimation();
            this.addChild(explosionAnimation);
        }

        public function clone():DrawObject {
            var d:DrawRobot = new DrawRobot(robotID, type, team, overrideSize);
            d.location = location;
            d.energon = energon;
            d.movementDelay = movementDelay;
            d.targetLocation = targetLocation;
            d.alive = alive;

            d.actions = new Vector.<DrawAction>(actions.length);
            for each (var o:DrawAction in actions) {
                d.actions.push(o.clone());
            }

            d.removeChild(d.broadcastAnimation);
            d.removeChild(d.explosionAnimation);
            d.broadcastAnimation = broadcastAnimation.clone() as BroadcastAnimation;
            d.explosionAnimation = explosionAnimation.clone() as ExplosionAnimation;
            d.addChild(d.broadcastAnimation);
            d.addChild(d.explosionAnimation);

            return d;
        }

        private function addAction(d:DrawAction):void {
            actions.push(d);
        }

        public function getRobotID():uint {
            return robotID;
        }

        public function getType():String {
            return type;
        }

        public function getTeam():String {
            return team;
        }

        public function getLocation():MapLocation {
            return location;
        }

        public function getSelected():Boolean {
            return selected;
        }

        public function getIndicatorString(index:uint):String {
            return indicatorStrings[index];
        }

        public function setLocation(location:MapLocation):void {
            this.location = location;
        }

        public function setEnergon(amt:Number):void {
            this.energon = Math.min(Math.max(0, amt), maxEnergon);
        }

        public function setSelected(val:Boolean):void {
            this.selected = val;
        }

        public function setIndicatorString(str:String, index:uint):void {
            indicatorStrings[index] = str;
            dispatchEvent(new RobotEvent(RobotEvent.INDICATOR_STRING_CHANGE, false, false, this));
        }

        public function setOverrideSize(overrideSize:Number):void {
            this.overrideSize = overrideSize;
            this.draw(true);
        }

        public function attack(targetLocation:MapLocation):void {
            this.targetLocation = targetLocation;
            this.addAction(new DrawAction(ActionType.ATTACKING, RobotType.attackDelay(type)));
        }

        public function broadcast():void {
            this.broadcastAnimation.broadcast();
        }

        public function destroyUnit():void {
            this.explosionAnimation.explode();
            this.alive = false;
        }

        public function moveToLocation(location:MapLocation):void {
            this.movementDelay = Direction.isDiagonal(direction) ?
                    RobotType.movementDelayDiagonal(type) :
                    RobotType.movementDelay(type);
            this.location = location;
            this.addAction(new DrawAction(ActionType.MOVING, movementDelay));
        }

        public function isAlive():Boolean {
            return alive || explosionAnimation.isAlive();
        }

        public function updateRound():void {
            // update actions
            for (var i:uint = 0; i < actions.length; i++) {
                var o:DrawAction = actions[i];
                o.decreaseRound();
                if (o.getRounds() <= 0) {
                    actions.splice(i, 1);
                    i--;
                }
            }

            // clear aura
            aura = null;

            // update animations
            broadcastAnimation.updateRound();
            explosionAnimation.updateRound();
        }

        public function draw(force:Boolean = false):void {
            if (explosionAnimation.isExploding()) {
                this.imageCanvas.visible = false;
                this.overlayCanvas.visible = false;
                this.graphics.clear();

                explosionAnimation.draw(force);
                return;
            }

            // draw direction
            this.imageCanvas.rotation = directionToRotation(direction);

            if (force) {
                this.image.width = getImageSize(true);
                this.image.height = getImageSize(true);
                this.image.x = -this.image.width / 2;
                this.image.y = -this.image.height / 2;
            }

            // clear the graphics object once
            this.graphics.clear();
            this.overlayCanvas.graphics.clear();

            var o:DrawAction;
            var movementRounds:uint = 0;
            for each (o in actions) {
                if (o.getType() == ActionType.MOVING) {
                    movementRounds = o.getRounds();
                    break;
                }
            }

            drawX = calculateDrawX(movementRounds);
            drawY = calculateDrawY(movementRounds);

            for each (o in actions) {
                switch (o.getType()) {
                    case ActionType.ATTACKING:
                        drawAttack();
                        break;
                    case ActionType.MOVING:
                        drawMovement();
                        break;
                }
            }

            drawEnergonBar();
            drawSelected();

            // draw animations
            broadcastAnimation.draw(force);
        }

        ///////////////////////////////////////////////////////
        ////////////// DRAWING HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function drawEnergonBar():void {
            if (!RenderConfiguration.showEnergon())
                return;

            var ratio:Number = RobotType.isEncampment(type) ? (flux / maxFlux) : (energon / maxEnergon);
            var size:Number = getImageSize();
            this.graphics.lineStyle();
            this.graphics.beginFill(0x00FF00, 0.8);
            this.graphics.drawRect(-size / 2, size / 2, ratio * size, 5 * getImageScale());
            this.graphics.endFill();
            this.graphics.beginFill(0x000000, 0.8);
            this.graphics.drawRect(-size / 2 + ratio * size, size / 2, (1 - ratio) * size, 5 * getImageScale());
            this.graphics.endFill();
        }

        private function drawAttack():void {
            var targetOffsetX:Number = (targetLocation.getX() - location.getX()) * getImageSize();
            var targetOffsetY:Number = (targetLocation.getY() - location.getY()) * getImageSize();

            this.graphics.lineStyle(2, team == Team.A ? 0xFF0000 : 0x0000FF);
            this.graphics.moveTo(0, 0);
            this.graphics.lineTo(targetOffsetX - drawX, targetOffsetY - drawY);
            this.graphics.drawCircle(targetOffsetX - drawX, targetOffsetY - drawY, getImageSize() / 2 * .6);
        }

        private function drawMovement():void {
            if (RenderConfiguration.showDiscrete()) return;

            this.x += drawX;
            this.y += drawY;
        }

        private function drawSelected():void {
            var size:Number = getImageSize();

            if (selected) {
                this.graphics.lineStyle(2, 0xFFFFFF);
                this.graphics.drawRect(-size / 2, -size / 2, size, size);
            }
        }

        ///////////////////////////////////////////////////////
        ////////////// PRIVATE HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function getImageSize(scale:Boolean = false):Number {
            if (overrideSize)
                return overrideSize;
            return RenderConfiguration.getGridSize() * (scale ? getUnitScale(type) : 1.0);
        }

        private function getImageScale():Number {
            if (overrideSize)
                return 1.0;
            return RenderConfiguration.getScalingFactor();
        }

        private function getUnitAvatar(type:String, team:String):Class {
            return ImageAssets[type + "_" + team];
        }

        private function getUnitScale(type:String):Number {
            switch (type) {
                case RobotType.HQ:
                    return 1.4;
                case RobotType.SOLDIER:
                    return 1.0;
                default:
                    return 1.0;
            }
        }

        private function getUnitOffset(type:String):Number {
            return 0;
        }

        private function directionToRotation(dir:String):int {
            switch (dir) {
                case Direction.NORTH:
                    return -90;
                case Direction.NORTH_EAST:
                    return -45;
                case Direction.EAST:
                    return 0;
                case Direction.SOUTH_EAST:
                    return 45;
                case Direction.SOUTH:
                    return 90;
                case Direction.SOUTH_WEST:
                    return 135;
                case Direction.WEST:
                    return 180;
                case Direction.NORTH_WEST:
                    return -135;
                default:
                    return 0;
            }
        }

        private function directionOffsetX(dir:String):int {
            switch (dir) {
                case Direction.NORTH_EAST:
                    return +1;
                case Direction.NORTH_WEST:
                    return -1;
                case Direction.SOUTH_EAST:
                    return +1;
                case Direction.SOUTH_WEST:
                    return -1;
                case Direction.NORTH:
                    return 0;
                case Direction.SOUTH:
                    return 0;
                case Direction.EAST:
                    return +1;
                case Direction.WEST:
                    return -1;
                default:
                    return 0;
            }
        }

        private function directionOffsetY(dir:String):int {
            switch (dir) {
                case Direction.NORTH_EAST:
                    return -1;
                case Direction.NORTH_WEST:
                    return -1;
                case Direction.SOUTH_EAST:
                    return +1;
                case Direction.SOUTH_WEST:
                    return +1;
                case Direction.NORTH:
                    return -1;
                case Direction.SOUTH:
                    return +1;
                case Direction.EAST:
                    return 0;
                case Direction.WEST:
                    return 0;
                default:
                    return 0;
            }
        }

        private function calculateDrawX(rounds:uint):Number {
            if (RenderConfiguration.showDiscrete()) return 0;
            return getImageSize() * directionOffsetX(direction) * (rounds / movementDelay);
        }

        private function calculateDrawY(rounds:uint):Number {
            if (RenderConfiguration.showDiscrete()) return 0;
            return getImageSize() * directionOffsetY(direction) * (rounds / movementDelay);
        }

    }

}

package battlecode.client.viewer.render {
    import battlecode.common.ActionType;
    import battlecode.common.Direction;
    import battlecode.common.MapLocation;
    import battlecode.common.RobotType;
    import battlecode.common.Team;
    import battlecode.events.RobotEvent;

    import mx.containers.Canvas;
    import mx.controls.Image;
    import mx.core.UIComponent;

    [Event(name="indicatorStringChange", type="battlecode.events.RobotEvent")]
    public class DrawRobot extends Canvas implements DrawObject {
        private var actions:Vector.<DrawAction>;

        private var broadcastAnimation:BroadcastAnimation;
        private var explosionAnimation:ExplosionAnimation;

        private var overlayCanvas:UIComponent;
        private var imageCanvas:UIComponent;
        private var image:Image;

        // size
        private var overrideSize:Number;

        // indicator strings
        private var indicatorStrings:Vector.<String> = new Vector.<String>(3, true);

        // movement animation
        private var drawX:Number, drawY:Number;
        private var movementDelay:uint;

        // attack animation
        private var targetLocation:MapLocation;

        private var selected:Boolean = false;

        private var robotID:uint;
        private var type:String;
        private var team:String;
        private var location:MapLocation;
        private var direction:String;
        private var energon:Number = 0;
        private var maxEnergon:Number = 0;
        private var flux:Number = 0;
        private var maxFlux:Number = 0;
        private var aura:String;
        private var alive:Boolean = true;

        public function DrawRobot(robotID:uint, type:String, team:String, overrideSize:Number = 0) {
            this.robotID = robotID;
            this.type = type;
            this.team = team;
            this.maxEnergon = RobotType.maxEnergon(type);
            this.maxFlux = RobotType.maxFlux(type);

            this.actions = new Vector.<DrawAction>();

            this.overrideSize = overrideSize;

            // set the unit avatar image
            var avatarClass:Class = getUnitAvatar(type, team);
            this.imageCanvas = new UIComponent();
            this.image = new Image();
            this.image.source = avatarClass;
            this.image.width = getImageSize(true);
            this.image.height = getImageSize(true);
            this.image.x = -this.image.width / 2;
            this.image.y = -this.image.height / 2;
            this.imageCanvas.addChild(image);
            this.addChild(imageCanvas);

            this.width = this.image.width;
            this.height = this.image.height;

            this.overlayCanvas = new UIComponent();
            this.addChild(overlayCanvas);

            // set the hit area for click selection
            this.hitArea = imageCanvas;

            // animations
            this.broadcastAnimation = new BroadcastAnimation(0, team);
            this.addChild(broadcastAnimation);

            this.explosionAnimation = new ExplosionAnimation();
            this.addChild(explosionAnimation);
        }

        public function clone():DrawObject {
            var d:DrawRobot = new DrawRobot(robotID, type, team, overrideSize);
            d.location = location;
            d.energon = energon;
            d.movementDelay = movementDelay;
            d.targetLocation = targetLocation;
            d.alive = alive;

            d.actions = new Vector.<DrawAction>(actions.length);
            for each (var o:DrawAction in actions) {
                d.actions.push(o.clone());
            }

            d.removeChild(d.broadcastAnimation);
            d.removeChild(d.explosionAnimation);
            d.broadcastAnimation = broadcastAnimation.clone() as BroadcastAnimation;
            d.explosionAnimation = explosionAnimation.clone() as ExplosionAnimation;
            d.addChild(d.broadcastAnimation);
            d.addChild(d.explosionAnimation);

            return d;
        }

        private function addAction(d:DrawAction):void {
            actions.push(d);
        }

        public function getRobotID():uint {
            return robotID;
        }

        public function getType():String {
            return type;
        }

        public function getTeam():String {
            return team;
        }

        public function getLocation():MapLocation {
            return location;
        }

        public function getSelected():Boolean {
            return selected;
        }

        public function getIndicatorString(index:uint):String {
            return indicatorStrings[index];
        }

        public function setLocation(location:MapLocation):void {
            this.location = location;
        }

        public function setEnergon(amt:Number):void {
            this.energon = Math.min(Math.max(0, amt), maxEnergon);
        }

        public function setSelected(val:Boolean):void {
            this.selected = val;
        }

        public function setIndicatorString(str:String, index:uint):void {
            indicatorStrings[index] = str;
            dispatchEvent(new RobotEvent(RobotEvent.INDICATOR_STRING_CHANGE, false, false, this));
        }

        public function setOverrideSize(overrideSize:Number):void {
            this.overrideSize = overrideSize;
            this.draw(true);
        }

        public function attack(targetLocation:MapLocation):void {
            this.targetLocation = targetLocation;
            this.addAction(new DrawAction(ActionType.ATTACKING, RobotType.attackDelay(type)));
        }

        public function broadcast():void {
            this.broadcastAnimation.broadcast();
        }

        public function destroyUnit():void {
            this.explosionAnimation.explode();
            this.alive = false;
        }

        public function moveToLocation(location:MapLocation):void {
            this.movementDelay = Direction.isDiagonal(direction) ?
                    RobotType.movementDelayDiagonal(type) :
                    RobotType.movementDelay(type);
            this.location = location;
            this.addAction(new DrawAction(ActionType.MOVING, movementDelay));
        }

        public function isAlive():Boolean {
            return alive || explosionAnimation.isAlive();
        }

        public function updateRound():void {
            // update actions
            for (var i:uint = 0; i < actions.length; i++) {
                var o:DrawAction = actions[i];
                o.decreaseRound();
                if (o.getRounds() <= 0) {
                    actions.splice(i, 1);
                    i--;
                }
            }

            // clear aura
            aura = null;

            // update animations
            broadcastAnimation.updateRound();
            explosionAnimation.updateRound();
        }

        public function draw(force:Boolean = false):void {
            if (explosionAnimation.isExploding()) {
                this.imageCanvas.visible = false;
                this.overlayCanvas.visible = false;
                this.graphics.clear();

                explosionAnimation.draw(force);
                return;
            }

            // draw direction
            this.imageCanvas.rotation = directionToRotation(direction);

            if (force) {
                this.image.width = getImageSize(true);
                this.image.height = getImageSize(true);
                this.image.x = -this.image.width / 2;
                this.image.y = -this.image.height / 2;
            }

            // clear the graphics object once
            this.graphics.clear();
            this.overlayCanvas.graphics.clear();

            var o:DrawAction;
            var movementRounds:uint = 0;
            for each (o in actions) {
                if (o.getType() == ActionType.MOVING) {
                    movementRounds = o.getRounds();
                    break;
                }
            }

            drawX = calculateDrawX(movementRounds);
            drawY = calculateDrawY(movementRounds);

            for each (o in actions) {
                switch (o.getType()) {
                    case ActionType.ATTACKING:
                        drawAttack();
                        break;
                    case ActionType.MOVING:
                        drawMovement();
                        break;
                }
            }

            drawEnergonBar();
            drawSelected();

            // draw animations
            broadcastAnimation.draw(force);
        }

        ///////////////////////////////////////////////////////
        ////////////// DRAWING HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function drawEnergonBar():void {
            if (!RenderConfiguration.showEnergon())
                return;

            var ratio:Number = RobotType.isEncampment(type) ? (flux / maxFlux) : (energon / maxEnergon);
            var size:Number = getImageSize();
            this.graphics.lineStyle();
            this.graphics.beginFill(0x00FF00, 0.8);
            this.graphics.drawRect(-size / 2, size / 2, ratio * size, 5 * getImageScale());
            this.graphics.endFill();
            this.graphics.beginFill(0x000000, 0.8);
            this.graphics.drawRect(-size / 2 + ratio * size, size / 2, (1 - ratio) * size, 5 * getImageScale());
            this.graphics.endFill();
        }

        private function drawAttack():void {
            var targetOffsetX:Number = (targetLocation.getX() - location.getX()) * getImageSize();
            var targetOffsetY:Number = (targetLocation.getY() - location.getY()) * getImageSize();

            this.graphics.lineStyle(2, team == Team.A ? 0xFF0000 : 0x0000FF);
            this.graphics.moveTo(0, 0);
            this.graphics.lineTo(targetOffsetX - drawX, targetOffsetY - drawY);
            this.graphics.drawCircle(targetOffsetX - drawX, targetOffsetY - drawY, getImageSize() / 2 * .6);
        }

        private function drawMovement():void {
            if (RenderConfiguration.showDiscrete()) return;

            this.x += drawX;
            this.y += drawY;
        }

        private function drawSelected():void {
            var size:Number = getImageSize();

            if (selected) {
                this.graphics.lineStyle(2, 0xFFFFFF);
                this.graphics.drawRect(-size / 2, -size / 2, size, size);
            }
        }

        ///////////////////////////////////////////////////////
        ////////////// PRIVATE HELPER FUNCTIONS ///////////////
        ///////////////////////////////////////////////////////

        private function getImageSize(scale:Boolean = false):Number {
            if (overrideSize)
                return overrideSize;
            return RenderConfiguration.getGridSize() * (scale ? getUnitScale(type) : 1.0);
        }

        private function getImageScale():Number {
            if (overrideSize)
                return 1.0;
            return RenderConfiguration.getScalingFactor();
        }

        private function getUnitAvatar(type:String, team:String):Class {
            return ImageAssets[type + "_" + team];
        }

        private function getUnitScale(type:String):Number {
            switch (type) {
                case RobotType.HQ:
                    return 2.0;
                case RobotType.SOLDIER:
                    return 1.0;
                default:
                    return 1.6;
            }
        }

        private function getUnitOffset(type:String):Number {
            return 0;
        }

        private function directionToRotation(dir:String):int {
            switch (dir) {
                case Direction.NORTH:
                    return -90;
                case Direction.NORTH_EAST:
                    return -45;
                case Direction.EAST:
                    return 0;
                case Direction.SOUTH_EAST:
                    return 45;
                case Direction.SOUTH:
                    return 90;
                case Direction.SOUTH_WEST:
                    return 135;
                case Direction.WEST:
                    return 180;
                case Direction.NORTH_WEST:
                    return -135;
                default:
                    return 0;
            }
        }

        private function directionOffsetX(dir:String):int {
            switch (dir) {
                case Direction.NORTH_EAST:
                    return +1;
                case Direction.NORTH_WEST:
                    return -1;
                case Direction.SOUTH_EAST:
                    return +1;
                case Direction.SOUTH_WEST:
                    return -1;
                case Direction.NORTH:
                    return 0;
                case Direction.SOUTH:
                    return 0;
                case Direction.EAST:
                    return +1;
                case Direction.WEST:
                    return -1;
                default:
                    return 0;
            }
        }

        private function directionOffsetY(dir:String):int {
            switch (dir) {
                case Direction.NORTH_EAST:
                    return -1;
                case Direction.NORTH_WEST:
                    return -1;
                case Direction.SOUTH_EAST:
                    return +1;
                case Direction.SOUTH_WEST:
                    return +1;
                case Direction.NORTH:
                    return -1;
                case Direction.SOUTH:
                    return +1;
                case Direction.EAST:
                    return 0;
                case Direction.WEST:
                    return 0;
                default:
                    return 0;
            }
        }

        private function calculateDrawX(rounds:uint):Number {
            if (RenderConfiguration.showDiscrete()) return 0;
            return getImageSize() * directionOffsetX(direction) * (rounds / movementDelay);
        }

        private function calculateDrawY(rounds:uint):Number {
            if (RenderConfiguration.showDiscrete()) return 0;
            return getImageSize() * directionOffsetY(direction) * (rounds / movementDelay);
        }

    }

}

make hq's and encampments more visible (larger)


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mangui.basic {
    import org.mangui.hls.HLS;
    import org.mangui.hls.event.HLSEvent;

    import flash.display.Sprite;
    import flash.media.Video;

    public class Player extends Sprite {
        private var hls : HLS = null;
        private var video : Video = null;

        public function Player() {
            hls = new HLS();

            video = new Video(640, 480);
            addChild(video);
            video.x = 0;
            video.y = 0;
            video.smoothing = true;
            video.attachNetStream(hls.stream);
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, manifestHandler);
            hls.load("http://domain.com/hls/m1.m3u8");
        }

        public function manifestHandler(event : HLSEvent) : void {
            hls.stream.play();
        };
    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.basic {
    import org.mangui.hls.HLS;
    import org.mangui.hls.event.HLSEvent;

    import flash.display.Sprite;
    import flash.media.Video;

    public class Player extends Sprite {
        private var hls : HLS = null;
        private var video : Video = null;

        public function Player() {
            hls = new HLS();
            hls.stage = this.stage;
            video = new Video(640, 480);
            addChild(video);
            video.x = 0;
            video.y = 0;
            video.smoothing = true;
            video.attachNetStream(hls.stream);
            hls.addEventListener(HLSEvent.MANIFEST_LOADED, manifestHandler);
            hls.load("http://domain.com/hls/m1.m3u8");
        }

        public function manifestHandler(event : HLSEvent) : void {
            hls.stream.play(null, -1);
        };
    }
}

set stage

basic/Player.as: set stage


package aerys.minko.scene.controller
{
	import aerys.minko.scene.data.TransformDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	public final class TransformController extends AbstractController
	{
		private var _node	: ISceneNode;
		private var _data	: TransformDataProvider;

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											node 	: ISceneNode) : void
		{
			if (_node)
				throw new Error();

			_node = node;
			_node.transform.changed.add(transformChangedHandler);
			_node.added.add(addedHandler);
			_node.removed.add(removedHandler);

			if (_node is Mesh)
			{
				_node.addedToScene.add(meshAddedToSceneHandler);
				_node.removedFromScene.add(meshRemovedFromSceneHandler);
			}

			transformChangedHandler(_node.transform);
		}

		private function targetRemovedHandler(ctrl	: TransformController,
											  node 	: ISceneNode) : void
		{
			if (_node is Mesh)
			{
				_node.addedToScene.remove(meshAddedToSceneHandler);
				_node.removedFromScene.remove(meshRemovedFromSceneHandler);
			}

			_node.removed.remove(removedHandler);
			_node.added.remove(addedHandler);
			_node.transform.changed.remove(transformChangedHandler);
			_node = null;
		}

		private function meshAddedToSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			_data = new TransformDataProvider(mesh.localToWorld, mesh.worldToLocal);
			mesh.bindings.addProvider(_data);
		}

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			mesh.bindings.removeProvider(_data);
			_data = null;
		}

		private function transformChangedHandler(transform : Matrix4x4) : void
		{
			var parent : ISceneNode = _node.parent;

            _node.worldToLocal.lock();
            _node.localToWorld.lock();

			_node.localToWorld.copyFrom(_node.transform);
			if (_node.parent)
				_node.localToWorld.append(parent.localToWorld);

			_node.worldToLocal
				.copyFrom(_node.localToWorld)
				.invert();

            _node.worldToLocal.unlock();
            _node.localToWorld.unlock();
		}

		private function addedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.add(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}

		private function removedHandler(node : ISceneNode, parent : Group) : void
		{
			if (node === _node)
			{
				parent.localToWorld.changed.remove(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}
	}
}

package aerys.minko.scene.controller
{
	import aerys.minko.scene.data.TransformDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.math.Matrix4x4;

	public final class TransformController extends AbstractController
	{
		private var _node	: ISceneNode;
		private var _data	: TransformDataProvider;

		public function TransformController()
		{
			super();

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		private function targetAddedHandler(ctrl	: TransformController,
											node 	: ISceneNode) : void
		{
			if (_node)
				throw new Error();

			_node = node;
			_node.transform.changed.add(transformChangedHandler);
			_node.added.add(addedHandler);
			_node.removed.add(removedHandler);

			if (_node is Mesh)
			{
				_node.addedToScene.add(meshAddedToSceneHandler);
				_node.removedFromScene.add(meshRemovedFromSceneHandler);
			}

			transformChangedHandler(_node.transform);
		}

		private function targetRemovedHandler(ctrl	: TransformController,
											  node 	: ISceneNode) : void
		{
			if (_node is Mesh)
			{
				_node.addedToScene.remove(meshAddedToSceneHandler);
				_node.removedFromScene.remove(meshRemovedFromSceneHandler);
			}

			_node.removed.remove(removedHandler);
			_node.added.remove(addedHandler);
			_node.transform.changed.remove(transformChangedHandler);
			_node = null;
		}

		private function meshAddedToSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			_data = new TransformDataProvider(mesh.localToWorld, mesh.worldToLocal);
			mesh.bindings.addProvider(_data);
		}

		private function meshRemovedFromSceneHandler(mesh : Mesh, scene : Scene) : void
		{
			mesh.bindings.removeProvider(_data);
			_data = null;
		}

		private function transformChangedHandler(transform : Matrix4x4) : void
		{
			var parent : ISceneNode = _node.parent;

            _node.worldToLocal.lock();
            _node.localToWorld.lock();

			_node.localToWorld.copyFrom(_node.transform);
			if (_node.parent)
				_node.localToWorld.append(parent.localToWorld);

			_node.worldToLocal
				.copyFrom(_node.localToWorld)
				.invert();

            _node.worldToLocal.unlock();
            _node.localToWorld.unlock();
		}

		private function addedHandler(node : ISceneNode, ancestor : Group) : void
		{
			if (node === _node && ancestor === _node.parent)
			{
				ancestor.localToWorld.changed.add(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}

		private function removedHandler(node : ISceneNode, ancestor : Group) : void
		{
			if (node === _node && !_node.parent)
			{
                ancestor.localToWorld.changed.remove(transformChangedHandler);
				transformChangedHandler(_node.transform);
			}
		}
	}
}

fix TransformController.removedHandler() and TransformController.addedHandler() to fit to the corresponding signals new propagation rules