/*
# Copyright (c) 2008-2010 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any warranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
#
*/

package com.rpath.xobj
{
import mx.collections.Sort;
import flash.utils.Dictionary;
import mx.collections.SortField;

public class XSmartURL extends URL
{
    public function XSmartURL(s:*=null)
    {
        // unwrap the URL passed in
        if (s is XSmartURL)
        {
            s = (s as XSmartURL).baseURL;
        }

        super(s);

        // keep track of the base URL distinct from
        // our current value
        baseURL = new URL(this.value);
    }

    public function get baseURL():URL
    {
        return _baseURL;
    }

    private var _baseURL:URL;

    public function set baseURL(value:URL):void
    {
        _baseURL = value;
        recomputeQualifiedURL();
    }

    /** descriptor provides the encoding chars to use
     * which is a poor man's metadata right now
     */
    [xobjTransient]
    public var descriptor:XSmartURLDescriptor;

    /** paging support
     */

    [xobjTransient]
    public var rangeRequest:Boolean;
    [xobjTransient]
    public var startIndex:Number;
    [xobjTransient]
    public var limit:Number;
    [xobjTransient]
    public var rangeUsingHeaders:Boolean;

    /** filterTerms is an array of (field, operator, value) clauses
     * that are assumed to be AND clauses
     */
    private var _filterTerms:Array;

    [xobjTransient]
    public function get filterTerms():Array
    {
        return _filterTerms;
    }

    [xobjTransient]
    public function set filterTerms(value:Array):void
    {
        _filterTerms = value;
        recomputeQualifiedURL();
    }

    /** sortTerms are an ordered set of (asc, desc) fields to order by
     */
    private var _sortTerms:Sort;

    [xobjTransient]
    public function get sortTerms():Sort
    {
        return _sortTerms;
    }

    [xobjTransient]
    public function set sortTerms(value:Sort):void
    {
        _sortTerms = value;
        recomputeQualifiedURL();
    }

    /** freeSearch is for google-like full text searching
     */
    private var _freeSearch:String;

    [xobjTransient]
    public function get freeSearch():String
    {
        return _freeSearch;
    }

    [xobjTransient]
    public function set freeSearch(value:String):void
    {
        _freeSearch = value;
        recomputeQualifiedURL();
    }

    /** url takes the baseURL and modifies it to encode the query,
     * page, etc. params requested
     */

    // TODO: optimize this via setter/getter pairs on query terms
    [xobjTransient]
    [Bindable]
    public function get qualifiedURL():String
    {
        return _qualifiedURL;
    }

    private var _qualifiedURL:String;

    [xobjTransient]
    public function set qualifiedURL(s:String):void
    {
        _qualifiedURL = s;
    }

    [xobjTransient]
    public function recomputeQualifiedURL():void
    {
        if (!baseURL)
            return;

        var fullURL:String = baseURL.value;

        var queryFrag:String = queryFragment();

        if (queryFrag)
        {
            if (baseURL.hasQuery())
            {
                fullURL = fullURL + "&" + queryFrag;
            }
            else
            {
                fullURL = fullURL + "?" + queryFrag;
            }
        }

        qualifiedURL = fullURL;
    }

    [xobjTransient]
    public function queryFragment():String
    {
        var params:Dictionary = new Dictionary();
        var query:String = "";

        if (rangeRequest)
        {
            if (descriptor.rangeUsingHeaders)
            {
                // DAMN HTTPService won't pass RANGE header
                setHeader("X-Range", "rows "+startIndex+"-"+(startIndex+ limit -1));
            }
            else
            {
                params[descriptor.startKey] = startIndex;
                params[descriptor.limitKey] = limit;
            }
        }

        // TODO: use descriptor to tell us how to express sort
        if (sortTerms)
        {
            var terms:String = "";

            if (sortTerms.fields && sortTerms.fields.length > 0)
            {
                var first:Boolean = true;

                for each (var term:SortField in sortTerms.fields)
                {
                    if (!term || !term.name)
                        continue;

                    terms = terms
                        + (first ? "": descriptor.sortConjunction)
                        + (term.descending ? descriptor.sortDescMarker: descriptor.sortAscMarker)
                        + term.name;
                    first = false;
                }

                if (terms)
                {
                    params[descriptor.sortKey] = terms;
                }
            }
        }

        if (filterTerms)
        {
            var search:String;
            first = true;
            for each (var searchTerm:FilterTerm in filterTerms)
            {
                search = search
                    + (first ? "": descriptor.filterTermConjunction)
                    + descriptor.filterTermStart
                    + searchTerm.name + descriptor.filterTermSeperator+ searchTerm.operator +descriptor.filterTermSeperator + searchTerm.value
                    + descriptor.filterTermEnd;
                first = false;
            }

            if (search)
            {
                params[descriptor.filterKey] = search;
            }
        }

        // Google like full text searching
        if (freeSearch)
        {
            params[descriptor.searchKey] = freeSearch;
        }

        first = true;
        for (var param:String in params)
        {
            query = query + (first ? "" : "&") + param + "=" + params[param];
            first = false;
        }

        return query;
    }

    [xobjTransient]
    public function addSearchTerm(term:FilterTerm):void
    {
        if (!filterTerms)
            filterTerms = [];

        filterTerms.push(term);

        recomputeQualifiedURL();
    }

    /* TODO: decide if headers really belong on resource spec */
    [xobjTransient]
    public var headers:Dictionary;
    [xobjTransient]
    public var hasHeaders:Boolean;

    [xobjTransient]
    public function setHeader(name:String, value:String):void
    {
        headers[name] = value;
        hasHeaders = true;
    }

}
}

/*
# Copyright (c) 2008-2010 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any warranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
#
*/

package com.rpath.xobj
{
import mx.collections.Sort;
import flash.utils.Dictionary;
import mx.collections.SortField;

public class XSmartURL extends URL
{
    public function XSmartURL(s:*=null)
    {
        // unwrap the URL passed in
        if (s is XSmartURL)
        {
            s = (s as XSmartURL).baseURL;
        }

        super(s);

        // keep track of the base URL distinct from
        // our current value
        baseURL = new URL(this.value);
    }

    public function get baseURL():URL
    {
        return _baseURL;
    }

    private var _baseURL:URL;

    public function set baseURL(value:URL):void
    {
        _baseURL = value;
        recomputeQualifiedURL();
    }

    /** descriptor provides the encoding chars to use
     * which is a poor man's metadata right now
     */
    [xobjTransient]
    private var _descriptor:XSmartURLDescriptor;

    public function get descriptor():XSmartURLDescriptor
    {
        return _descriptor;
    }

    public function set descriptor(value:XSmartURLDescriptor):void
    {
        _descriptor = value;
        recomputeQualifiedURL();
    }

    /** paging support
     */

    [xobjTransient]
    public var rangeRequest:Boolean;
    [xobjTransient]
    public var startIndex:Number;
    [xobjTransient]
    public var limit:Number;
    [xobjTransient]
    public var rangeUsingHeaders:Boolean;

    /** filterTerms is an array of (field, operator, value) clauses
     * that are assumed to be AND clauses
     */
    private var _filterTerms:Array;

    [xobjTransient]
    public function get filterTerms():Array
    {
        return _filterTerms;
    }

    [xobjTransient]
    public function set filterTerms(value:Array):void
    {
        _filterTerms = value;
        recomputeQualifiedURL();
    }

    /** sortTerms are an ordered set of (asc, desc) fields to order by
     */
    private var _sortTerms:Sort;

    [xobjTransient]
    public function get sortTerms():Sort
    {
        return _sortTerms;
    }

    [xobjTransient]
    public function set sortTerms(value:Sort):void
    {
        _sortTerms = value;
        recomputeQualifiedURL();
    }

    /** freeSearch is for google-like full text searching
     */
    private var _freeSearch:String;

    [xobjTransient]
    public function get freeSearch():String
    {
        return _freeSearch;
    }

    [xobjTransient]
    public function set freeSearch(value:String):void
    {
        _freeSearch = value;
        recomputeQualifiedURL();
    }

    /** url takes the baseURL and modifies it to encode the query,
     * page, etc. params requested
     */

    // TODO: optimize this via setter/getter pairs on query terms
    [xobjTransient]
    [Bindable]
    public function get qualifiedURL():String
    {
        return _qualifiedURL;
    }

    private var _qualifiedURL:String;

    [xobjTransient]
    public function set qualifiedURL(s:String):void
    {
        _qualifiedURL = s;
    }

    [xobjTransient]
    public function recomputeQualifiedURL():void
    {
        if (!baseURL)
            return;

        var fullURL:String = baseURL.value;

        var queryFrag:String = queryFragment();

        if (queryFrag)
        {
            if (baseURL.hasQuery())
            {
                fullURL = fullURL + "&" + queryFrag;
            }
            else
            {
                fullURL = fullURL + "?" + queryFrag;
            }
        }

        qualifiedURL = fullURL;
    }

    [xobjTransient]
    public function queryFragment():String
    {
        var params:Dictionary = new Dictionary();
        var query:String = "";

        if (!descriptor)
            return query;

        if (rangeRequest)
        {
            if (descriptor.rangeUsingHeaders)
            {
                // DAMN HTTPService won't pass RANGE header
                setHeader("X-Range", "rows "+startIndex+"-"+(startIndex+ limit -1));
            }
            else
            {
                params[descriptor.startKey] = startIndex;
                params[descriptor.limitKey] = limit;
            }
        }

        // TODO: use descriptor to tell us how to express sort
        if (sortTerms)
        {
            var terms:String = "";

            if (sortTerms.fields && sortTerms.fields.length > 0)
            {
                var first:Boolean = true;

                for each (var term:SortField in sortTerms.fields)
                {
                    if (!term || !term.name)
                        continue;

                    terms = terms
                        + (first ? "": descriptor.sortConjunction)
                        + (term.descending ? descriptor.sortDescMarker: descriptor.sortAscMarker)
                        + term.name;
                    first = false;
                }

                if (terms)
                {
                    params[descriptor.sortKey] = terms;
                }
            }
        }

        if (filterTerms)
        {
            var search:String;
            first = true;
            for each (var searchTerm:FilterTerm in filterTerms)
            {
                search = search
                    + (first ? "": descriptor.filterTermConjunction)
                    + descriptor.filterTermStart
                    + searchTerm.name + descriptor.filterTermSeperator+ searchTerm.operator +descriptor.filterTermSeperator + searchTerm.value
                    + descriptor.filterTermEnd;
                first = false;
            }

            if (search)
            {
                params[descriptor.filterKey] = search;
            }
        }

        // Google like full text searching
        if (freeSearch)
        {
            params[descriptor.searchKey] = freeSearch;
        }

        first = true;
        for (var param:String in params)
        {
            query = query + (first ? "" : "&") + param + "=" + params[param];
            first = false;
        }

        return query;
    }

    [xobjTransient]
    public function addSearchTerm(term:FilterTerm):void
    {
        if (!filterTerms)
            filterTerms = [];

        filterTerms.push(term);

        recomputeQualifiedURL();
    }

    /* TODO: decide if headers really belong on resource spec */
    [xobjTransient]
    public var headers:Dictionary;
    [xobjTransient]
    public var hasHeaders:Boolean;

    [xobjTransient]
    public function setHeader(name:String, value:String):void
    {
        headers[name] = value;
        hasHeaders = true;
    }

}
}

Handle disparate initialization patterns in XSmartURL


package aerys.minko.render
{
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.resource.texture.TextureResource;

	/**
	 * RenderTarget objects can be used to render in the backbuffer or
	 * in textures.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 * @see aerys.minko.render.resource.texture.ITextureResource
	 *
	 */
	public final class RenderTarget
	{
		private var _width				: uint				= 0;
		private var _height				: uint				= 0;
		private var _resource			: ITextureResource	= null;
		private var _surfaceSelector	: uint				= 0;
		private var _useDepthAndStencil	: Boolean			= false;
		private var _antiAliasing		: int				= 0;
		private var _backgroundColor	: uint				= 0;

		public function get width() : uint
		{
			return _width;
		}
		public function set width(value : uint) : void
		{
			_width = value;

			if (_resource)
				_resource.setSize(value, _height);
		}

		public function get height() : uint
		{
			return _height;
		}
		public function set height(value : uint) : void
		{
			_height = value;

			if (_resource)
				_resource.setSize(_width, value);
		}

		public function get textureResource() : ITextureResource
		{
			return _resource;
		}

		public function get surfaceSelector() : uint
		{
			return _surfaceSelector;
		}

		public function get useDepthAndStencil() : Boolean
		{
			return _useDepthAndStencil;
		}

		public function get antiAliasing() : int
		{
			return _antiAliasing;
		}

		public function get backgroundColor() : uint
		{
			return _backgroundColor;
		}
		public function set backgroundColor(value : uint) : void
		{
			_backgroundColor = value;
		}

		public function RenderTarget(width				: uint,
									 height				: uint,
									 resource			: ITextureResource	= null,
									 surfaceSelector	: uint				= 0,
									 backgroundColor	: uint				= 0,
									 useDepthAndStencil	: Boolean			= true,
									 antiAliasing		: int				= 0)
		{
			_width = width;
			_height = height;
			_resource = resource;
			_surfaceSelector = surfaceSelector;
			_backgroundColor = backgroundColor;
			_useDepthAndStencil = useDepthAndStencil;
			_antiAliasing = antiAliasing;

			if (_resource && (_resource.width != _width || _resource.height != _height))
				throw new Error(
					'The texture resource and the render target must have the same size.'
				);
		}

		public function resize(width : uint, height	: uint) : void
		{
			_width = width;
			_height = height;

			if (_resource)
				_resource.setSize(width, height);
		}
	}
}

package aerys.minko.render
{
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.resource.texture.TextureResource;

	/**
	 * RenderTarget objects can be used to render in the backbuffer or
	 * in textures.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 * @see aerys.minko.render.resource.texture.ITextureResource
	 *
	 */
	public final class RenderTarget
	{
		private var _width				: uint				= 0;
		private var _height				: uint				= 0;
		private var _resource			: ITextureResource	= null;
		private var _surfaceSelector	: uint				= 0;
		private var _useDepthAndStencil	: Boolean			= false;
		private var _antiAliasing		: int				= 0;
		private var _backgroundColor	: uint				= 0;

		public function get width() : uint
		{
			return _width;
		}
		public function set width(value : uint) : void
		{
			_width = value;
			_resource.setSize(value, _height);
		}

		public function get height() : uint
		{
			return _height;
		}
		public function set height(value : uint) : void
		{
			_height = value;
			_resource.setSize(_width, value);
		}

		public function get textureResource() : ITextureResource
		{
			return _resource;
		}

		public function get surfaceSelector() : uint
		{
			return _surfaceSelector;
		}

		public function get useDepthAndStencil() : Boolean
		{
			return _useDepthAndStencil;
		}

		public function get antiAliasing() : int
		{
			return _antiAliasing;
		}

		public function get backgroundColor() : uint
		{
			return _backgroundColor;
		}
		public function set backgroundColor(value : uint) : void
		{
			_backgroundColor = value;
		}

		public function RenderTarget(width				: uint,
									 height				: uint,
									 resource			: ITextureResource	= null,
									 surfaceSelector	: uint				= 0,
									 backgroundColor	: uint				= 0,
									 useDepthAndStencil	: Boolean			= true,
									 antiAliasing		: int				= 0)
		{
			_width = width;
			_height = height;
			_resource = resource;
			_surfaceSelector = surfaceSelector;
			_backgroundColor = backgroundColor;
			_useDepthAndStencil = useDepthAndStencil;
			_antiAliasing = antiAliasing;

			if (_resource && (_resource.width != _width || _resource.height != _height))
				throw new Error(
					'The texture resource and the render target must have the same size.'
				);
		}

		public function resize(width : uint, height	: uint) : void
		{
			_width = width;
			_height = height;

			if (_resource)
				_resource.setSize(width, height);
		}
	}
}

remove useless check for _resource != null in RenderTarget.width and RenderTarget.height setters


package com.mangui.HLS.streaming {

    import com.mangui.HLS.*;
    import com.mangui.HLS.muxing.*;
    import com.mangui.HLS.streaming.*;
    import com.mangui.HLS.parsing.*;
    import com.mangui.HLS.utils.*;

    import flash.media.*;
    import flash.net.*;
    import flash.utils.*;

    /** Class that keeps the buffer filled. **/
    public class Buffer {
        /** Reference to the framework controller. **/
        private var _hls:HLS;
        /** The buffer with video tags. **/
        private var _buffer:Vector.<Tag>;
        /** NetConnection legacy stuff. **/
        private var _connection:NetConnection;
        /** The fragment loader. **/
        private var _loader:FragmentLoader;
        /** Store that a fragment load is in progress. **/
        private var _loading:Boolean;
        /** means that last fragment of a VOD playlist has been loaded */
        private var _reached_vod_end:Boolean;
        /** Interval for checking buffer and position. **/
        private var _interval:Number;
        /** Next loading fragment sequence number. **/
        /** The start position of the stream. **/
        public var PlaybackStartPosition:Number = 0;
         /** start play time **/
        private var _playback_start_time:Number;
        /** playback start PTS. **/
        private var _playback_start_pts:Number;
        /** playlist start PTS when playback started. **/
        private var _playlist_start_pts:Number;
        /** Current play position (relative position from beginning of sliding window) **/
        private var _playback_current_position:Number;
        /** buffer last PTS. **/
        private var _buffer_last_pts:Number;
         /** next buffer time. **/
        private var _buffer_next_time:Number;
         /** previous buffer time. **/
        private var _last_buffer:Number;
        /** Current playback state. **/
        private var _state:String;
        /** Netstream instance used for playing the stream. **/
        private var _stream:NetStream;
        /** The last tag that was appended to the buffer. **/
        private var _buffer_current_index:Number;
        /** soundtransform object. **/
        private var _transform:SoundTransform;
        /** Reference to the video object. **/
        private var _video:Object;

        /** Create the buffer. **/
        public function Buffer(hls:HLS, loader:FragmentLoader, video:Object):void {
            _hls = hls;
            _loader = loader;
            _video = video;
            _hls.addEventListener(HLSEvent.MANIFEST,_manifestHandler);
            _connection = new NetConnection();
            _connection.connect(null);
            _transform = new SoundTransform();
            _transform.volume = 0.9;
            _setState(HLSStates.IDLE);
        };

        /** Check the bufferlength. **/
        private function _checkBuffer():void {
            var buffer:Number = 0;
            // Calculate the buffer and position.
            if(_buffer.length) {
               buffer = (_buffer_last_pts - _playback_start_pts)/1000 - _stream.time;
               /** Current play time (time since beginning of playback) **/
               var playback_current_time:Number = (Math.round(_stream.time*100 + _playback_start_time*100)/100);
               var current_playlist_start_pts:Number = _loader.getPlayListStartPTS();
               var play_position:Number;
               if(current_playlist_start_pts ==Number.NEGATIVE_INFINITY) {
                  play_position = 0;
               } else {
                  play_position = playback_current_time -(current_playlist_start_pts-_playlist_start_pts)/1000;
                }
               if(play_position != _playback_current_position || buffer !=_last_buffer) {
                  if (play_position <0) {
                     play_position = 0;
                  }
                  _playback_current_position = play_position;
                  _last_buffer = buffer;
                  _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME,{ position:_playback_current_position, buffer:buffer, duration:_loader.getPlayListDuration()}));
               }
            }

            // Load new tags from fragment.
            if(_reached_vod_end == false && buffer < _loader.getBufferLength() && ((!_loading) || _loader.getIOError() == true)) {
               var loadstatus:Number = _loader.loadfragment(_buffer_next_time,_buffer_last_pts,buffer,_loaderCallback,(_buffer.length == 0));
               if (loadstatus == 0) {
                  // good, new fragment being loaded
                  _loading = true;
               } else  if (loadstatus < 0) {
                  /* it means PTS requested is smaller than playlist start PTS.
                     it could happen on live playlist :
                     - if bandwidth available is lower than lowest quality needed bandwidth
                     - after long pause
                     seek to offset 0 to force a restart of the playback session  */
                  Log.txt("long pause on live stream or bad network quality");
                  seek(0);
                  return;
               } else if(loadstatus > 0) {
                  //seqnum not available in playlist
                  if (_hls.getType() == HLSTypes.VOD) {
                     // if VOD playlist, it means we reached the end, on live playlist do nothing and wait ...
                     _reached_vod_end = true;
                  }
               }
            }
            if((_state == HLSStates.PLAYING) ||
               (_state == HLSStates.BUFFERING && buffer > _loader.getSegmentAverageDuration()))
             {
                //Log.txt("appending data into NetStream");
                while(_buffer_current_index < _buffer.length && _stream.bufferLength < 2*_loader.getSegmentAverageDuration()) {
                    try {
                        _stream.appendBytes(_buffer[_buffer_current_index].data);
                    } catch (error:Error) {
                        _errorHandler(new Error(_buffer[_buffer_current_index].type+": "+ error.message));
                    }
                    // Last tag done? Then append sequence end.
                    if (_reached_vod_end ==true && _buffer_current_index == _buffer.length - 1) {
                        _stream.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
                        _stream.appendBytes(new ByteArray());
                    }
                    _buffer_current_index++;
                }
            }
            // Set playback state and complete.
            if(_stream.bufferLength < 3) {
                if(_stream.bufferLength == 0 && _reached_vod_end ==true) {
                    clearInterval(_interval);
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.COMPLETE));
                    _setState(HLSStates.IDLE);
                } else if(_state == HLSStates.PLAYING) {
                    _setState(HLSStates.BUFFERING);
                }
            } else if (_state == HLSStates.BUFFERING) {
                _setState(HLSStates.PLAYING);
            }
        };

        /** Dispatch an error to the controller. **/
        private function _errorHandler(error:Error):void {
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR,error.toString()));
        };

        /** Return the current playback state. **/
        public function getPosition():Number {
            return _playback_current_position;
        };

        /** Return the current playback state. **/
        public function getState():String {
            return _state;
        };

        /** Add a fragment to the buffer. **/
        private function _loaderCallback(tags:Vector.<Tag>,min_pts:Number,max_pts:Number):void {
            _buffer = _buffer.slice(_buffer_current_index);
            _buffer_current_index = 0;
            if (_playback_start_pts == Number.NEGATIVE_INFINITY) {
               _playback_start_pts = min_pts;
               _playlist_start_pts = _loader.getPlayListStartPTS();
               _playback_start_time = (_playback_start_pts-_playlist_start_pts)/1000;
            }
            _buffer_last_pts = max_pts;
            tags.sort(_sortTagsbyDTS);
            for each (var t:Tag in tags) {
               _buffer.push(t);
            }
            _buffer_next_time=_playback_start_time+(_buffer_last_pts-_playback_start_pts)/1000;
            Log.txt("_loaderCallback,_buffer_next_time:"+ _buffer_next_time);
            _loading = false;
        };

        /** Start streaming on manifest load. **/
        private function _manifestHandler(event:HLSEvent):void {
            if(_state == HLSStates.IDLE) {
                _stream = new NetStream(_connection);
                _video.attachNetStream(_stream);
                _stream.play(null);
                _stream.soundTransform = _transform;
                _stream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
                _stream.appendBytes(FLV.getHeader());
                seek(PlaybackStartPosition);
            }
        };

        /** Toggle playback. **/
        public function pause():void {
            clearInterval(_interval);
            if(_state == HLSStates.PAUSED) {
                _setState(HLSStates.BUFFERING);
                _stream.resume();
                _interval = setInterval(_checkBuffer,100);
            } else if(_state == HLSStates.PLAYING) {
                _setState(HLSStates.PAUSED);
                _stream.pause();
            }
        };

        /** Change playback state. **/
        private function _setState(state:String):void {
            if(state != _state) {
                _state = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.STATE,_state));
            }
        };

        /** Sort the buffer by tag. **/
        private function _sortTagsbyDTS(x:Tag,y:Tag):Number {
            if(x.dts < y.dts) {
                return -1;
            } else if (x.dts > y.dts) {
                return 1;
            } else {
                if(x.type == Tag.AVC_HEADER || x.type == Tag.AAC_HEADER) {
                    return -1;
                } else if (y.type == Tag.AVC_HEADER || y.type == Tag.AAC_HEADER) {
                    return 1;
                } else {
                    if(x.type == Tag.AVC_NALU) {
                        return -1;
                    } else if (y.type == Tag.AVC_NALU) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            }
        };

        /** Start playing data in the buffer. **/
        public function seek(position:Number):void {
               _buffer = new Vector.<Tag>();
               _loader.clearLoader();
               _loading = false;
               _buffer_current_index = 0;
                PlaybackStartPosition = position;
               _stream.seek(0);
               _stream.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
               _reached_vod_end = false;
               _playback_start_pts = Number.NEGATIVE_INFINITY;
               _buffer_next_time = position;
               _buffer_last_pts = 0;
               _last_buffer = 0;
               _setState(HLSStates.BUFFERING);
               clearInterval(_interval);
               _interval = setInterval(_checkBuffer,100);
        };

        /** Stop playback. **/
        public function stop():void {
            if(_stream) {
                _stream.pause();
            }
            _loading = false;
            clearInterval(_interval);
            _setState(HLSStates.IDLE);
        };

        /** Change the volume (set in the NetStream). **/
        public function volume(percent:Number):void {
            _transform.volume = percent/100;
            if(_stream) {
                _stream.soundTransform = _transform;
            }
        };

    }

}

package com.mangui.HLS.streaming {

    import com.mangui.HLS.*;
    import com.mangui.HLS.muxing.*;
    import com.mangui.HLS.streaming.*;
    import com.mangui.HLS.parsing.*;
    import com.mangui.HLS.utils.*;

    import flash.media.*;
    import flash.net.*;
    import flash.utils.*;

    /** Class that keeps the buffer filled. **/
    public class Buffer {
        /** Reference to the framework controller. **/
        private var _hls:HLS;
        /** The buffer with video tags. **/
        private var _buffer:Vector.<Tag>;
        /** NetConnection legacy stuff. **/
        private var _connection:NetConnection;
        /** The fragment loader. **/
        private var _loader:FragmentLoader;
        /** Store that a fragment load is in progress. **/
        private var _loading:Boolean;
        /** means that last fragment of a VOD playlist has been loaded */
        private var _reached_vod_end:Boolean;
        /** Interval for checking buffer and position. **/
        private var _interval:Number;
        /** Next loading fragment sequence number. **/
        /** The start position of the stream. **/
        public var PlaybackStartPosition:Number = 0;
         /** start playback position in second, retrieved from first fragment **/
        private var _playback_start_position:Number;
        /** playback start PTS. **/
        private var _playback_start_pts:Number;
        /** playlist start PTS when playback started. **/
        private var _playlist_start_pts:Number;
        /** Current play position (relative position from beginning of sliding window) **/
        private var _playback_current_position:Number;
        /** buffer last PTS. **/
        private var _buffer_last_pts:Number;
         /** next buffer time. **/
        private var _buffer_next_time:Number;
         /** previous buffer time. **/
        private var _last_buffer:Number;
        /** Current playback state. **/
        private var _state:String;
        /** Netstream instance used for playing the stream. **/
        private var _stream:NetStream;
        /** The last tag that was appended to the buffer. **/
        private var _buffer_current_index:Number;
        /** soundtransform object. **/
        private var _transform:SoundTransform;
        /** Reference to the video object. **/
        private var _video:Object;

        /** Create the buffer. **/
        public function Buffer(hls:HLS, loader:FragmentLoader, video:Object):void {
            _hls = hls;
            _loader = loader;
            _video = video;
            _hls.addEventListener(HLSEvent.MANIFEST,_manifestHandler);
            _connection = new NetConnection();
            _connection.connect(null);
            _transform = new SoundTransform();
            _transform.volume = 0.9;
            _setState(HLSStates.IDLE);
        };

        /** Check the bufferlength. **/
        private function _checkBuffer():void {
            var buffer:Number = 0;
            // Calculate the buffer and position.
            if(_buffer.length) {
               buffer = (_buffer_last_pts - _playback_start_pts)/1000 - _stream.time;
               /** Current play time (time since beginning of playback) **/
               var playback_current_time:Number = (Math.round(_stream.time*100 + _playback_start_position*100)/100);
               var current_playlist_start_pts:Number = _loader.getPlayListStartPTS();
               var play_position:Number;
               if(current_playlist_start_pts ==Number.NEGATIVE_INFINITY) {
                  play_position = 0;
               } else {
                  play_position = playback_current_time -(current_playlist_start_pts-_playlist_start_pts)/1000;
                }
               if(play_position != _playback_current_position || buffer !=_last_buffer) {
                  if (play_position <0) {
                     play_position = 0;
                  }
                  _playback_current_position = play_position;
                  _last_buffer = buffer;
                  _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME,{ position:_playback_current_position, buffer:buffer, duration:_loader.getPlayListDuration()}));
               }
            }

            // Load new tags from fragment.
            if(_reached_vod_end == false && buffer < _loader.getBufferLength() && ((!_loading) || _loader.getIOError() == true)) {
               var loadstatus:Number = _loader.loadfragment(_buffer_next_time,_buffer_last_pts,buffer,_loaderCallback,(_buffer.length == 0));
               if (loadstatus == 0) {
                  // good, new fragment being loaded
                  _loading = true;
               } else  if (loadstatus < 0) {
                  /* it means PTS requested is smaller than playlist start PTS.
                     it could happen on live playlist :
                     - if bandwidth available is lower than lowest quality needed bandwidth
                     - after long pause
                     seek to offset 0 to force a restart of the playback session  */
                  Log.txt("long pause on live stream or bad network quality");
                  seek(0);
                  return;
               } else if(loadstatus > 0) {
                  //seqnum not available in playlist
                  if (_hls.getType() == HLSTypes.VOD) {
                     // if VOD playlist, it means we reached the end, on live playlist do nothing and wait ...
                     _reached_vod_end = true;
                  }
               }
            }
            if((_state == HLSStates.PLAYING) ||
               (_state == HLSStates.BUFFERING && buffer > _loader.getSegmentAverageDuration()))
             {
                //Log.txt("appending data into NetStream");
                while(_buffer_current_index < _buffer.length && _stream.bufferLength < 2*_loader.getSegmentAverageDuration()) {
                    try {
                        _stream.appendBytes(_buffer[_buffer_current_index].data);
                    } catch (error:Error) {
                        _errorHandler(new Error(_buffer[_buffer_current_index].type+": "+ error.message));
                    }
                    // Last tag done? Then append sequence end.
                    if (_reached_vod_end ==true && _buffer_current_index == _buffer.length - 1) {
                        _stream.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
                        _stream.appendBytes(new ByteArray());
                    }
                    _buffer_current_index++;
                }
            }
            // Set playback state and complete.
            if(_stream.bufferLength < 3) {
                if(_stream.bufferLength == 0 && _reached_vod_end ==true) {
                    clearInterval(_interval);
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.COMPLETE));
                    _setState(HLSStates.IDLE);
                } else if(_state == HLSStates.PLAYING) {
                    _setState(HLSStates.BUFFERING);
                }
            } else if (_state == HLSStates.BUFFERING) {
                _setState(HLSStates.PLAYING);
            }
        };

        /** Dispatch an error to the controller. **/
        private function _errorHandler(error:Error):void {
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR,error.toString()));
        };

        /** Return the current playback state. **/
        public function getPosition():Number {
            return _playback_current_position;
        };

        /** Return the current playback state. **/
        public function getState():String {
            return _state;
        };

        /** Add a fragment to the buffer. **/
        private function _loaderCallback(tags:Vector.<Tag>,min_pts:Number,max_pts:Number):void {
            // flush already injected Tags and restart index from 0
            _buffer = _buffer.slice(_buffer_current_index);
            _buffer_current_index = 0;
            if (_playback_start_pts == Number.NEGATIVE_INFINITY) {
               _playback_start_pts = min_pts;
               _playlist_start_pts = _loader.getPlayListStartPTS();
               _playback_start_position = (_playback_start_pts-_playlist_start_pts)/1000;
            }
            _buffer_last_pts = max_pts;
            tags.sort(_sortTagsbyDTS);
            for each (var t:Tag in tags) {
               _buffer.push(t);
            }
            _buffer_next_time=_playback_start_position+(_buffer_last_pts-_playback_start_pts)/1000;
            Log.txt("_loaderCallback,_buffer_next_time:"+ _buffer_next_time);
            _loading = false;
        };

        /** Start streaming on manifest load. **/
        private function _manifestHandler(event:HLSEvent):void {
            if(_state == HLSStates.IDLE) {
                _stream = new NetStream(_connection);
                _video.attachNetStream(_stream);
                _stream.play(null);
                _stream.soundTransform = _transform;
                _stream.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
                _stream.appendBytes(FLV.getHeader());
                seek(PlaybackStartPosition);
            }
        };

        /** Toggle playback. **/
        public function pause():void {
            clearInterval(_interval);
            if(_state == HLSStates.PAUSED) {
                _setState(HLSStates.BUFFERING);
                _stream.resume();
                _interval = setInterval(_checkBuffer,100);
            } else if(_state == HLSStates.PLAYING) {
                _setState(HLSStates.PAUSED);
                _stream.pause();
            }
        };

        /** Change playback state. **/
        private function _setState(state:String):void {
            if(state != _state) {
                _state = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.STATE,_state));
            }
        };

        /** Sort the buffer by tag. **/
        private function _sortTagsbyDTS(x:Tag,y:Tag):Number {
            if(x.dts < y.dts) {
                return -1;
            } else if (x.dts > y.dts) {
                return 1;
            } else {
                if(x.type == Tag.AVC_HEADER || x.type == Tag.AAC_HEADER) {
                    return -1;
                } else if (y.type == Tag.AVC_HEADER || y.type == Tag.AAC_HEADER) {
                    return 1;
                } else {
                    if(x.type == Tag.AVC_NALU) {
                        return -1;
                    } else if (y.type == Tag.AVC_NALU) {
                        return 1;
                    } else {
                        return 0;
                    }
                }
            }
        };

        /** Start playing data in the buffer. **/
        public function seek(position:Number):void {
               _buffer = new Vector.<Tag>();
               _loader.clearLoader();
               _loading = false;
               _buffer_current_index = 0;
                PlaybackStartPosition = position;
               _stream.seek(0);
               _stream.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
               _reached_vod_end = false;
               _playback_start_pts = Number.NEGATIVE_INFINITY;
               _buffer_next_time = position;
               _buffer_last_pts = 0;
               _last_buffer = 0;
               _setState(HLSStates.BUFFERING);
               clearInterval(_interval);
               _interval = setInterval(_checkBuffer,100);
        };

        /** Stop playback. **/
        public function stop():void {
            if(_stream) {
                _stream.pause();
            }
            _loading = false;
            clearInterval(_interval);
            _setState(HLSStates.IDLE);
        };

        /** Change the volume (set in the NetStream). **/
        public function volume(percent:Number):void {
            _transform.volume = percent/100;
            if(_stream) {
                _stream.soundTransform = _transform;
            }
        };

    }

}

rename variable for sake of clarity

rename variable for sake of clarity


package
{
	import com.*;
	import com.errors.RuntimeError;
	import com.events.*;

	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.external.ExternalInterface;
	import flash.system.ApplicationDomain;
	import flash.system.Security;
	import flash.utils.*;

	import mxi.Extend;
	import mxi.Utils;
	import mxi.events.ODataEvent;
	import mxi.events.OErrorEvent;
	import mxi.events.OProgressEvent;

	[SWF(width='500', height='500')]
	public class Moxie extends Sprite
	{
		public static var uid:String;

		private var eventDispatcher:String = "moxie.core.EventTarget.instance.dispatchEvent";

		public static var compFactory:ComponentFactory;

		public static var stageOccupied:Boolean = false; // whether a display facility is already occupied

		/**
		 * Main constructor for the Plupload class.
		 */
		public function Moxie()
		{
			if (stage) {
				_init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}

		/**
		 * Initialization event handler.
		 *
		 * @param e Event object.
		 */
		private function _init(e:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, _init);

			// Allow scripting on swf loaded from another domain
			if (MXI::EnableCSS) {
				Security.allowDomain("*");
			}

			// Align and scale stage
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.EXACT_FIT;

			// use only FlashVars, ignore QueryString
			var params:Object, url:String, urlParts:Object, pos:int, query:Object;

			params = root.loaderInfo.parameters;

			pos = root.loaderInfo.url.indexOf('?');
			if (pos !== -1) {
				return; // we do not allow anything from query sring, so if there's anything, we - quit
			}

			// Setup id
			if (!params.hasOwnProperty("uid")) {
				return; // we do not have uid, so we cannot fire error event - lets simply wait until it timeouts
			}

			Moxie.uid = Utils.sanitize(params["uid"]);

			// Event dispatcher
			if (params.hasOwnProperty("target") && /^[\w\.]+$/.test(params["target"])) {
				eventDispatcher = params["target"];
			}

			//ExternalInterface.marshallExceptions = true; // propagate AS exceptions to JS and vice-versa
			ExternalInterface.addCallback('exec', exec);
			ExternalInterface.addCallback('isOccupied', isOccupied);

			// initialize component factory
			Moxie.compFactory = new ComponentFactory;

			_fireEvent(Moxie.uid + "::Init");
		}

		public function exec(uid:String, compName:String, action:String, args:* = null) : *
		{
			// Moxie.log([uid, compName, action, args]);

			uid = Utils.sanitize(uid); // make it safe

			var comp:* = Moxie.compFactory.get(uid);

			// Moxie.log([compName, action]);

			try {
				// initialize corresponding com
				if (!comp) {
					comp = Moxie.compFactory.create(this, uid, compName);
				}

				// execute the action if available
				if (comp.hasOwnProperty(action)) {
					return comp[action].apply(comp, args as Array);
				}
			} catch(err:*) { // re-route exceptions thrown by components (TODO: check marshallExceptions feature)
				_fireEvent(Moxie.uid + "::Exception", {
					name: err.name,
					code: err.errorID,
					message: compName + "::" + action
				});
			}
		}

		public function isOccupied() : Boolean {
			return Moxie.stageOccupied;
		}

		/**
		 * Intercept component events and do some operations if required
		 *
		 * @param uid String unique identifier of the component throwing the event
		 * @param e mixed Event object
		 * @param exType String event type in mOxie format
		 */
		public function onComponentEvent(uid:String, e:*, exType:String) : void
		{
			var evt:Object = {};
			var data:* = e.hasOwnProperty('data') ? e.data : null;

			switch (e.type)
			{
				case ProgressEvent.PROGRESS:
				case OProgressEvent.PROGRESS:
					evt.loaded = e.bytesLoaded;
					evt.total = e.bytesTotal;
					break;

				case OErrorEvent.ERROR:
					data = e.code;
					break;
			}

			evt.type = [uid, exType].join('::');

			_fireEvent(evt, data);
		}

		/**
		 * Fires an event from the flash movie out to the page level JS.
		 *
		 * @param uid String unique identifier of the component throwing the event
		 * @param type Name of event to fire.
		 * @param obj Object with optional data.
		 */
		private function _fireEvent(evt:*, obj:* = null):void {
			try {
				ExternalInterface.call(eventDispatcher, evt, obj);
			} catch(err:*) {
				//_fireEvent("Exception", { name: 'RuntimeError', message: 4 });

				// throwing an exception would be better here
			}
		}

		public static function log(obj:*) : void {
			ExternalInterface.call('console.info', obj);
		}

	}
}

package
{
	import com.*;
	import com.errors.RuntimeError;
	import com.events.*;

	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.external.ExternalInterface;
	import flash.system.ApplicationDomain;
	import flash.system.Security;
	import flash.utils.*;

	import mxi.Extend;
	import mxi.Utils;
	import mxi.events.ODataEvent;
	import mxi.events.OErrorEvent;
	import mxi.events.OProgressEvent;

	[SWF(width='500', height='500')]
	public class Moxie extends Sprite
	{
		public static var uid:String;

		private var eventDispatcher:String = "moxie.core.EventTarget.instance.dispatchEvent";

		public static var compFactory:ComponentFactory;

		public static var stageOccupied:Boolean = false; // whether a display facility is already occupied

		/**
		 * Main constructor for the Plupload class.
		 */
		public function Moxie()
		{
			if (stage) {
				_init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}

		/**
		 * Initialization event handler.
		 *
		 * @param e Event object.
		 */
		private function _init(e:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, _init);

			// Allow scripting on swf loaded from another domain
			if (MXI::EnableCSS) {
				Security.allowDomain("*");
			}

			// Align and scale stage
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.EXACT_FIT;

			// use only FlashVars, ignore QueryString
			var params:Object = root.loaderInfo.parameters;
			var url:String = root.loaderInfo.url;
			var pos:int = url.indexOf('?');

			if (pos !== -1 && !/^\?[\d\.ab]+$/.test(url.substr(pos))) {
				return; // we do not allow anything from query sring, except the version (for caching purposes)
			}

			// Setup id
			if (!params.hasOwnProperty("uid")) {
				return; // we do not have uid, so we cannot fire error event - lets simply wait until it timeouts
			}

			Moxie.uid = Utils.sanitize(params["uid"]);

			// Event dispatcher
			if (params.hasOwnProperty("target") && /^[\w\.]+$/.test(params["target"])) {
				eventDispatcher = params["target"];
			}

			//ExternalInterface.marshallExceptions = true; // propagate AS exceptions to JS and vice-versa
			ExternalInterface.addCallback('exec', exec);
			ExternalInterface.addCallback('isOccupied', isOccupied);

			// initialize component factory
			Moxie.compFactory = new ComponentFactory;

			_fireEvent(Moxie.uid + "::Init");
		}

		public function exec(uid:String, compName:String, action:String, args:* = null) : *
		{
			// Moxie.log([uid, compName, action, args]);

			uid = Utils.sanitize(uid); // make it safe

			var comp:* = Moxie.compFactory.get(uid);

			// Moxie.log([compName, action]);

			try {
				// initialize corresponding com
				if (!comp) {
					comp = Moxie.compFactory.create(this, uid, compName);
				}

				// execute the action if available
				if (comp.hasOwnProperty(action)) {
					return comp[action].apply(comp, args as Array);
				}
			} catch(err:*) { // re-route exceptions thrown by components (TODO: check marshallExceptions feature)
				_fireEvent(Moxie.uid + "::Exception", {
					name: err.name,
					code: err.errorID,
					message: compName + "::" + action
				});
			}
		}

		public function isOccupied() : Boolean {
			return Moxie.stageOccupied;
		}

		/**
		 * Intercept component events and do some operations if required
		 *
		 * @param uid String unique identifier of the component throwing the event
		 * @param e mixed Event object
		 * @param exType String event type in mOxie format
		 */
		public function onComponentEvent(uid:String, e:*, exType:String) : void
		{
			var evt:Object = {};
			var data:* = e.hasOwnProperty('data') ? e.data : null;

			switch (e.type)
			{
				case ProgressEvent.PROGRESS:
				case OProgressEvent.PROGRESS:
					evt.loaded = e.bytesLoaded;
					evt.total = e.bytesTotal;
					break;

				case OErrorEvent.ERROR:
					data = e.code;
					break;
			}

			evt.type = [uid, exType].join('::');

			_fireEvent(evt, data);
		}

		/**
		 * Fires an event from the flash movie out to the page level JS.
		 *
		 * @param uid String unique identifier of the component throwing the event
		 * @param type Name of event to fire.
		 * @param obj Object with optional data.
		 */
		private function _fireEvent(evt:*, obj:* = null):void {
			try {
				ExternalInterface.call(eventDispatcher, evt, obj);
			} catch(err:*) {
				//_fireEvent("Exception", { name: 'RuntimeError', message: 4 });

				// throwing an exception would be better here
			}
		}

		public static function log(obj:*) : void {
			ExternalInterface.call('console.info', obj);
		}

	}
}

Flash: Allow (only) version or timestamp in swf url.


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.geom.Transformation;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;
	import com.merlinds.unitls.Resolutions;

	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;

		private var _scale:Number;
		private var _elements:Object;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				_scale = Resolutions.width(this.projectModel.targetResolution) /
						Resolutions.width(this.projectModel.referenceResolution);
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			_elements = {};
			var meshes:Array = [];
			var animations:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var mesh:Array = [];
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
					//save mesh bounds to buffer for animation bounds calculation
					_elements[element.name] = element.vertexes;
				}
				meshes.push({name:source.clearName, mesh:mesh});
				//get animation
				m = source.animations.length;
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j], source.clearName);
					animations.push(animation);
				}
			}
			trace("mesh");
			var json:String = JSON.stringify(meshes);
			trace(json);
			_mesh = new ByteArray();
			_mesh.writeObject(meshes);
			_png = PNGEncoder.encode(buffer);
			_animations = new ByteArray();
			_animations.writeObject(animations);
		}

		private function createAnimationOutput(animationVO:AnimationVO, meshPrefix:String):Object {
			var data:Object = { name:meshPrefix + "." + animationVO.name,// name of the matrix
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			//scale bounds
			var bounds:Rectangle = animationVO.bounds.clone();
			bounds.x = bounds.x * _scale;
			bounds.y = bounds.y * _scale;
			bounds.width = bounds.width * _scale;
			bounds.height = bounds.height * _scale;
			data.bounds = bounds;
			//calculate frames
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create matrix
					var transform:Transformation = frameVO.generateTransform(_scale,
							//get previous frame transformation object
							layer.matrixList.length > 0 ? layer.matrixList[ layer.matrixList.length - 1] : null
					);
					var index:int = transform == null ? -1 : layer.matrixList.push(transform) - 1;
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.framesList = layer.framesList.concat(framesArray);
				}
				data.layers.unshift(layer);
			}
			trace(JSON.stringify(data));
			return data;
		}

		[Inline]
		private function createFramesInfo(index:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(index > -1){
				var t:Number = 1 / duration;
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.index = index;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}

		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var matrixList:Array;
	public var framesList:Array;

	public function Layer() {
		this.framesList = [];
		this.matrixList = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var index:int;
	public var t:Number;
}

/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 15:14
 */
package com.merlinds.miracle_tool.controllers {
	import com.merlinds.debug.log;
	import com.merlinds.miracle.geom.Transformation;
	import com.merlinds.miracle_tool.events.ActionEvent;
	import com.merlinds.miracle_tool.events.DialogEvent;
	import com.merlinds.miracle_tool.models.ProjectModel;
	import com.merlinds.miracle_tool.models.vo.AnimationVO;
	import com.merlinds.miracle_tool.models.vo.ElementVO;
	import com.merlinds.miracle_tool.models.vo.FrameVO;
	import com.merlinds.miracle_tool.models.vo.SourceVO;
	import com.merlinds.miracle_tool.models.vo.TimelineVO;
	import com.merlinds.miracle_tool.services.ActionService;
	import com.merlinds.miracle_tool.services.FileSystemService;
	import com.merlinds.miracle_tool.utils.MeshUtils;
	import com.merlinds.unitls.Resolutions;

	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;

	import org.robotlegs.mvcs.Command;

	public class PublishCommand extends Command {

		[Inject]
		public var projectModel:ProjectModel;
		[Inject]
		public var fileSystemService:FileSystemService;
		[Inject]
		public var actionService:ActionService;
		[Inject]
		public var event:ActionEvent;

		private var _mesh:ByteArray;
		private var _png:ByteArray;
		private var _animations:ByteArray;

		private var _scale:Number;
		private var _elements:Object;
		//==============================================================================
		//{region							PUBLIC METHODS
		public function PublishCommand() {
			super();
		}

		override public function execute():void {
			log(this, "execute");
			if(event.body == null){
				var data:Object = {
					projectName:this.projectModel.name
				};
				this.actionService.addAcceptActions(new <String>[ActionEvent.PUBLISHING]);
				this.dispatch(new DialogEvent(DialogEvent.PUBLISH_SETTINGS, data));
			}else{
				var  projectName:String = event.body.projectName;
				_scale = Resolutions.width(this.projectModel.targetResolution) /
						Resolutions.width(this.projectModel.referenceResolution);
				this.createOutput();
				this.fileSystemService.writeTexture(projectName, _png, _mesh);
				this.fileSystemService.writeAnimation(_animations);
			}
		}

		//} endregion PUBLIC METHODS ===================================================

		//==============================================================================
		//{region						PRIVATE\PROTECTED METHODS
		private function createOutput():void{
			var buffer:BitmapData = new BitmapData(this.projectModel.outputSize, this.projectModel.outputSize, true, 0x0);
			_elements = {};
			var meshes:Array = [];
			var animations:Array = [];
			var n:int = this.projectModel.sources.length;
			for(var i:int = 0; i < n; i++){
				var mesh:Array = [];
				var source:SourceVO = this.projectModel.sources[i];
				var m:int = source.elements.length;
				for(var j:int = 0; j < m; j++){
					//push element view to buffer
					var element:ElementVO = source.elements[j];
					var depth:Point = new Point(element.x + this.projectModel.boundsOffset,
									element.y + this.projectModel.boundsOffset);
					buffer.copyPixels(element.bitmapData, element.bitmapData.rect, depth);
					//get mesh
					mesh.push({
						name:element.name,
						vertexes:MeshUtils.flipToY(element.vertexes),
						uv:MeshUtils.covertToRelative(element.uv, this.projectModel.outputSize),
						indexes:element.indexes
					});
					//save mesh bounds to buffer for animation bounds calculation
					_elements[element.name] = element.vertexes;
				}
				meshes.push({name:source.clearName, mesh:mesh});
				//get animation
				m = source.animations.length;
				for(j = 0; j < m; j++){
					var animation:Object = this.createAnimationOutput(source.animations[j], source.clearName);
					animations.push(animation);
				}
			}
			trace("mesh");
			var json:String = JSON.stringify(meshes);
			trace(json);
			_mesh = new ByteArray();
			_mesh.writeObject(meshes);
			_animations = new ByteArray();
			_animations.writeObject(animations);
			_animations.position = 0;
			var test:Object = _animations.readObject();
			_png = PNGEncoder.encode(buffer);
		}

		private function createAnimationOutput(animationVO:AnimationVO, meshPrefix:String):Object {
			var data:Object = { name:meshPrefix + "." + animationVO.name,// name of the matrix
				totalFrames:animationVO.totalFrames,// Total animation frames
				layers:[]};
			//scale bounds
			var bounds:Rectangle = animationVO.bounds.clone();
			bounds.x = bounds.x * _scale;
			bounds.y = bounds.y * _scale;
			bounds.width = bounds.width * _scale;
			bounds.height = bounds.height * _scale;
			data.bounds = bounds;
			//calculate frames
			var n:int = animationVO.timelines.length;
			//find matrix sequence for current animation
			for(var i:int = 0; i < n; i++){
				var timelineVO:TimelineVO = animationVO.timelines[i];
				var m:int = timelineVO.frames.length;
				var layer:Layer = new Layer();
				for(var j:int = 0; j < m; j++){
					var frameVO:FrameVO = timelineVO.frames[j];
					//create matrix
					var transform:Transformation = frameVO.generateTransform(_scale,
							//get previous frame transformation object
							layer.matrixList.length > 0 ? layer.matrixList[ layer.matrixList.length - 1] : null
					);
					var index:int = transform == null ? -1 : layer.matrixList.push(transform) - 1;
					var framesArray:Array = this.createFramesInfo(index, frameVO.name,
							frameVO.duration, frameVO.type == "motion");
					layer.framesList = layer.framesList.concat(framesArray);
				}
				data.layers.unshift(layer);
			}
			return data;
		}

		[Inline]
		private function createFramesInfo(index:int, polygonName:String,
		                                  duration:int, motion:Boolean):Array {
			var result:Array = new Array(duration);
			if(duration < 2){
				motion = false;
			}
			if(index > -1){
				var t:Number = 1 / duration;
				for(var i:int = 0; i < duration; i++){
					var frameInfoData:FrameInfoData = new FrameInfoData();
					frameInfoData.index = index;
					frameInfoData.polygonName = polygonName;
					frameInfoData.motion = motion;
					frameInfoData.t = motion ? t * i : 0;
					result[i] = frameInfoData;
				}
			}
			return result;
		}

		//} endregion PRIVATE\PROTECTED METHODS ========================================

		//==============================================================================
		//{region							EVENTS HANDLERS
		//} endregion EVENTS HANDLERS ==================================================

		//==============================================================================
		//{region							GETTERS/SETTERS
		//} endregion GETTERS/SETTERS ==================================================
	}
}
class Layer{
	public var matrixList:Array;
	public var framesList:Array;

	public function Layer() {
		this.framesList = [];
		this.matrixList = [];
	}
}

class FrameInfoData{
	public var polygonName:String;
	public var motion:Boolean;
	public var index:int;
	public var t:Number;
}

Add gestouch extension for miracle

Add gestouch extension for miracle


package aerys.minko.scene.node
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.material.Material;
	import aerys.minko.render.material.basic.BasicMaterial;
	import aerys.minko.render.material.basic.BasicShader;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.type.Signal;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.bounding.FrustumCulling;
	import aerys.minko.type.enum.DataProviderUsage;
	import aerys.minko.type.math.Ray;

	/**
	 * Mesh objects are a visible instance of a Geometry rendered using a specific
	 * Effect with specific rendering properties.
	 *
	 *

	 * Those rendering properties are stored in a DataBindings object so they can
	 * be directly used by the shaders in the rendering API.
	 *

	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Mesh extends AbstractSceneNode
	{
		public static const DEFAULT_MATERIAL	: Effect	= new Effect(
			new BasicShader()
		);

		private var _geometry			: Geometry					= null;
		private var _properties			: DataProvider				= null;
		private var _material			: Material					= null;
		private var _bindings			: DataBindings				= null;

		private var _visibility			: MeshVisibilityController	= new MeshVisibilityController();

		private var _frame				: uint						= 0;

		private var _cloned				: Signal 					= new Signal('Mesh.clones');
		private var _materialChanged	: Signal					= new Signal('Mesh.materialChanged');
		private var _frameChanged		: Signal					= new Signal('Mesh.frameChanged');
		private var _geometryChanged	: Signal					= new Signal('Mesh.geometryChanged');

		/**
		 * A DataProvider object already bound to the Mesh bindings.
		 *
		 *

		 * // set the "diffuseColor" property to 0x0000ffff
		 * mesh.properties.diffuseColor = 0x0000ffff;
		 *
		 * // animate the "diffuseColor" property
		 * mesh.addController(
		 *   new AnimationController(
		 * 	  new <ITimeline>[new ColorTimeline(
		 * 	    "dataProvider.diffuseColor",
		 * 	    5000,
		 * 	    new <uint>[0xffffffff, 0xffffff00, 0xffffffff]
		 *    )]
		 *   )
		 * );
		 *

		 *
		 * @return
		 *
		 */
		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 material() : Material
		{
			return _material;
		}
		public function set material(value : Material) : void
		{
			if (_material != value)
			{
				if (_material)
					_bindings.removeProvider(_material);

				_material = value;

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

		/**
		 * The rendering properties provided to the shaders to customize
		 * how the mesh will appear on screen.
		 *
		 * @return
		 *
		 */
		public function get bindings() : DataBindings
		{
			return _bindings;
		}

		/**
		 * The Geometry of the mesh.
		 * @return
		 *
		 */
		public function get geometry() : Geometry
		{
			return _geometry;
		}

		public function set geometry(value : Geometry) : void
		{
			if (_geometry != value)
			{
				var oldGeometry	: Geometry = _geometry;

				_geometry = value;
				_geometryChanged.execute(this, oldGeometry, value);
			}
		}

		/**
		 * Whether the mesh is visible or not.
		 *
		 * @return
		 *
		 */
		public function get visible() : Boolean
		{
			return _visibility.visible;
		}
		public function set visible(value : Boolean) : void
		{
			_visibility.visible = value;
		}

		public function get frustumCulling() : uint
		{
			return _visibility.frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_visibility.frustumCulling = value;
		}

		public function get cloned() : Signal
		{
			return _cloned;
		}

		public function get materialChanged() : Signal
		{
			return _materialChanged;
		}

		public function get frameChanged() : Signal
		{
			return _frameChanged;
		}

		public function get geometryChanged() : Signal
		{
			return _geometryChanged;
		}

		public function get frame() : uint
		{
			return _frame;
		}
		public function set frame(value : uint) : void
		{
			if (_frame != value)
			{
				var oldFrame : uint = _frame;

				_frame = value;
				_frameChanged.execute(this, oldFrame, value);
			}
		}

		public function Mesh(geometry	: Geometry	= null,
							 material	: Material	= null,
							 effect		: Effect	= null,
							 ...controllers)
		{
			super();

			initialize(geometry, material, effect, controllers);
		}

		private function initialize(geometry	: Geometry,
									material	: Material,
									effect		: Effect,
									controllers	: Array) : void
		{
			_bindings = new DataBindings(this);
			this.properties = new DataProvider(properties, 'meshProperties', DataProviderUsage.EXCLUSIVE);

			_geometry = geometry;
			this.material = material || new BasicMaterial();

//			_visibility.frustumCulling = FrustumCulling.ENABLED;
			addController(_visibility);

			while (controllers && !(controllers[0] is AbstractController))
				controllers = controllers[0];

			if (controllers)
				for each (var ctrl : AbstractController in controllers)
					addController(ctrl);
		}

		public function cast(ray : Ray, maxDistance : Number = Number.POSITIVE_INFINITY) : Number
		{
			return _geometry.boundingBox.testRay(
				ray,
				worldToLocal,
				maxDistance
			);
		}

		override public function clone(cloneControllers : Boolean = false) : ISceneNode
		{
			var clone : Mesh = new Mesh();

			clone.copyFrom(this, true, cloneControllers);

			return clone;
		}

		override protected function addedToSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			super.addedToSceneHandler(child, scene);

			if (child === this)
				_bindings.addProvider(transformData);
		}

		override protected function removedFromSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			super.removedFromSceneHandler(child, scene);

			if (child === this)
				_bindings.removeProvider(transformData);
		}

		protected function copyFrom(source 				: Mesh,
									withBindings 		: Boolean,
									cloneControllers 	: Boolean) : void
		{
			var numControllers : uint = source.numControllers;

			name 		= source.name;

			geometry 	= source._geometry;
			properties	= DataProvider(source._properties.clone());

			_bindings.copySharedProvidersFrom(source._bindings);

			copyControllersFrom(
				source, this, cloneControllers, new <AbstractController>[source._visibility]
			);

			_visibility = source._visibility.clone() as MeshVisibilityController;
			addController(_visibility);

			transform.copyFrom(source.transform);
			material = source._material;

			source.cloned.execute(this, source);
		}
	}
}

package aerys.minko.scene.node
{
	import aerys.minko.render.Effect;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.material.Material;
	import aerys.minko.render.material.basic.BasicMaterial;
	import aerys.minko.render.material.basic.BasicShader;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.mesh.MeshVisibilityController;
	import aerys.minko.type.Signal;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.binding.DataProvider;
	import aerys.minko.type.bounding.FrustumCulling;
	import aerys.minko.type.enum.DataProviderUsage;
	import aerys.minko.type.math.Ray;

	/**
	 * Mesh objects are a visible instance of a Geometry rendered using a specific
	 * Effect with specific rendering properties.
	 *
	 *

	 * Those rendering properties are stored in a DataBindings object so they can
	 * be directly used by the shaders in the rendering API.
	 *

	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Mesh extends AbstractSceneNode
	{
		public static const DEFAULT_MATERIAL	: Effect	= new Effect(
			new BasicShader()
		);

		private var _geometry			: Geometry					= null;
		private var _properties			: DataProvider				= null;
		private var _material			: Material					= null;
		private var _bindings			: DataBindings				= null;

		private var _visibility			: MeshVisibilityController	= new MeshVisibilityController();

		private var _frame				: uint						= 0;

		private var _cloned				: Signal 					= new Signal('Mesh.clones');
		private var _materialChanged	: Signal					= new Signal('Mesh.materialChanged');
		private var _frameChanged		: Signal					= new Signal('Mesh.frameChanged');
		private var _geometryChanged	: Signal					= new Signal('Mesh.geometryChanged');

		/**
		 * A DataProvider object already bound to the Mesh bindings.
		 *
		 *

		 * // set the "diffuseColor" property to 0x0000ffff
		 * mesh.properties.diffuseColor = 0x0000ffff;
		 *
		 * // animate the "diffuseColor" property
		 * mesh.addController(
		 *   new AnimationController(
		 * 	  new <ITimeline>[new ColorTimeline(
		 * 	    "dataProvider.diffuseColor",
		 * 	    5000,
		 * 	    new <uint>[0xffffffff, 0xffffff00, 0xffffffff]
		 *    )]
		 *   )
		 * );
		 *

		 *
		 * @return
		 *
		 */
		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 material() : Material
		{
			return _material;
		}
		public function set material(value : Material) : void
		{
			if (_material != value)
			{
				if (_material)
					_bindings.removeProvider(_material);

				_material = value;

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

		/**
		 * The rendering properties provided to the shaders to customize
		 * how the mesh will appear on screen.
		 *
		 * @return
		 *
		 */
		public function get bindings() : DataBindings
		{
			return _bindings;
		}

		/**
		 * The Geometry of the mesh.
		 * @return
		 *
		 */
		public function get geometry() : Geometry
		{
			return _geometry;
		}

		public function set geometry(value : Geometry) : void
		{
			if (_geometry != value)
			{
				var oldGeometry	: Geometry = _geometry;

				_geometry = value;
				_geometryChanged.execute(this, oldGeometry, value);
			}
		}

		/**
		 * Whether the mesh is visible or not.
		 *
		 * @return
		 *
		 */
		public function get visible() : Boolean
		{
			return _visibility.visible;
		}
		public function set visible(value : Boolean) : void
		{
			_visibility.visible = value;
		}

		public function get frustumCulling() : uint
		{
			return _visibility.frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_visibility.frustumCulling = value;
		}

		public function get cloned() : Signal
		{
			return _cloned;
		}

		public function get materialChanged() : Signal
		{
			return _materialChanged;
		}

		public function get frameChanged() : Signal
		{
			return _frameChanged;
		}

		public function get geometryChanged() : Signal
		{
			return _geometryChanged;
		}

		public function get frame() : uint
		{
			return _frame;
		}
		public function set frame(value : uint) : void
		{
			if (_frame != value)
			{
				var oldFrame : uint = _frame;

				_frame = value;
				_frameChanged.execute(this, oldFrame, value);
			}
		}

		public function Mesh(geometry	: Geometry	= null,
							 material	: Material	= null,
							 ...controllers)
		{
			super();

			initialize(geometry, material, controllers);
		}

		private function initialize(geometry	: Geometry,
									material	: Material,
									controllers	: Array) : void
		{
			_bindings = new DataBindings(this);
			this.properties = new DataProvider(properties, 'meshProperties', DataProviderUsage.EXCLUSIVE);

			_geometry = geometry;
			this.material = material || new BasicMaterial();

//			_visibility.frustumCulling = FrustumCulling.ENABLED;
			addController(_visibility);

			while (controllers && !(controllers[0] is AbstractController))
				controllers = controllers[0];

			if (controllers)
				for each (var ctrl : AbstractController in controllers)
					addController(ctrl);
		}

		public function cast(ray : Ray, maxDistance : Number = Number.POSITIVE_INFINITY) : Number
		{
			return _geometry.boundingBox.testRay(
				ray,
				worldToLocal,
				maxDistance
			);
		}

		override public function clone(cloneControllers : Boolean = false) : ISceneNode
		{
			var clone : Mesh = new Mesh();

			clone.copyFrom(this, true, cloneControllers);

			return clone;
		}

		override protected function addedToSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			super.addedToSceneHandler(child, scene);

			if (child === this)
				_bindings.addProvider(transformData);
		}

		override protected function removedFromSceneHandler(child : ISceneNode, scene : Scene) : void
		{
			super.removedFromSceneHandler(child, scene);

			if (child === this)
				_bindings.removeProvider(transformData);
		}

		protected function copyFrom(source 				: Mesh,
									withBindings 		: Boolean,
									cloneControllers 	: Boolean) : void
		{
			var numControllers : uint = source.numControllers;

			name 		= source.name;

			geometry 	= source._geometry;
			properties	= DataProvider(source._properties.clone());

			_bindings.copySharedProvidersFrom(source._bindings);

			copyControllersFrom(
				source, this, cloneControllers, new <AbstractController>[source._visibility]
			);

			_visibility = source._visibility.clone() as MeshVisibilityController;
			addController(_visibility);

			transform.copyFrom(source.transform);
			material = source._material;

			source.cloned.execute(this, source);
		}
	}
}

remove deprecated effect : Effect argument in Mesh constructor and Mesh.initialize()


//
// $Id: $

package flash.utils {

import flash.events.EventDispatcher;
import flash.events.TimerEvent;

import de.polygonal.ds.Heap;

public class Timer extends EventDispatcher
{
    public function Timer (delay :Number, repeatCount :int = 0)
    {
        if (_heap == null) {
            _heap = new Heap(10000, compareTimers);
            Thane.requestHeartbeat(heartbeat);
        }

        _delay = delay;
        _repeatCount = repeatCount;
        reset();
    }

    public function get currentCount () :int
    {
        return _currentCount;
    }

    public function get delay () :Number
    {
        return _delay;
    }

    public function set delay (value :Number) :void
    {
        _delay = value;
        if (running) {
            stop();
            start();
        }
    }

    public function get repeatCount () :int
    {
        return _repeatCount;
    }

    public function get running () :Boolean
    {
        return _buddy != null;
    }

    public function start () :void
    {
        if (_buddy != null) {
            return;
        }

        enqueue(new Buddy(this, getTimer() + _delay));
    }

    public function stop () :void
    {
        // cut any current enqueued buddy adrift
        if (_buddy != null) {
            _buddy.budette = null;
            _buddy = null;
        }
    }

    public function reset () :void
    {
        stop();
        _currentCount = 0;
    }

    protected function expire (buddy :Buddy) :void
    {
        if (buddy !== _buddy) {
            // the timer was stopped since this buddy was enqueued
            return;
        }
        _currentCount ++;
        dispatchEvent(new TimerEvent(TimerEvent.TIMER));
        if (_buddy == null) {
            // the timer was stopped in the TIMER event itself - do not requeue!
            return;
        }

        if (repeatCount == 0 || _currentCount < _repeatCount) {
            buddy.expiration += _delay;
            enqueue(buddy);

        } else {
            dispatchEvent(new TimerEvent(TimerEvent.TIMER_COMPLETE));
            _buddy = null;
        }
    }

    private function enqueue (buddy :Buddy) :void
    {
        _buddy = buddy;
        if (_heap.enqueue(_buddy)) {
            return;
        }

        // TODO: formalize this as a quota rather than an implementation deficiency?
        throw new Error("Too many simultaneous running flash.utils.Timer objects");
    }

    private static function compareTimers (a :Buddy, b :Buddy) :int
    {
        // b-a rather than a-b so as to order low values before high
        return b.expiration - a.expiration;
    }

    protected static function heartbeat () :void
    {
        var now :int = getTimer();

        while (_heap.size > 0 && _heap.front.expiration <= now) {
            var buddy :Buddy = _heap.dequeue();
            if (buddy.budette != null) {
                buddy.budette.expire(buddy);
            }
        }
    }

    static function createTimer (closure :Function, delay :Number,
                                 timeout :Boolean, args :Array) :uint
    {
        var timer :Timer = new Timer(delay, timeout ? 1 : 0);
        var thisIx = _timerIx;
        var fun :Function = function (event :TimerEvent) :void {
            if (timeout) {
                destroyTimer(thisIx);
            }
            closure.apply(null, args);
        };
        timer.addEventListener(TimerEvent.TIMER, fun);
        _timers[_timerIx] = [ timer, fun ];
        timer.start();
        return _timerIx ++;
    }

    static function destroyTimer (id :uint) :void
    {
        var bits :Array = _timers[id];
        if (bits) {
            bits[0].removeEventListener(TimerEvent.TIMER, bits[1]);
            bits[0].stop();
            delete _timers[id];
        }
    }

    private var _currentCount :int;
    private var _delay :Number;
    private var _repeatCount :int;
    private var _buddy :Buddy;

    private static var _heap :Heap;
    private static var _timers :Dictionary = new Dictionary();
    private static var _timerIx :uint = 1;
}
}

import flash.utils.Timer;

class Buddy {
    public var budette :Timer;
    public var expiration :int;

    public function Buddy (timer :Timer, expiration :int)
    {
        this.budette = timer;
        this.expiration = expiration;
    }
}

//
// $Id: $

package flash.utils {

import flash.events.EventDispatcher;
import flash.events.TimerEvent;

import de.polygonal.ds.Heap;

public class Timer extends EventDispatcher
{
    public function Timer (delay :Number, repeatCount :int = 0)
    {
        if (_heap == null) {
            _heap = new Heap(10000, compareTimers);
            Thane.requestHeartbeat(heartbeat);
        }

        _delay = delay;
        _repeatCount = repeatCount;
        reset();
    }

    public function get currentCount () :int
    {
        return _currentCount;
    }

    public function get delay () :Number
    {
        return _delay;
    }

    public function set delay (value :Number) :void
    {
        _delay = value;
        if (running) {
            stop();
            start();
        }
    }

    public function get repeatCount () :int
    {
        return _repeatCount;
    }

    public function get running () :Boolean
    {
        return _buddy != null;
    }

    public function start () :void
    {
        if (_buddy != null) {
            return;
        }

        scheduleBuddy(new Buddy(this));
    }

    public function stop () :void
    {
        // cut any current enqueued buddy adrift
        if (_buddy != null) {
            _buddy.budette = null;
            _buddy = null;
        }
    }

    public function reset () :void
    {
        stop();
        _currentCount = 0;
    }

    protected function expire (buddy :Buddy) :void
    {
        if (buddy !== _buddy) {
            // the timer was stopped since this buddy was enqueued
            return;
        }
        _currentCount ++;
        dispatchEvent(new TimerEvent(TimerEvent.TIMER));
        if (_buddy == null) {
            // the timer was stopped in the TIMER event itself - do not requeue!
            return;
        }

        if (repeatCount == 0 || _currentCount < _repeatCount) {
            scheduleBuddy(buddy);

        } else {
            dispatchEvent(new TimerEvent(TimerEvent.TIMER_COMPLETE));
            _buddy = null;
        }
    }

    private function scheduleBuddy (buddy :Buddy) :void
    {
        _buddy = buddy;
        _buddy.expiration = getTimer() + Math.max(_delay, 5);
        if (_heap.enqueue(_buddy)) {
            return;
        }

        // TODO: formalize this as a quota rather than an implementation deficiency?
        throw new Error("Too many simultaneous running flash.utils.Timer objects");
    }

    private static function compareTimers (a :Buddy, b :Buddy) :int
    {
        // b-a rather than a-b so as to order low values before high
        return b.expiration - a.expiration;
    }

    protected static function heartbeat () :void
    {
        var now :int = getTimer();

        // see if we should pop one timer off the queue and expire it; we could pop all the
        // outstanding ones, but this solution puts the onus on developers to make their event
        // handlers light-weight, and protects the socket code from starvation
        if (_heap.size > 0 && _heap.front.expiration <= now) {
            var buddy :Buddy = _heap.dequeue();
            if (buddy.budette != null) {
                buddy.budette.expire(buddy);
            }
        }
    }

    static function createTimer (closure :Function, delay :Number,
                                 timeout :Boolean, args :Array) :uint
    {
        var timer :Timer = new Timer(delay, timeout ? 1 : 0);
        var thisIx = _timerIx;
        var fun :Function = function (event :TimerEvent) :void {
            if (timeout) {
                destroyTimer(thisIx);
            }
            closure.apply(null, args);
        };
        timer.addEventListener(TimerEvent.TIMER, fun);
        _timers[_timerIx] = [ timer, fun ];
        timer.start();
        return _timerIx ++;
    }

    static function destroyTimer (id :uint) :void
    {
        var bits :Array = _timers[id];
        if (bits) {
            bits[0].removeEventListener(TimerEvent.TIMER, bits[1]);
            bits[0].stop();
            delete _timers[id];
        }
    }

    private var _currentCount :int;
    private var _delay :Number;
    private var _repeatCount :int;
    private var _buddy :Buddy;

    private static var _heap :Heap;
    private static var _timers :Dictionary = new Dictionary();
    private static var _timerIx :uint = 1;
}
}

import flash.utils.Timer;
import flash.utils.getTimer;

class Buddy {
    public var budette :Timer;
    public var expiration :int;

    public function Buddy (timer :Timer)
    {
        this.budette = timer;
    }
}

Tweak the Timer implementation: first, schedule repeat expiration based on the current time, not when the timer was supposed to go off. Second, make 5 ms the minimum actual delay used on a timer. Third, Only pop one timer event off the queue in any Thane heartbeat. This last change is somewhere between audacious and questionable; it will definitely help prevent e.g. socket starvation and keep programmers honest. On the other hand it sacrifices some (admittedly bizarre) scenarios where multiple timers ought to fire simultaneously.


package server {
	import flash.events.EventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.events.Event;
	import flash.net.Responder;

	public final class FMSBridge extends EventDispatcher {
		public var connection:FMSConnection;
		public var client:FMSClient;

		public function FMSBridge(rtmpAddress:String = null, ...rest) {
			connection = new FMSConnection();
			connection.client = client = new FMSClient();
			rtmpAddress && connect.apply(this, [rtmpAddress].concat(rest));
		}

		public function connect(rtmpAddress:String, ...rest):void {
			connection.connect.apply(this, [rtmpAddress].concat(rest));
		}

		public function dispose():void {
			connection.dispose();
		}

		public function close():void {
			connection.close();
		}

		public function registerUser(token:String, email:String, player:String):void {
			connection.call('registerUser', null, token, email, player);
		}

		public function signinUser(token:String):void {
			connection.call('signinUser', null, token);
		}

		public function changePlayer(player:uint):void {
			connection.call('changePlayer', null, player);
		}

		public function startGame():void {
			connection.call('startGame', null);
		}

		public function updateScore(score:uint):void {
			connection.call('updateScore', null, score);
		}
	}
}

package server {
	import flash.events.EventDispatcher;
	import flash.events.NetStatusEvent;
	import flash.events.Event;
	import flash.net.Responder;

	public final class FMSBridge extends EventDispatcher {
		public var connection:FMSConnection;
		public var client:FMSClient;

		public function FMSBridge(rtmpAddress:String = null, ...rest) {
			connection = new FMSConnection();
			connection.client = client = new FMSClient();
			rtmpAddress && connect.apply(this, [rtmpAddress].concat(rest));
		}

		public function connect(rtmpAddress:String, ...rest):void {
			connection.connect.apply(this, [rtmpAddress].concat(rest));
		}

		public function dispose():void {
			connection.dispose();
		}

		public function close():void {
			connection.close();
		}

		public function registerUser(token:String, email:String, player:uint):void {
			connection.call('registerUser', null, token, email, player);
		}

		public function signinUser(token:String):void {
			connection.call('signinUser', null, token);
		}

		public function changePlayer(player:uint):void {
			connection.call('changePlayer', null, player);
		}

		public function startGame():void {
			connection.call('startGame', null);
		}

		public function updateScore(score:uint):void {
			connection.call('updateScore', null, score);
		}
	}
}

Update FMSBridge.as

Update FMSBridge.as