////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2016 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers.supportClasses
{

import com.esri.builder.model.CustomWidgetType;
import com.esri.builder.model.WidgetType;
import com.esri.builder.supportClasses.FileUtil;
import com.esri.builder.supportClasses.LogUtil;

import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.utils.ByteArray;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    internal static var loadedModuleInfos:Array = [];

    private var swf:File;
    private var config:File;

    //need reference when loading to avoid being garbage collected before load completes
    private var moduleInfo:IModuleInfo;

    //requires either swf or config
    public function WidgetTypeLoader(swf:File = null, config:File = null)
    {
        this.swf = swf;
        this.config = config;
    }

    public function get name():String
    {
        var fileName:String;

        if (swf && swf.exists)
        {
            fileName = FileUtil.getFileName(swf);
        }
        else if (config && config.exists)
        {
            fileName = FileUtil.getFileName(config);
        }

        return fileName;
    }

    public function load():void
    {
        if (swf && swf.exists)
        {
            loadModule();
        }
        else if (config && config.exists)
        {
            loadConfigModule();
        }
        else
        {
            dispatchError();
        }
    }

    private function loadConfigModule():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading XML module: {0}', config.url);
        }

        var moduleConfig:XML = readModuleConfig(config);
        if (moduleConfig)
        {
            var customWidgetType:CustomWidgetType = parseCustomWidgetType(moduleConfig);
            if (customWidgetType)
            {
                dispatchComplete(customWidgetType);
                return;
            }
        }

        dispatchError();
    }

    private function loadModule():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading SWF module: {0}', swf.url);
        }

        moduleInfo = ModuleManager.getModule(swf.url);

        if (moduleInfo.ready)
        {
            if (Log.isInfo())
            {
                LOG.info('Unloading module: {0}', swf.url);
            }

            moduleInfo.addEventListener(ModuleEvent.UNLOAD, moduleInfo_unloadHandler);
            moduleInfo.release();
            moduleInfo.unload();
        }
        else
        {
            if (Log.isInfo())
            {
                LOG.info('Loading module: {0}', swf.url);
            }

            var fileBytes:ByteArray = new ByteArray();
            var fileStream:FileStream = new FileStream();
            fileStream.open(swf, FileMode.READ);
            fileStream.readBytes(fileBytes);
            fileStream.close();

            moduleInfo.addEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(null, null, fileBytes, FlexGlobals.topLevelApplication.moduleFactory);
        }
    }

    private function moduleInfo_unloadHandler(event:ModuleEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module unloaded: {0}', swf.url);
        }

        var moduleInfo:IModuleInfo = event.module;
        moduleInfo.removeEventListener(ModuleEvent.UNLOAD, moduleInfo_unloadHandler);
        var moduleInfoIndex:int = loadedModuleInfos.indexOf(moduleInfo);
        if (moduleInfoIndex > -1)
        {
            loadedModuleInfos.splice(moduleInfoIndex, 1);
        }
        this.moduleInfo = null;
        FlexGlobals.topLevelApplication.callLater(loadModule);
    }

    private function moduleInfo_readyHandler(event:ModuleEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module loaded: {0}', swf.url);
        }

        var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);

        if (config && config.exists)
        {
            if (Log.isInfo())
            {
                LOG.info('Reading module config: {0}', config.url);
            }
            var moduleConfig:XML = readModuleConfig(config);
        }

        const builderModule:IBuilderModule = event.module.factory.create() as IBuilderModule;
        if (builderModule)
        {
            if (Log.isInfo())
            {
                LOG.info('Widget type created for module: {0}', swf.url);
            }
            var widgetType:WidgetType = getWidgetType(moduleInfo, builderModule, moduleConfig);
        }

        loadedModuleInfos.push(moduleInfo);
        this.moduleInfo = null;
        dispatchComplete(widgetType);
    }

    private function dispatchComplete(widgetType:WidgetType):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module load complete: {0}', name);
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_COMPLETE, widgetType));
    }

    private function getWidgetType(moduleInfo:IModuleInfo, builderModule:IBuilderModule, moduleConfig:XML):WidgetType
    {
        var customModulesDirectoryURL:String = WellKnownDirectories.getInstance().customModules.url;
        var isCustomModule:Boolean = (moduleInfo.url.indexOf(customModulesDirectoryURL) > -1);
        if (moduleConfig)
        {
            var configXML:XML = moduleConfig.configuration[0];
            var version:String = moduleConfig.widgetversion[0];
        }

        return isCustomModule ?
            new CustomWidgetType(builderModule, version, configXML) :
            new WidgetType(builderModule);
    }

    private function readModuleConfig(configFile:File):XML
    {
        var configXML:XML;

        var fileStream:FileStream = new FileStream();
        try
        {
            fileStream.open(configFile, FileMode.READ);
            configXML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
        }
        catch (e:Error)
        {
            //ignore
            if (Log.isInfo())
            {
                LOG.info('Could not read module config: {0}', e.toString());
            }
        }
        finally
        {
            fileStream.close();
        }

        return configXML;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.module;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.READY, moduleInfo_readyHandler);
        this.moduleInfo = null;
        dispatchError();
    }

    private function dispatchError():void
    {
        if (Log.isInfo())
        {
            LOG.info('Module load failed: {0}', name);
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_ERROR));
    }

    private function parseCustomWidgetType(configXML:XML):CustomWidgetType
    {
        if (Log.isInfo())
        {
            LOG.info('Creating widget type from XML: {0}', configXML);
        }

        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:XML = configXML.configuration[0] ? configXML.configuration[0] : <configuration/>;
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";

        return new CustomWidgetType(customModule, widgetVersion, widgetConfiguration);
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2016 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers.supportClasses
{

import com.esri.builder.model.CustomWidgetType;
import com.esri.builder.model.WidgetType;
import com.esri.builder.supportClasses.FileUtil;
import com.esri.builder.supportClasses.LogUtil;

import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.utils.ByteArray;

import modules.IBuilderModule;
import modules.supportClasses.CustomXMLModule;

import mx.core.FlexGlobals;
import mx.events.ModuleEvent;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;

public class WidgetTypeLoader extends EventDispatcher
{
    private static const LOG:ILogger = LogUtil.createLogger(WidgetTypeLoader);

    internal static var loadedModuleInfos:Array = [];

    private var swf:File;
    private var config:File;

    //need reference when loading to avoid being garbage collected before load completes
    private var moduleInfo:IModuleInfo;

    //requires either swf or config
    public function WidgetTypeLoader(swf:File = null, config:File = null)
    {
        this.swf = swf;
        this.config = config;
    }

    public function get name():String
    {
        var fileName:String;

        if (swf && swf.exists)
        {
            fileName = FileUtil.getFileName(swf);
        }
        else if (config && config.exists)
        {
            fileName = FileUtil.getFileName(config);
        }

        return fileName;
    }

    public function load():void
    {
        if (swf && swf.exists)
        {
            loadModule();
        }
        else if (config && config.exists)
        {
            loadConfigModule();
        }
        else
        {
            dispatchError();
        }
    }

    private function loadConfigModule():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading XML module: {0}', config.url);
        }

        var moduleConfig:XML = readModuleConfig(config);
        if (moduleConfig)
        {
            var customWidgetType:CustomWidgetType = parseCustomWidgetType(moduleConfig);
            if (customWidgetType)
            {
                dispatchComplete(customWidgetType);
                return;
            }
        }

        dispatchError();
    }

    private function loadModule():void
    {
        if (Log.isInfo())
        {
            LOG.info('Loading SWF module: {0}', swf.url);
        }

        moduleInfo = ModuleManager.getModule(swf.url);

        if (moduleInfo.ready)
        {
            if (Log.isInfo())
            {
                LOG.info('Unloading module: {0}', swf.url);
            }

            moduleInfo.addEventListener(ModuleEvent.UNLOAD, moduleInfo_unloadHandler);
            moduleInfo.release();
            moduleInfo.unload();
        }
        else
        {
            if (Log.isInfo())
            {
                LOG.info('Loading module: {0}', swf.url);
            }

            var fileBytes:ByteArray = new ByteArray();
            var fileStream:FileStream = new FileStream();
            fileStream.open(swf, FileMode.READ);
            fileStream.readBytes(fileBytes);
            fileStream.close();

            //Use ModuleEvent.PROGRESS instead of ModuleEvent.READY to avoid the case where the latter is never dispatched.
            moduleInfo.addEventListener(ModuleEvent.PROGRESS, moduleInfo_progressHandler);
            moduleInfo.addEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            moduleInfo.load(null, null, fileBytes, FlexGlobals.topLevelApplication.moduleFactory);
        }
    }

    private function moduleInfo_progressHandler(event:ModuleEvent):void
    {
        if (event.bytesLoaded == event.bytesTotal)
        {
            var moduleInfo:IModuleInfo = event.currentTarget as IModuleInfo;
            moduleInfo.removeEventListener(ModuleEvent.PROGRESS, moduleInfo_progressHandler);
            moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
            FlexGlobals.topLevelApplication.callLater(processModuleInfo, [ moduleInfo ]);
        }
    }

    private function moduleInfo_unloadHandler(event:ModuleEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module unloaded: {0}', swf.url);
        }

        var moduleInfo:IModuleInfo = event.module;
        moduleInfo.removeEventListener(ModuleEvent.UNLOAD, moduleInfo_unloadHandler);
        var moduleInfoIndex:int = loadedModuleInfos.indexOf(moduleInfo);
        if (moduleInfoIndex > -1)
        {
            loadedModuleInfos.splice(moduleInfoIndex, 1);
        }
        this.moduleInfo = null;
        FlexGlobals.topLevelApplication.callLater(loadModule);
    }

    private function processModuleInfo(moduleInfo:IModuleInfo):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module loaded: {0}', swf.url);
        }

        if (config && config.exists)
        {
            if (Log.isInfo())
            {
                LOG.info('Reading module config: {0}', config.url);
            }
            var moduleConfig:XML = readModuleConfig(config);
        }

        const builderModule:IBuilderModule = moduleInfo.factory.create() as IBuilderModule;
        if (builderModule)
        {
            if (Log.isInfo())
            {
                LOG.info('Widget type created for module: {0}', swf.url);
            }
            var widgetType:WidgetType = getWidgetType(moduleInfo, builderModule, moduleConfig);
        }

        loadedModuleInfos.push(moduleInfo);
        this.moduleInfo = null;
        dispatchComplete(widgetType);
    }

    private function dispatchComplete(widgetType:WidgetType):void
    {
        if (Log.isInfo())
        {
            LOG.info('Module load complete: {0}', name);
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_COMPLETE, widgetType));
    }

    private function getWidgetType(moduleInfo:IModuleInfo, builderModule:IBuilderModule, moduleConfig:XML):WidgetType
    {
        var customModulesDirectoryURL:String = WellKnownDirectories.getInstance().customModules.url;
        var isCustomModule:Boolean = (moduleInfo.url.indexOf(customModulesDirectoryURL) > -1);
        if (moduleConfig)
        {
            var configXML:XML = moduleConfig.configuration[0];
            var version:String = moduleConfig.widgetversion[0];
        }

        return isCustomModule ?
            new CustomWidgetType(builderModule, version, configXML) :
            new WidgetType(builderModule);
    }

    private function readModuleConfig(configFile:File):XML
    {
        var configXML:XML;

        var fileStream:FileStream = new FileStream();
        try
        {
            fileStream.open(configFile, FileMode.READ);
            configXML = XML(fileStream.readUTFBytes(fileStream.bytesAvailable));
        }
        catch (e:Error)
        {
            //ignore
            if (Log.isInfo())
            {
                LOG.info('Could not read module config: {0}', e.toString());
            }
        }
        finally
        {
            fileStream.close();
        }

        return configXML;
    }

    private function moduleInfo_errorHandler(event:ModuleEvent):void
    {
        var moduleInfo:IModuleInfo = event.module;
        moduleInfo.removeEventListener(ModuleEvent.ERROR, moduleInfo_errorHandler);
        moduleInfo.removeEventListener(ModuleEvent.PROGRESS, moduleInfo_progressHandler);
        this.moduleInfo = null;
        dispatchError();
    }

    private function dispatchError():void
    {
        if (Log.isInfo())
        {
            LOG.info('Module load failed: {0}', name);
        }

        dispatchEvent(new WidgetTypeLoaderEvent(WidgetTypeLoaderEvent.LOAD_ERROR));
    }

    private function parseCustomWidgetType(configXML:XML):CustomWidgetType
    {
        if (Log.isInfo())
        {
            LOG.info('Creating widget type from XML: {0}', configXML);
        }

        var customModule:CustomXMLModule = new CustomXMLModule();
        customModule.widgetName = configXML.name;
        customModule.isOpenByDefault = (configXML.openbydefault == 'true');

        var widgetLabel:String = configXML.label[0];
        var widgetDescription:String = configXML.description[0];
        var widgetHelpURL:String = configXML.helpurl[0];
        var widgetConfiguration:XML = configXML.configuration[0] ? configXML.configuration[0] : <configuration/>;
        var widgetVersion:String = configXML.widgetversion[0];

        customModule.widgetIconLocation = createWidgetIconPath(configXML.icon[0], customModule.widgetName);
        customModule.widgetLabel = widgetLabel ? widgetLabel : customModule.widgetName;
        customModule.widgetDescription = widgetDescription ? widgetDescription : "";
        customModule.widgetHelpURL = widgetHelpURL ? widgetHelpURL : "";

        return new CustomWidgetType(customModule, widgetVersion, widgetConfiguration);
    }

    private function createWidgetIconPath(iconPath:String, widgetName:String):String
    {
        var widgetIconPath:String;

        if (iconPath)
        {
            widgetIconPath = "widgets/" + widgetName + "/" + iconPath;
        }
        else
        {
            widgetIconPath = "assets/images/i_widget.png";
        }

        return widgetIconPath;
    }
}
}

Add workaround to prevent widget load process from hanging.


package aerys.minko.type.binding
{
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;

	public dynamic class DataProvider extends Proxy implements IDynamicDataProvider
	{
		private var _usage				: uint;
		private var _name				: String;
		private var _descriptor			: Object;

		private var _propertyChanged	: Signal;
		private var _propertyAdded		: Signal;
		private var _propertyRemoved	: Signal;

		private var _nameToProperty		: Object;
		private var _propertyToNames	: Dictionary;

		public function get usage() : uint
		{
			return _usage;
		}

		public function get dataDescriptor() : Object
		{
			return _descriptor;
		}

		public function get propertyChanged() : Signal
		{
			return _propertyChanged;
		}

		public function get propertyAdded() : Signal
		{
			return _propertyAdded;
		}

		public function get propertyRemoved() : Signal
		{
			return _propertyRemoved;
		}

		public function get name() : String
		{
			return _name;
		}

		public function set name(v : String) : void
		{
			_name = v;
		}

		public function DataProvider(properties	: Object	= null,
									 name		: String	= null,
									 usage		: uint		= 1)
		{
			_name	= name;
			_usage	= usage;

			initialize(properties);
		}

		private function initialize(properties : Object) : void
		{
			_descriptor = {};
			_propertyChanged = new Signal('DataProvider.changed');
			_propertyAdded = new Signal('DataProvider.propertyAdded');
			_propertyRemoved = new Signal('DataProvider.propertyRemoved');
			_nameToProperty = {};
			_propertyToNames = new Dictionary();

			var propertyName : String = null;

			if (properties is DataProvider)
			{
				var provider : DataProvider = properties as DataProvider;

				for each (propertyName in provider.dataDescriptor)
					setProperty(propertyName, provider[propertyName]);
			}
			else if (properties is Object)
			{
				for (propertyName in properties)
					setProperty(propertyName, properties[propertyName]);
			}
		}

		override flash_proxy function getProperty(name : *) : *
		{
			return getProperty(String(name));
		}

		override flash_proxy function setProperty(name : *, value : *) : void
		{
			setProperty(String(name), value);
		}

		override flash_proxy function deleteProperty(name : *) : Boolean
		{
			removeProperty(String(name));

			return true;
		}

		public function getProperty(name : String) : *
		{
			return _nameToProperty[name];
		}

		public function setProperty(name : String, newValue : Object) : DataProvider
		{
			var oldValue 			: Object			= _nameToProperty[name];
			var oldWatchedValue	    : IWatchable		= oldValue as IWatchable;
			var newWatchedValue	    : IWatchable		= newValue as IWatchable;
			var oldPropertyNames	: Vector.<String>	= _propertyToNames[oldWatchedValue];
			var newPropertyNames	: Vector.<String>	= _propertyToNames[newWatchedValue];

            if (newWatchedValue != oldWatchedValue)
            {
    			if (oldWatchedValue != null)
    			    unwatchProperty(name, oldWatchedValue);

    			if (newWatchedValue != null)
    				watchProperty(name, newWatchedValue);
            }

			var propertyAdded : Boolean = !_descriptor.hasOwnProperty(name);

			_descriptor[name]		= name;
			_nameToProperty[name]	= newValue;

			if (propertyAdded)
				_propertyAdded.execute(this, name, name, newValue);
			else
				_propertyChanged.execute(this, name);

			return this;
		}

		public function setProperties(properties : Object) : DataProvider
		{
			for (var propertyName : String in properties)
				setProperty(propertyName, properties[propertyName]);

			return this;
		}

		protected function watchProperty(name : String, property : IWatchable) : void
		{
			if (property != null)
			{
				var newPropertyNames : Vector.<String> = _propertyToNames[property] as Vector.<String>;

				if (newPropertyNames == null)
				{
					_propertyToNames[property] = newPropertyNames = new <String>[name];
					property.changed.add(propertyChangedHandler);
				}
				else
					newPropertyNames.push(name);
			}
		}

        protected function unwatchProperty(name : String, property : IWatchable) : void
        {
            var oldPropertyNames	: Vector.<String>	= _propertyToNames[property];

            if (oldPropertyNames.length == 1)
            {
                property.changed.remove(propertyChangedHandler);
                delete _propertyToNames[property];
            }
            else
            {
                var numPropertyNames : uint = oldPropertyNames.length - 1;

                oldPropertyNames[oldPropertyNames.indexOf(name)] = oldPropertyNames[numPropertyNames];
                oldPropertyNames.length = numPropertyNames;
            }
        }

		public function removeProperty(name : String) : DataProvider
		{
			if (_descriptor.hasOwnProperty(name))
			{
				var oldMonitoredValue	: IWatchable		= _nameToProperty[name] as IWatchable;
				var oldPropertyNames	: Vector.<String>	= _propertyToNames[oldMonitoredValue];
				var bindingName			: String			= _descriptor[name];

				delete _descriptor[name];
				delete _nameToProperty[name];

				if (oldMonitoredValue != null)
					unwatchProperty(name, oldMonitoredValue);

	//			_changed.execute(this, 'dataDescriptor');
				_propertyRemoved.execute(this, name, bindingName, oldMonitoredValue);
			}

			return this;
		}

		public function removeAllProperties() : DataProvider
		{
			for (var propertyName : String in _nameToProperty)
				removeProperty(propertyName);

			return this;
		}

		public function propertyExists(name : String) : Boolean
		{
			return _descriptor.hasOwnProperty(name);
		}

		public function invalidate() : DataProvider
		{
			_propertyChanged.execute(this, null);

			return this;
		}

		public function clone() : IDataProvider
		{
			switch (_usage)
			{
				case DataProviderUsage.EXCLUSIVE:
					return new DataProvider(_nameToProperty, _name + '_cloned', _usage);
				case DataProviderUsage.SHARED:
					return this;
				case DataProviderUsage.MANAGED:
					throw new Error('This dataprovider is managed, and must not be cloned');

				default:
					throw new Error('Unkown usage value');
			}
		}

		private function propertyChangedHandler(source : IWatchable) : void
		{
			var names		: Vector.<String>	= _propertyToNames[source];
			var numNames	: uint				= names.length;

			for (var nameId : uint = 0; nameId < numNames; ++nameId)
				_propertyChanged.execute(this, names[nameId]);
		}
	}
}

package aerys.minko.type.binding
{
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;

	public dynamic class DataProvider extends Proxy implements IDynamicDataProvider
	{
		private var _usage				: uint;
		private var _name				: String;
		private var _descriptor			: Object;

		private var _propertyChanged	: Signal;
		private var _propertyAdded		: Signal;
		private var _propertyRemoved	: Signal;

		private var _nameToProperty		: Object;
		private var _propertyToNames	: Dictionary;

		public function get usage() : uint
		{
			return _usage;
		}

		public function get dataDescriptor() : Object
		{
			return _descriptor;
		}

		public function get propertyChanged() : Signal
		{
			return _propertyChanged;
		}

		public function get propertyAdded() : Signal
		{
			return _propertyAdded;
		}

		public function get propertyRemoved() : Signal
		{
			return _propertyRemoved;
		}

		public function get name() : String
		{
			return _name;
		}

		public function set name(v : String) : void
		{
			_name = v;
		}

		public function DataProvider(properties	: Object	= null,
									 name		: String	= null,
									 usage		: uint		= 1)
		{
			_name	= name;
			_usage	= usage;

			initialize(properties);
		}

		private function initialize(properties : Object) : void
		{
			_descriptor = {};
			_propertyChanged = new Signal('DataProvider.changed');
			_propertyAdded = new Signal('DataProvider.propertyAdded');
			_propertyRemoved = new Signal('DataProvider.propertyRemoved');
			_nameToProperty = {};
			_propertyToNames = new Dictionary();

			var propertyName : String = null;

			if (properties is DataProvider)
			{
				var provider : DataProvider = properties as DataProvider;

				for each (propertyName in provider.dataDescriptor)
					setProperty(propertyName, provider[propertyName]);
			}
			else if (properties is Object)
			{
				for (propertyName in properties)
					setProperty(propertyName, properties[propertyName]);
			}
		}

		override flash_proxy function getProperty(name : *) : *
		{
			return getProperty(String(name));
		}

		override flash_proxy function setProperty(name : *, value : *) : void
		{
			setProperty(String(name), value);
		}

		override flash_proxy function deleteProperty(name : *) : Boolean
		{
			removeProperty(String(name));

			return true;
		}

		public function getProperty(name : String) : *
		{
			return _nameToProperty[name];
		}

		public function setProperty(name : String, newValue : Object) : DataProvider
		{
			var oldValue 			: Object			= _nameToProperty[name];
			var oldWatchedValue	    : IWatchable		= oldValue as IWatchable;
			var newWatchedValue	    : IWatchable		= newValue as IWatchable;
			var oldPropertyNames	: Vector.<String>	= _propertyToNames[oldWatchedValue];
			var newPropertyNames	: Vector.<String>	= _propertyToNames[newWatchedValue];

            if (newWatchedValue != oldWatchedValue)
            {
    			if (oldWatchedValue != null)
    			    unwatchProperty(name, oldWatchedValue);

    			if (newWatchedValue != null)
    				watchProperty(name, newWatchedValue);
            }

			var propertyAdded : Boolean = !_descriptor.hasOwnProperty(name);

			_descriptor[name]		= name;
			_nameToProperty[name]	= newValue;

			if (propertyAdded)
				_propertyAdded.execute(this, name, name, newValue);
			else
				_propertyChanged.execute(this, name);

			return this;
		}

		public function setProperties(properties : Object) : DataProvider
		{
			for (var propertyName : String in properties)
				setProperty(propertyName, properties[propertyName]);

			return this;
		}

		protected function watchProperty(name : String, property : IWatchable) : void
		{
			if (property != null)
			{
				var newPropertyNames : Vector.<String> = _propertyToNames[property] as Vector.<String>;

				if (newPropertyNames == null)
				{
					_propertyToNames[property] = newPropertyNames = new <String>[name];
					property.changed.add(propertyChangedHandler);
				}
				else
					newPropertyNames.push(name);
			}
		}

        protected function unwatchProperty(name : String, property : IWatchable) : void
        {
            var oldPropertyNames	: Vector.<String>	= _propertyToNames[property];

            if (oldPropertyNames.length == 1)
            {
                property.changed.remove(propertyChangedHandler);
                delete _propertyToNames[property];
            }
            else
            {
                var numPropertyNames : uint = oldPropertyNames.length - 1;

                oldPropertyNames[oldPropertyNames.indexOf(name)] = oldPropertyNames[numPropertyNames];
                oldPropertyNames.length = numPropertyNames;
            }
        }

		public function removeProperty(name : String) : DataProvider
		{
			if (_descriptor.hasOwnProperty(name))
			{
				var oldMonitoredValue	: IWatchable		= _nameToProperty[name] as IWatchable;
				var oldPropertyNames	: Vector.<String>	= _propertyToNames[oldMonitoredValue];
				var bindingName			: String			= _descriptor[name];

				delete _descriptor[name];
				delete _nameToProperty[name];

				if (oldMonitoredValue != null)
					unwatchProperty(name, oldMonitoredValue);

	//			_changed.execute(this, 'dataDescriptor');
				_propertyRemoved.execute(this, name, bindingName, oldMonitoredValue);
			}

			return this;
		}

		public function removeAllProperties() : DataProvider
		{
			for (var propertyName : String in _nameToProperty)
				removeProperty(propertyName);

			return this;
		}

        public function dispose() : void
        {
            removeAllProperties();
            _propertyToNames = null;
            _nameToProperty = null;
            _descriptor = null;
            _propertyAdded = null;
            _propertyChanged = null;
            _propertyRemoved = null;
        }

		public function propertyExists(name : String) : Boolean
		{
			return _descriptor.hasOwnProperty(name);
		}

		public function invalidate() : DataProvider
		{
			_propertyChanged.execute(this, null);

			return this;
		}

		public function clone() : IDataProvider
		{
			switch (_usage)
			{
				case DataProviderUsage.EXCLUSIVE:
					return new DataProvider(_nameToProperty, _name + '_cloned', _usage);
				case DataProviderUsage.SHARED:
					return this;
				case DataProviderUsage.MANAGED:
					throw new Error('This dataprovider is managed, and must not be cloned');

				default:
					throw new Error('Unkown usage value');
			}
		}

		private function propertyChangedHandler(source : IWatchable) : void
		{
			var names		: Vector.<String>	= _propertyToNames[source];
			var numNames	: uint				= names.length;

			for (var nameId : uint = 0; nameId < numNames; ++nameId)
				_propertyChanged.execute(this, names[nameId]);
		}
	}
}

add DataProvider.dispose() method to remove/unwatch all properties and free the memory


package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.1";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.2";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

update version 3.9.2

update version 3.9.2


//
// Flump - Copyright 2013 Flump Authors

package flump.xfl {

import aspire.util.XmlUtil;

import flump.mold.KeyframeMold;
import flump.mold.LayerMold;

public class XflLayer
{
    public static const NAME :String = "name";
    public static const TYPE :String = "layerType";

    public static const TYPE_GUIDE :String = "guide";
    public static const TYPE_FOLDER :String = "folder";
	public static const TYPE_MASK :String = "mask";

    use namespace xflns;

    public static function parse (lib :XflLibrary, baseLocation :String, xml :XML, flipbook :Boolean) :LayerMold {
        const layer :LayerMold = new LayerMold();
        layer.name = XmlUtil.getStringAttr(xml, NAME);
        layer.flipbook = flipbook;
		// mask
		if (mask!=null) layer.mask = mask;
        const location :String = baseLocation + ":" + layer.name;
        var frameXmlList :XMLList = xml.frames.DOMFrame;
        for each (var frameXml :XML in frameXmlList) {
            layer.keyframes.push(XflKeyframe.parse(lib, location, frameXml, flipbook));
        }
        if (layer.keyframes.length == 0) lib.addError(location, ParseError.INFO, "No keyframes on layer");

        var ii :int;
        var kf :KeyframeMold;
        var nextKf :KeyframeMold;

        // normalize skews, so that we always skew the shortest distance between
        // two angles (we don't want to skew more than Math.PI)
        for (ii = 0; ii < layer.keyframes.length - 1; ++ii) {
            kf = layer.keyframes[ii];
            nextKf = layer.keyframes[ii+1];
            frameXml = frameXmlList[ii];

            if (kf.skewX + Math.PI < nextKf.skewX) {
                nextKf.skewX += -Math.PI * 2;
            } else if (kf.skewX - Math.PI > nextKf.skewX) {
                nextKf.skewX += Math.PI * 2;
            }
            if (kf.skewY + Math.PI < nextKf.skewY) {
                nextKf.skewY += -Math.PI * 2;
            } else if (kf.skewY - Math.PI > nextKf.skewY) {
                nextKf.skewY += Math.PI * 2;
            }
        }

        // apply additional rotations
        var additionalRotation :Number = 0;
        for (ii = 0; ii < layer.keyframes.length - 1; ++ii) {
            kf = layer.keyframes[ii];
            nextKf = layer.keyframes[ii+1];
            frameXml = frameXmlList[ii];

            var motionTweenRotate :String = XmlUtil.getStringAttr(frameXml,
                XflKeyframe.MOTION_TWEEN_ROTATE, XflKeyframe.MOTION_TWEEN_ROTATE_NONE);

            // If a direction is specified, take it into account
            if (motionTweenRotate != XflKeyframe.MOTION_TWEEN_ROTATE_NONE) {
                var direction :Number = (motionTweenRotate == XflKeyframe.MOTION_TWEEN_ROTATE_CLOCKWISE ? 1 : -1);
                // negative scales affect rotation direction
                direction *= sign(nextKf.scaleX) * sign(nextKf.scaleY);

                while (direction < 0 && kf.skewX < nextKf.skewX) {
                    nextKf.skewX -= Math.PI * 2;
                }
                while (direction > 0 && kf.skewX > nextKf.skewX) {
                    nextKf.skewX += Math.PI * 2;
                }
                while (direction < 0 && kf.skewY < nextKf.skewY) {
                    nextKf.skewY -= Math.PI * 2;
                }
                while (direction > 0 && kf.skewY > nextKf.skewY) {
                    nextKf.skewY += Math.PI * 2;
                }

                // additional rotations specified?
                var motionTweenRotateTimes :Number =
                    XmlUtil.getNumberAttr(frameXml, XflKeyframe.MOTION_TWEEN_ROTATE_TIMES, 0);
                var thisRotation :Number = motionTweenRotateTimes * Math.PI * 2 * direction;
                additionalRotation += thisRotation;
            }

            nextKf.rotate(additionalRotation);
        }

        return layer;
    }

    protected static function sign (n :Number) :Number {
        return (n > 0 ? 1 : (n < 0 ? -1 : 0));
    }
}
}

//
// Flump - Copyright 2013 Flump Authors

package flump.xfl {

import aspire.util.XmlUtil;

import flump.mold.KeyframeMold;
import flump.mold.LayerMold;

public class XflLayer
{
    public static const NAME :String = "name";
    public static const TYPE :String = "layerType";

    public static const TYPE_GUIDE :String = "guide";
    public static const TYPE_FOLDER :String = "folder";
	public static const TYPE_MASK :String = "mask";

    use namespace xflns;

    public static function parse (lib :XflLibrary, baseLocation :String, xml :XML, flipbook :Boolean,mask:String=null) :LayerMold {
        const layer :LayerMold = new LayerMold();
        layer.name = XmlUtil.getStringAttr(xml, NAME);
        layer.flipbook = flipbook;
		// mask
		if (mask!=null) layer.mask = mask;
        const location :String = baseLocation + ":" + layer.name;
        var frameXmlList :XMLList = xml.frames.DOMFrame;
        for each (var frameXml :XML in frameXmlList) {
            layer.keyframes.push(XflKeyframe.parse(lib, location, frameXml, flipbook));
        }
        if (layer.keyframes.length == 0) lib.addError(location, ParseError.INFO, "No keyframes on layer");

        var ii :int;
        var kf :KeyframeMold;
        var nextKf :KeyframeMold;

        // normalize skews, so that we always skew the shortest distance between
        // two angles (we don't want to skew more than Math.PI)
        for (ii = 0; ii < layer.keyframes.length - 1; ++ii) {
            kf = layer.keyframes[ii];
            nextKf = layer.keyframes[ii+1];
            frameXml = frameXmlList[ii];

            if (kf.skewX + Math.PI < nextKf.skewX) {
                nextKf.skewX += -Math.PI * 2;
            } else if (kf.skewX - Math.PI > nextKf.skewX) {
                nextKf.skewX += Math.PI * 2;
            }
            if (kf.skewY + Math.PI < nextKf.skewY) {
                nextKf.skewY += -Math.PI * 2;
            } else if (kf.skewY - Math.PI > nextKf.skewY) {
                nextKf.skewY += Math.PI * 2;
            }
        }

        // apply additional rotations
        var additionalRotation :Number = 0;
        for (ii = 0; ii < layer.keyframes.length - 1; ++ii) {
            kf = layer.keyframes[ii];
            nextKf = layer.keyframes[ii+1];
            frameXml = frameXmlList[ii];

            var motionTweenRotate :String = XmlUtil.getStringAttr(frameXml,
                XflKeyframe.MOTION_TWEEN_ROTATE, XflKeyframe.MOTION_TWEEN_ROTATE_NONE);

            // If a direction is specified, take it into account
            if (motionTweenRotate != XflKeyframe.MOTION_TWEEN_ROTATE_NONE) {
                var direction :Number = (motionTweenRotate == XflKeyframe.MOTION_TWEEN_ROTATE_CLOCKWISE ? 1 : -1);
                // negative scales affect rotation direction
                direction *= sign(nextKf.scaleX) * sign(nextKf.scaleY);

                while (direction < 0 && kf.skewX < nextKf.skewX) {
                    nextKf.skewX -= Math.PI * 2;
                }
                while (direction > 0 && kf.skewX > nextKf.skewX) {
                    nextKf.skewX += Math.PI * 2;
                }
                while (direction < 0 && kf.skewY < nextKf.skewY) {
                    nextKf.skewY -= Math.PI * 2;
                }
                while (direction > 0 && kf.skewY > nextKf.skewY) {
                    nextKf.skewY += Math.PI * 2;
                }

                // additional rotations specified?
                var motionTweenRotateTimes :Number =
                    XmlUtil.getNumberAttr(frameXml, XflKeyframe.MOTION_TWEEN_ROTATE_TIMES, 0);
                var thisRotation :Number = motionTweenRotateTimes * Math.PI * 2 * direction;
                additionalRotation += thisRotation;
            }

            nextKf.rotate(additionalRotation);
        }

        return layer;
    }

    protected static function sign (n :Number) :Number {
        return (n > 0 ? 1 : (n < 0 ? -1 : 0));
    }
}
}

fix XflLayer parse signature

fix XflLayer parse signature


package com.threerings.ezgame.client {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Loader;

import flash.events.Event;

import flash.utils.Dictionary;

import mx.containers.Canvas;
import mx.containers.VBox;

import mx.core.Container;
import mx.core.IChildList;

import mx.utils.DisplayUtil;

import com.threerings.util.MediaContainer;

import com.threerings.mx.controls.ChatDisplayBox;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;

import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.EZGameObject;

public class EZGamePanel extends VBox
    implements PlaceView
{
    /** The game object backend. */
    public var backend :GameControlBackend;

    public function EZGamePanel (ctx :CrowdContext, ctrl :EZGameController)
    {
        _ctx = ctx;
        _ctrl = ctrl;

        //addChild(new ChatDisplayBox(ctx));
    }

    // from PlaceView
    public function willEnterPlace (plobj :PlaceObject) :void
    {
        var cfg :EZGameConfig = (_ctrl.getPlaceConfig() as EZGameConfig);

        _ezObj = (plobj as EZGameObject);
        backend = new GameControlBackend(_ctx, _ezObj);

        _gameView = new GameContainer(cfg.configData); // TODO?
        backend.setSharedEvents(
            Loader(_gameView.getMedia()).contentLoaderInfo.sharedEvents);
        backend.setContainer(_gameView);
        addChild(_gameView);
    }

    // from PlaceView
    public function didLeavePlace (plobj :PlaceObject) :void
    {
        _gameView.shutdown(true);
        removeChild(_gameView);

        backend.shutdown();
    }

    protected var _ctx :CrowdContext;
    protected var _ctrl :EZGameController;

    protected var _gameView :GameContainer;

    protected var _ezObj :EZGameObject;
}
}

package com.threerings.ezgame.client {

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Loader;

import flash.events.Event;

import flash.utils.Dictionary;

import mx.containers.Canvas;
import mx.containers.VBox;

import mx.core.Container;
import mx.core.IChildList;

import mx.utils.DisplayUtil;

import com.threerings.util.MediaContainer;

import com.threerings.mx.controls.ChatDisplayBox;

import com.threerings.crowd.client.PlaceView;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.util.CrowdContext;

import com.threerings.ezgame.data.EZGameConfig;
import com.threerings.ezgame.data.EZGameObject;

public class EZGamePanel extends VBox
    implements PlaceView
{
    /** The game object backend. */
    public var backend :GameControlBackend;

    public function EZGamePanel (ctx :CrowdContext, ctrl :EZGameController)
    {
        _ctx = ctx;
        _ctrl = ctrl;

        //addChild(new ChatDisplayBox(ctx));
    }

    // from PlaceView
    public function willEnterPlace (plobj :PlaceObject) :void
    {
        var cfg :EZGameConfig = (_ctrl.getPlaceConfig() as EZGameConfig);

        _ezObj = (plobj as EZGameObject);
        backend = createBackend();

        _gameView = new GameContainer(cfg.configData); // TODO?
        backend.setSharedEvents(
            Loader(_gameView.getMedia()).contentLoaderInfo.sharedEvents);
        backend.setContainer(_gameView);
        addChild(_gameView);
    }

    // from PlaceView
    public function didLeavePlace (plobj :PlaceObject) :void
    {
        _gameView.shutdown(true);
        removeChild(_gameView);

        backend.shutdown();
    }

    /**
     * Creates the backend object that will handle requests from user code.
     */
    protected function createBackend () :GameControlBackend
    {
        return new GameControlBackend(_ctx, _ezObj);
    }

    protected var _ctx :CrowdContext;
    protected var _ctrl :EZGameController;

    protected var _gameView :GameContainer;

    protected var _ezObj :EZGameObject;
}
}

Allow subclasses to create custom backends.

git-svn-id: a3e1eb16dde062992de22c830ed8045c8013209a@143 c613c5cb-e716-0410-b11b-feb51c14d237


package org.mangui.HLS.streaming {

    import org.mangui.HLS.*;
    import org.mangui.HLS.utils.Log;

    /** Class that manages auto level selection **/
    public class AutoLevelManager {

        /** Reference to the HLS controller. **/
        private var _hls:HLS;
        /** Reference to the manifest levels. **/
        private var _levels:Array;
        /** switch up threshold **/
        private var _switchup:Array = null;
        /** switch down threshold **/
        private var _switchdown:Array = null;

        /** Create the loader. **/
        public function AutoLevelManager(hls:HLS):void {
            _hls = hls;
            _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
        };

        /** Store the manifest data. **/
        private function _manifestLoadedHandler(event:HLSEvent):void {
            _levels = event.levels;
            _initlevelswitch();
        };

        /* initialize level switching heuristic tables */
        private function _initlevelswitch():void {
          var i:Number;
          var maxswitchup:Number=0;
          var minswitchdwown:Number=Number.MAX_VALUE;
          _switchup = new Array(_levels.length);
          _switchdown = new Array(_levels.length);

          for(i=0 ; i < _levels.length-1; i++) {
             _switchup[i] = (_levels[i+1].bitrate - _levels[i].bitrate) / _levels[i].bitrate;
             maxswitchup = Math.max(maxswitchup,_switchup[i]);
          }
          for(i=0 ; i < _levels.length-1; i++) {
             _switchup[i] = Math.min(maxswitchup,2*_switchup[i]);
             //Log.txt("_switchup["+i+"]="+_switchup[i]);
          }

          for(i = 1; i < _levels.length; i++) {
             _switchdown[i] = (_levels[i].bitrate - _levels[i-1].bitrate) / _levels[i].bitrate;
             minswitchdwown  =Math.min(minswitchdwown,_switchdown[i]);
          }
          for(i = 1; i < _levels.length; i++) {
             _switchdown[i] = Math.max(2*minswitchdwown,_switchdown[i]);
             //Log.txt("_switchdown["+i+"]="+_switchdown[i]);
          }
        }

        /** Update the quality level for the next fragment load. **/
        public function getnextlevel(current_level:Number, buffer:Number, last_segment_duration:Number, last_fetch_duration:Number, last_bandwidth:Number):Number {
         var i:Number;

            var level:Number = -1;
            // Select the lowest non-audio level.
            for(i = 0; i < _levels.length; i++) {
                if(!_levels[i].audio) {
                    level = i;
                    break;
                }
            }
            if(level == -1) {
                Log.txt("No other quality levels are available");
                return -1;
            }
            if(last_fetch_duration == 0 || last_segment_duration == 0) {
               return 0;
            }
            var fetchratio:Number = last_segment_duration/last_fetch_duration;
            var bufferratio:Number = 1000*buffer/last_segment_duration;
            //Log.txt("fetchratio:" + fetchratio);
            //Log.txt("bufferratio:" + bufferratio);

            /* to switch level up :
              fetchratio should be greater than switch up condition,
               but also, when switching levels, we might have to load two fragments :
                - first one for PTS analysis,
                - second one for NetStream injection
               the condition (bufferratio > 2*_levels[_level+1].bitrate/_last_bandwidth)
               ensures that buffer time is bigger than than the time to download 2 fragments from current_level+1, if we keep same bandwidth
            */
            if((current_level < _levels.length-1) && (fetchratio > (1+_switchup[current_level])) && (bufferratio > 2*_levels[current_level+1].bitrate/last_bandwidth)) {
               //Log.txt("fetchratio:> 1+_switchup[_level]="+(1+_switchup[current_level]));
               //Log.txt("switch to level " + (current_level+1));
                  //level up
                  return (current_level+1);
            }
            /* to switch level down :
              fetchratio should be smaller than switch down condition,
               or buffer time is too small to retrieve one fragment with current level
            */

            else if(current_level > 0 &&((fetchratio < (1-_switchdown[current_level])) || (bufferratio < 1)) ) {
                  //Log.txt("bufferratio < 2 || fetchratio: < 1-_switchdown[_level]="+(1-_switchdown[_level]));
                  /* find suitable level matching current bandwidth, starting from current level
                     when switching level down, we also need to consider that we might need to load two fragments.
                     the condition (bufferratio > 2*_levels[j].bitrate/_last_bandwidth)
                    ensures that buffer time is bigger than than the time to download 2 fragments from level j, if we keep same bandwidth
                  */
                  for(var j:Number = current_level-1; j > 0; j--) {
                     if( _levels[j].bitrate <= last_bandwidth && (bufferratio > 2*_levels[j].bitrate/last_bandwidth)) {
                          Log.txt("switch to level " + j);
                          return j;
                      }
                  }
                  Log.txt("switch to level 0");
                  return 0;
               }
            return current_level;
        }
    }
}

package org.mangui.HLS.streaming {

    import org.mangui.HLS.*;
    import org.mangui.HLS.utils.Log;

    /** Class that manages auto level selection **/
    public class AutoLevelManager {

        /** Reference to the HLS controller. **/
        private var _hls:HLS;
        /** switch up threshold **/
        private var _switchup:Array = null;
        /** switch down threshold **/
        private var _switchdown:Array = null;
        /** bitrate array **/
        private var _bitrate:Array = null;
        /** nb level **/
        private var _nbLevel:Number = 0;
        /** lowest non audio level **/
        private var _lowestNonAudioLevel:Number = -1;

        /** Create the loader. **/
        public function AutoLevelManager(hls:HLS):void {
            _hls = hls;
            _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
        };

        /** Store the manifest data. **/
        private function _manifestLoadedHandler(event:HLSEvent):void {
            var levels:Array= event.levels;
            var maxswitchup:Number=0;
            var minswitchdwown:Number=Number.MAX_VALUE;
            _nbLevel = levels.length;
            _bitrate = new Array(_nbLevel);
            _switchup = new Array(_nbLevel);
            _switchdown = new Array(_nbLevel);
            var i:Number;

            for(i=0 ; i < _nbLevel; i++) {
              _bitrate[i] = levels[i].bitrate;
            }

            // Select the lowest non-audio level.
            for(i = 0; i < _nbLevel; i++) {
                if(!levels[i].audio) {
                    _lowestNonAudioLevel = i;
                    break;
                }
            }

            for(i=0 ; i < _nbLevel-1; i++) {
               _switchup[i] = (_bitrate[i+1] - _bitrate[i]) / _bitrate[i];
               maxswitchup = Math.max(maxswitchup,_switchup[i]);
            }
            for(i=0 ; i < _nbLevel-1; i++) {
               _switchup[i] = Math.min(maxswitchup,2*_switchup[i]);
               //Log.txt("_switchup["+i+"]="+_switchup[i]);
            }

            for(i = 1; i < _nbLevel; i++) {
               _switchdown[i] = (_bitrate[i] - _bitrate[i-1]) / _bitrate[i];
               minswitchdwown  =Math.min(minswitchdwown,_switchdown[i]);
            }
            for(i = 1; i < _nbLevel; i++) {
               _switchdown[i] = Math.max(2*minswitchdwown,_switchdown[i]);
               //Log.txt("_switchdown["+i+"]="+_switchdown[i]);
            }
        };

        /** Update the quality level for the next fragment load. **/
        public function getnextlevel(current_level:Number, buffer:Number, last_segment_duration:Number, last_fetch_duration:Number, last_bandwidth:Number):Number {
            var i:Number;
            var level:Number = _lowestNonAudioLevel;
            if(level == -1) {
                Log.txt("No other quality levels are available");
                return -1;
            }
            if(last_fetch_duration == 0 || last_segment_duration == 0) {
               return 0;
            }
            var fetchratio:Number = last_segment_duration/last_fetch_duration;
            var bufferratio:Number = 1000*buffer/last_segment_duration;
            //Log.txt("fetchratio:" + fetchratio);
            //Log.txt("bufferratio:" + bufferratio);

            /* to switch level up :
              fetchratio should be greater than switch up condition,
               but also, when switching levels, we might have to load two fragments :
                - first one for PTS analysis,
                - second one for NetStream injection
               the condition (bufferratio > 2*_levels[_level+1].bitrate/_last_bandwidth)
               ensures that buffer time is bigger than than the time to download 2 fragments from current_level+1, if we keep same bandwidth
            */
            if((current_level < _nbLevel-1) && (fetchratio > (1+_switchup[current_level])) && (bufferratio > 2*_bitrate[current_level+1]/last_bandwidth)) {
               //Log.txt("fetchratio:> 1+_switchup[_level]="+(1+_switchup[current_level]));
               //Log.txt("switch to level " + (current_level+1));
                  //level up
                  return (current_level+1);
            }
            /* to switch level down :
              fetchratio should be smaller than switch down condition,
               or buffer time is too small to retrieve one fragment with current level
            */

            else if(current_level > 0 && ((fetchratio < (1-_switchdown[current_level])) || (bufferratio < 1)) ) {
                  //Log.txt("bufferratio < 2 || fetchratio: < 1-_switchdown[_level]="+(1-_switchdown[_level]));
                  /* find suitable level matching current bandwidth, starting from current level
                     when switching level down, we also need to consider that we might need to load two fragments.
                     the condition (bufferratio > 2*_levels[j].bitrate/_last_bandwidth)
                    ensures that buffer time is bigger than than the time to download 2 fragments from level j, if we keep same bandwidth
                  */
                  for(var j:Number = current_level-1; j > 0; j--) {
                     if( _bitrate[j] <= last_bandwidth && (bufferratio > 2*_bitrate[j]/last_bandwidth)) {
                          Log.txt("switch to level " + j);
                          return j;
                      }
                  }
                  Log.txt("switch to level 0");
                  return 0;
               }
            return current_level;
        }
    }
}

remove dependencies on global levels Array (make the code re-entrant)


/**
 * Copyright (c) 2009 Lance Carlson
 * See LICENSE for full license information.
 */

package flails.resource {
  import flails.request.RequestPipe;
  import flails.request.HTTPClient;
  import flails.request.ResourcePathBuilder;
  import flails.request.JSONFilter;

  import flash.utils.getQualifiedClassName;

  import mx.core.IMXMLObject;

  public class Resource {
    public var name:String;
    public var instanceClass:Class;

    public function Resource() {}

    public function initialized(parent:Object, id:String):void {
    }

    public function index(resultHandler:Function, errorHandler:Function = null):void {
      requestPipe(resultHandler, errorHandler).index();
    }

    public function show(id:Number, resultHandler:Function, errorHandler:Function = null):void {
      requestPipe(resultHandler, errorHandler).show(id);
    }

    public function requestPipe(resultHandler:Function, errorHandler:Function):RequestPipe {
      // TODO: The pluralization obviously needs to be taken care of
      var pipe:RequestPipe = new HTTPClient(new ResourcePathBuilder(name), new JSONFilter(instanceClass));

      pipe.addEventListener("result", resultHandler);

      return pipe;
    }
  }
}

/**
 * Copyright (c) 2009 Lance Carlson
 * See LICENSE for full license information.
 */

package flails.resource {
  import flails.request.RequestPipe;
  import flails.request.HTTPClient;
  import flails.request.ResourcePathBuilder;
  import flails.request.JSONFilter;

  import mx.core.IMXMLObject;

  public class Resource implements IMXMLObject {
    public var name:String;
    public var instanceClass:Class;

    public function Resource() {}

    public function initialized(parent:Object, id:String):void {
      if (name == null) throw new Error("Name not set for resource.");
      if (instanceClass == null) instanceClass = Record;
    }

    public function index(resultHandler:Function, errorHandler:Function = null):void {
      requestPipe(resultHandler, errorHandler).index();
    }

    public function show(id:Number, resultHandler:Function, errorHandler:Function = null):void {
      requestPipe(resultHandler, errorHandler).show(id);
    }

    public function requestPipe(resultHandler:Function, errorHandler:Function):RequestPipe {
      // TODO: The pluralization obviously needs to be taken care of
      var pipe:RequestPipe = new HTTPClient(new ResourcePathBuilder(name), new JSONFilter(instanceClass));

      pipe.addEventListener("result", resultHandler);

      return pipe;
    }
  }
}

Implement IMXMLObject. Some defaults and checks.


package aerys.minko.render.material.phong.multipass
{
    import aerys.minko.render.RenderTarget;
    import aerys.minko.render.shader.SFloat;
    import aerys.minko.render.shader.Shader;
    import aerys.minko.render.shader.ShaderSettings;
    import aerys.minko.render.shader.part.animation.VertexAnimationShaderPart;
    import aerys.minko.type.enum.ColorMask;

    public class ZPrepassShader extends Shader
    {
		private var _vertexAnimationPart	: VertexAnimationShaderPart;

        public function ZPrepassShader(renderTarget : RenderTarget  = null,
                                       priority     : Number        = 0.0)
        {
            super(renderTarget, priority);
			_vertexAnimationPart	= new VertexAnimationShaderPart(this);
        }

        override protected function initializeSettings(settings : ShaderSettings) : void
        {
            super.initializeSettings(settings);

            settings.colorMask = ColorMask.NONE;
        }

        override protected function getVertexPosition():SFloat
        {
            return localToScreen(_vertexAnimationPart.getAnimatedVertexPosition());
        }

        override protected function getPixelColor():SFloat
        {
            return float4(0, 0, 0, 0);
        }
    }
}

package aerys.minko.render.material.phong.multipass
{
    import aerys.minko.render.RenderTarget;
    import aerys.minko.render.material.basic.BasicProperties;
    import aerys.minko.render.shader.SFloat;
    import aerys.minko.render.shader.Shader;
    import aerys.minko.render.shader.ShaderSettings;
    import aerys.minko.render.shader.part.DiffuseShaderPart;
    import aerys.minko.render.shader.part.animation.VertexAnimationShaderPart;
    import aerys.minko.type.enum.ColorMask;

    public class ZPrepassShader extends Shader
    {
		private var _vertexAnimationPart	: VertexAnimationShaderPart;
		private var _diffuseShaderPart 		: DiffuseShaderPart;

        public function ZPrepassShader(renderTarget : RenderTarget  = null,
                                       priority     : Number        = 0.0)
        {
            super(renderTarget, priority);

			_diffuseShaderPart 		= new DiffuseShaderPart(this);
			_vertexAnimationPart	= new VertexAnimationShaderPart(this);
        }

        override protected function initializeSettings(settings : ShaderSettings) : void
        {
            super.initializeSettings(settings);

            settings.colorMask = ColorMask.NONE;
        }

        override protected function getVertexPosition():SFloat
        {
            return localToScreen(_vertexAnimationPart.getAnimatedVertexPosition());
        }

        override protected function getPixelColor():SFloat
        {
			if (meshBindings.propertyExists(BasicProperties.ALPHA_THRESHOLD))
			{
				var diffuseColor : SFloat = _diffuseShaderPart.getDiffuseColor(false);

				var alphaThreshold : SFloat = meshBindings.getParameter(
					BasicProperties.ALPHA_THRESHOLD, 1
				);

				kill(subtract(0.5, lessThan(diffuseColor.w, alphaThreshold)));
			}

            return float4(0, 0, 0, 0);
        }
    }
}

Fix alpha threshold on multi pass phong effect


/**
 * User: MerlinDS
 * Date: 18.07.2014
 * Time: 17:42
 */
package com.merlinds.miracle_tool.viewer {
	import com.bit101.components.List;
	import com.bit101.components.List;
	import com.bit101.components.Window;
	import com.merlinds.debug.log;
	import com.merlinds.miracle.Miracle;
	import com.merlinds.miracle.display.MiracleImage;
	import com.merlinds.miracle.utils.Asset;
	import com.merlinds.miracle.utils.MtfReader;
	import com.merlinds.miracle_tool.models.AppModel;

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

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

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

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

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

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

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose mesh");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
		}

		private function getAnimations(bytes:ByteArray):Array {
			var result:Array = [];

			return result;
		}
		//} endregion PRIVATE\PROTECTED METHODS ========================================

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

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

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			log(this, "selectAnimationHandler", list.selectedItem);
			//add animation to miracle
			var name:String = list.selectedItem.toString();

		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleImage = event.target as MiracleImage;
			target.moveTO(
					this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1
			);
		}
		//} endregion EVENTS HANDLERS ==================================================

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

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

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

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

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

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

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

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

		private function choseAnimation():void {
			//find animation asset and add all of animations to chose list
			var n:int = _assets.length;
			for(var i:int = 0; i < n; i++){
				var asset:Asset = _assets[i];
				if(asset.type == Asset.TIMELINE_TYPE){
					//parse output
					_window = new Window(this, 0, 0, "Chose mesh");
					var list:List = new List(_window, 0, 0, this.getAnimations(asset.output));
					_window.x = this.stage.stageWidth - _window.width;
					list.addEventListener(Event.SELECT, this.selectAnimationHandler);
				}
			}
		}

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

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

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

		private function selectAnimationHandler(event:Event):void {
			var list:List = event.target as List;
			log(this, "selectAnimationHandler", list.selectedItem);
			//add animation to miracle
			var name:String = list.selectedItem.toString();

		}

		private function imageAddedToStage(event:Event):void {
			var target:MiracleImage = event.target as MiracleImage;
			target.moveTO(
					this.stage.stageWidth - target.width >> 1,
					this.stage.stageHeight - target.height >> 1
			);
		}
		//} endregion EVENTS HANDLERS ==================================================

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

create animation format in miracle

create animation format in miracle