package aerys.minko.type.xpath
{
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.binding.DataBindings;

	import flash.utils.getQualifiedClassName;

	public final class XPathEvaluator
	{
		private var _path 		: String;
		private var _selection	: Vector.<ISceneNode>		= null;
		private var _modifier	: String					= null;
		private var _lexer		: XPathLexer				= null;

		public function get selection() : Vector.<ISceneNode>
		{
			return _selection;
		}

		public function XPathEvaluator(path			: String,
									   selection	: Vector.<ISceneNode>,
									   modifier		: String    = null)
		{
			_modifier = modifier;

			initialize(path, selection);
		}

		private function initialize(path : String, selection : Vector.<ISceneNode>) : void
		{
			_path = path;
			_lexer = new XPathLexer(_path);

			// update root
			var token : String = _lexer.getToken();

			_selection = selection.slice();
			if (token == "/")
			{
				selectRoots();
				_lexer.nextToken(token);
			}

			// parse
			while ((token = _lexer.getToken()) != null)
			{
				switch (token)
				{
					case '//' :
						_lexer.nextToken(token);
						selectDescendants();
						break ;
					case '/' :
						_lexer.nextToken(token);
						selectChildren();
						break ;
					default :
						_lexer.nextToken(token);
						parseNodeType(token);
						break ;
				}
			}
		}

		private function selectChildren(typeName : String = null) : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			if (typeName != null)
				typeName = typeName.toLowerCase();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node is Group)
				{
					var group 		: Group = node as Group;
					var numChildren : uint 	= group.numChildren;

					for (var i : uint = 0; i < numChildren; ++i)
					{
						var child 		: ISceneNode 	= group.getChildAt(i);
						var className	: String		= getQualifiedClassName(child)
						var childType 	: String 		= className.substr(className.lastIndexOf(':') + 1);

						if (typeName == null || childType.toLowerCase() == typeName)
							_selection.push(child);
					}
				}
			}
		}

		private function selectRoots() : void
		{
			var selection	: Vector.<ISceneNode>	= _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			if (_selection.indexOf(node.root) < 0)
				_selection.push(node);
		}

		private function selectDescendants() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				_selection.push(node);
				if (node is Group)
					(node as Group).getDescendantsByType(ISceneNode, _selection);
			}
		}

		private function selectParents() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node.parent)
					_selection.push(node.parent);
				else
					_selection.push(node);
			}
		}

		private function parseNodeType(nodeType : String) : void
		{
			if (nodeType == '.')
			{
				// nothing
			}
			if (nodeType == '..')
				selectParents();
			else if (nodeType == '*')
				selectChildren();
			else
				selectChildren(nodeType);

			// apply predicates
			var token : String = _lexer.getToken();

			while (token == '[')
			{
				_lexer.nextToken(token);
				parsePredicate();

				token = _lexer.getToken();
			}
		}

		private function parsePredicate() : void
		{
			var propertyName	: String	= _lexer.getToken(true);
			var isBinding		: Boolean	= propertyName == '@';

			if (isBinding)
				propertyName = _lexer.getToken(true);

			var index			: int		= parseInt(propertyName);

			filterProperty(_lexer, propertyName, _selection);

			_lexer.checkNextToken(']');
		}

		private function getValueObject(source : Object, chunks : Array) : Object
		{
			if (chunks)
				for each (var chunk : String in chunks)
				source = source[chunk];

			return source;
		}

		private function removeFromSelection(index : uint) : void
		{
			var numNodes : uint = _selection.length - 1;

			_selection[index] = _selection[numNodes];
			_selection.length = numNodes;
		}

		private function filterProperty(lexer : XPathLexer, propertyName : String, selection : Vector.<ISceneNode>) : void
		{
			var isBinding		: Boolean	= propertyName == '@';
			var index			: int		= parseInt(propertyName);

			if (propertyName == 'hasController')
				filterOnController(lexer, selection);
			else if (propertyName == 'hasProperty')
				filterOnProperty(lexer, selection);
			else if (propertyName == 'position')
				filterOnPosition(lexer, selection);
			else if (propertyName == 'last')
				filterLast(lexer, selection);
			else if (propertyName == 'first')
				filterFirst(lexer, selection);
			else if (index.toString() == propertyName)
			{
				if (index < selection.length)
				{
					selection[0] = selection[index];
					selection.length = 1;
				}
				else
					selection.length = 0;
			}
			else
				filterOnValue(lexer, selection, propertyName, isBinding);
		}

		private function filterOnValue(lexer		: XPathLexer,
									   selection	: Vector.<ISceneNode>,
									   propertyName : String,
									   isBinding	: Boolean = false) : void
		{
			var operator	: String	= lexer.getToken(true);
			var chunks		: Array		= [propertyName];

			while (operator == '.')
			{
				chunks.push(lexer.getToken(true));
				operator = lexer.getToken(true);
			}

			var value		: Object	= lexer.getValueToken();
			var numNodes	: uint		= selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node		: ISceneNode	= selection[i];
				var nodeValue 	: Object 		= null;

				if (isBinding && (node['bindings'] is DataBindings))
					nodeValue = (node['bindings'] as DataBindings).getProperty(propertyName);
				else
				{
					try
					{
						nodeValue = getValueObject(node, chunks);
						if (!compare(operator, nodeValue, value))
							removeFromSelection(i);
					}
					catch (e : Error)
					{
						removeFromSelection(i);
					}
				}

			}
		}

		private function filterLast(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			if (selection.length)
			{
				selection[0] = selection[uint(selection.length - 1)];
				selection.length = 1;
			}
		}

		private function filterFirst(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			selection.length = 1;
		}

		private function filterOnController(lexer : XPathLexer, selection : Vector.<ISceneNode>) : Object
		{
			lexer.checkNextToken('(');
			var controllerName : String = lexer.getToken(true);
			lexer.checkNextToken(')');

			var numNodes	: uint	= selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node			: ISceneNode	= selection[i];
				var numControllers	: uint			= node.numControllers;
				var keepSceneNode	: Boolean		= false;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controllerType : String = getQualifiedClassName(node.getController(controllerId));
					controllerType = controllerType.substr(controllerType.lastIndexOf(':') + 1);

					if (controllerType == controllerName)
					{
						keepSceneNode = true;
						break;
					}
				}

				if (!keepSceneNode)
					removeFromSelection(i);
			}

			return null;
		}

		private function filterOnPosition(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			var operator 	: String 	= lexer.getToken(true);
			var value 		: uint 		= uint(parseInt(lexer.getToken(true)));

			switch (operator)
			{
				case '>':
					++value;
				case '>=':
					selection = selection.slice(value);
					break;
				case '<':
					--value;
				case '<=':
					selection = selection.slice(0, value);
					break;
				case '=':
				case '==':
					selection[0] = selection[value];
					selection.length = 1;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnProperty(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');

			var chunks		: Array 	= [lexer.getToken(true)];
			var operator	: String 	= lexer.getToken(true);

			while (operator == '.')
			{
				chunks.push(operator);
				operator = lexer.getToken(true);
			}

			if (operator != ')')
				lexer.throwParseError(')', operator);

			var numNodes : uint	= selection.length;
			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				try
				{
					getValueObject(selection[i], chunks);
				}
				catch (e : Error)
				{
					removeFromSelection(i);
				}
			}
		}

		private function compare(operator : String, a : Object, b : Object) : Boolean
		{
			switch (operator)
			{
				case '>' :
					return a > b;
				case '>=' :
					return a >= b;
				case '<' :
					return a >= b;
				case '<=' :
					return a <= b;
				case '=' :
				case '==' :
					return a == b;
				case '~=' :
					var matches	: Array	= String(a).match(b);

					return matches && matches.length != 0;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}
	}
}

package aerys.minko.type.xpath
{
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.binding.DataBindings;

	import flash.utils.getQualifiedClassName;

	public final class XPathEvaluator
	{
		private var _path 		: String;
		private var _selection	: Vector.<ISceneNode>		= null;
		private var _modifier	: String					= null;
		private var _lexer		: XPathLexer				= null;

		public function get selection() : Vector.<ISceneNode>
		{
			return _selection;
		}

		public function XPathEvaluator(path			: String,
									   selection	: Vector.<ISceneNode>,
									   modifier		: String    = null)
		{
			_modifier = modifier;

			initialize(path, selection);
		}

		private function initialize(path : String, selection : Vector.<ISceneNode>) : void
		{
			_path = path;
			_lexer = new XPathLexer(_path);

			// update root
			var token : String = _lexer.getToken();

			_selection = selection.slice();
			if (token == "/")
			{
				selectRoots();
				_lexer.nextToken(token);
			}

			// parse
			while ((token = _lexer.getToken()) != null)
			{
				switch (token)
				{
					case '//' :
						_lexer.nextToken(token);
						selectDescendants();
						break ;
					case '/' :
						_lexer.nextToken(token);
						selectChildren();
						break ;
					default :
						_lexer.nextToken(token);
						parseNodeType(token);
						break ;
				}
			}
		}

		private function selectChildren(typeName : String = null) : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			if (typeName != null)
				typeName = typeName.toLowerCase();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node is Group)
				{
					var group 		: Group = node as Group;
					var numChildren : uint 	= group.numChildren;

					for (var i : uint = 0; i < numChildren; ++i)
					{
						var child 		: ISceneNode 	= group.getChildAt(i);
						var className	: String		= getQualifiedClassName(child)
						var childType 	: String 		= className.substr(className.lastIndexOf(':') + 1);

						if (typeName == null || childType.toLowerCase() == typeName)
							_selection.push(child);
					}
				}
			}
		}

		private function selectRoots() : void
		{
			var selection	: Vector.<ISceneNode>	= _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			if (_selection.indexOf(node.root) < 0)
				_selection.push(node);
		}

		private function selectDescendants() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				_selection.push(node);
				if (node is Group)
					(node as Group).getDescendantsByType(ISceneNode, _selection);
			}
		}

		private function selectParents() : void
		{
			var selection : Vector.<ISceneNode> = _selection.slice();

			_selection.length = 0;
			for each (var node : ISceneNode in selection)
			{
				if (node.parent)
					_selection.push(node.parent);
				else
					_selection.push(node);
			}
		}

		private function parseNodeType(nodeType : String) : void
		{
			if (nodeType == '.')
			{
				// nothing
			}
			if (nodeType == '..')
				selectParents();
			else if (nodeType == '*')
				selectChildren();
			else
				selectChildren(nodeType);

			// apply predicates
			var token : String = _lexer.getToken();

			while (token == '[')
			{
				_lexer.nextToken(token);
				parsePredicate();

				token = _lexer.getToken();
			}
		}

		private function parsePredicate() : void
		{
			var propertyName	: String	= _lexer.getToken(true);
			var isBinding		: Boolean	= propertyName == '@';

			if (isBinding)
				propertyName = _lexer.getToken(true);

			var index			: int		= parseInt(propertyName);

			filterProperty(_lexer, propertyName, _selection);

			_lexer.checkNextToken(']');
		}

		private function getValueObject(source : Object, chunks : Array) : Object
		{
			if (chunks)
				for each (var chunk : String in chunks)
				source = source[chunk];

			return source;
		}

		private function removeFromSelection(index : uint) : void
		{
			var numNodes : uint = _selection.length - 1;

			_selection[index] = _selection[numNodes];
			_selection.length = numNodes;
		}

		private function filterProperty(lexer : XPathLexer, propertyName : String, selection : Vector.<ISceneNode>) : void
		{
			var isBinding		: Boolean	= propertyName == '@';
			var index			: int		= parseInt(propertyName);

			if (propertyName == 'hasController')
				filterOnController(lexer, selection);
			else if (propertyName == 'hasProperty')
				filterOnProperty(lexer, selection);
			else if (propertyName == 'position')
				filterOnPosition(lexer, selection);
			else if (propertyName == 'last')
				filterLast(lexer, selection);
			else if (propertyName == 'first')
				filterFirst(lexer, selection);
			else if (index.toString() == propertyName)
			{
				if (index < selection.length)
				{
					selection[0] = selection[index];
					selection.length = 1;
				}
				else
					selection.length = 0;
			}
			else
				filterOnValue(lexer, selection, propertyName, isBinding);
		}

		private function filterOnValue(lexer		: XPathLexer,
									   selection	: Vector.<ISceneNode>,
									   propertyName : String,
									   isBinding	: Boolean = false) : void
		{
			var operator	: String	= lexer.getToken(true);
			var chunks		: Array		= [propertyName];

			while (operator == '.')
			{
				chunks.push(lexer.getToken(true));
				operator = lexer.getToken(true);
			}

			var value		: Object	= lexer.getValueToken();
			var numNodes	: uint		= selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node		: ISceneNode	= selection[i];
				var nodeValue 	: Object 		= null;

				if (isBinding && (node['bindings'] is DataBindings))
					nodeValue = (node['bindings'] as DataBindings).getProperty(propertyName);
				else
				{
					try
					{
						nodeValue = getValueObject(node, chunks);
						if (!compare(operator, nodeValue, value))
							removeFromSelection(i);
					}
					catch (e : Error)
					{
						removeFromSelection(i);
					}
				}

			}
		}

		private function filterLast(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			if (selection.length)
			{
				selection[0] = selection[uint(selection.length - 1)];
				selection.length = 1;
			}
		}

		private function filterFirst(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			selection.length = 1;
		}

		private function filterOnController(lexer : XPathLexer, selection : Vector.<ISceneNode>) : Object
		{
			lexer.checkNextToken('(');
			var controllerName : String = lexer.getToken(true);
			lexer.checkNextToken(')');

			var numNodes	: uint	= selection.length;

			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				var node			: ISceneNode	= selection[i];
				var numControllers	: uint			= node.numControllers;
				var keepSceneNode	: Boolean		= false;

				for (var controllerId : uint = 0; controllerId < numControllers; ++controllerId)
				{
					var controllerType : String = getQualifiedClassName(node.getController(controllerId));
					controllerType = controllerType.substr(controllerType.lastIndexOf(':') + 1);

					if (controllerType == controllerName)
					{
						keepSceneNode = true;
						break;
					}
				}

				if (!keepSceneNode)
					removeFromSelection(i);
			}

			return null;
		}

		private function filterOnPosition(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');
			lexer.checkNextToken(')');

			var operator 	: String 	= lexer.getToken(true);
			var value 		: uint 		= uint(parseInt(lexer.getToken(true)));

			switch (operator)
			{
				case '>':
					++value;
				case '>=':
					selection = selection.slice(value);
					break;
				case '<':
					--value;
				case '<=':
					selection = selection.slice(0, value);
					break;
				case '=':
				case '==':
					selection[0] = selection[value];
					selection.length = 1;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}

		private function filterOnProperty(lexer : XPathLexer, selection : Vector.<ISceneNode>) : void
		{
			lexer.checkNextToken('(');

			var chunks		: Array 	= [lexer.getToken(true)];
			var operator	: String 	= lexer.getToken(true);

			while (operator == '.')
			{
				//chunks.push(operator);
				chunks.push(lexer.getToken(true));
				operator = lexer.getToken(true);
			}

			if (operator != ')')
				lexer.throwParseError(')', operator);

			var numNodes : uint	= selection.length;
			for (var i : int = numNodes - 1; i >= 0; --i)
			{
				try
				{
					if (getValueObject(selection[i], chunks) == null)
						removeFromSelection(i);
				}
				catch (e : Error)
				{
					removeFromSelection(i);
				}
			}
		}

		private function compare(operator : String, a : Object, b : Object) : Boolean
		{
			switch (operator)
			{
				case '>' :
					return a > b;
				case '>=' :
					return a >= b;
				case '<' :
					return a >= b;
				case '<=' :
					return a <= b;
				case '=' :
				case '==' :
					return a == b;
				case '~=' :
					var matches	: Array	= String(a).match(b);

					return matches && matches.length != 0;
				default:
					throw new Error('Unknown comparison operator \'' + operator + '\'');
			}
		}
	}
}

Fix xpath for hasProperty command

Fix xpath for hasProperty command


//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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 GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.flex {

import mx.controls.Label;
import mx.controls.Spacer;
import mx.controls.Text;

import mx.core.Container;
import mx.core.UIComponent;

/**
 * Flex-related utility methods.
 */
public class FlexUtil
{
    /**
     * Creates a label with the supplied text and tooltip, and optionally applies a style class to
     * it.
     */
    public static function createTipLabel (
        text :String, toolTip :String, style :String = null) :Label
    {
        var label :Label = new Label();
        label.text = text;
        if (toolTip != null) {
            label.toolTip = toolTip;
        }
        if (style != null) {
            label.styleName = style;
        }
        return label;
    }

    /**
     * Creates a label with the supplied text, and optionally applies a style class to it.
     */
    public static function createLabel (text :String, style :String = null) :Label
    {
        return createTipLabel(text, null, style);
    }

    /**
     * Create an uneditable/unselectable multiline Text widget with the specified text and width.
     */
    public static function createText (text :String, width :int, style :String = null) :Text
    {
        var t :Text = new Text();
        t.styleName = style;
        t.width = width;
        t.selectable = false;
        t.text = text;
        return t;
    }

    /**
     * How hard would it have been for them make Spacer accept two optional arguments?
     */
    public static function createSpacer (width :int = 0, height :int = 0) :Spacer
    {
        var spacer :Spacer = new Spacer();
        spacer.width = width;
        spacer.height = height;
        return spacer;
    }

    /**
     * Return a count of how many visible (includeInLayout=true) children
     * are in the specified container.
     */
    public static function countLayoutChildren (container :Container) :int
    {
        var layoutChildren :int = container.numChildren;
        for each (var child :Object in container.getChildren()) {
            if ((child is UIComponent) && !UIComponent(child).includeInLayout) {
                layoutChildren--;
            }
        }
        return layoutChildren;
    }

    /**
     * In flex the 'visible' property controls visibility separate from whether
     * the component takes up space in the layout, which is controlled by 'includeInLayout'.
     * We usually want to set them together, so this does that for us.
     * @return the new visible setting.
     */
    public static function setVisible (component :UIComponent, visible :Boolean) :Boolean
    {
        component.visible = visible;
        component.includeInLayout = visible;
        return visible;
    }
}
}

//
// $Id$
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/nenya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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 GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.flex {

import mx.controls.Label;
import mx.controls.Spacer;
import mx.controls.Text;

import mx.core.Container;
import mx.core.UIComponent;

/**
 * Flex-related utility methods.
 */
public class FlexUtil
{
    /**
     * Creates a label with the supplied text and tooltip, and optionally applies a style class to
     * it.
     */
    public static function createTipLabel (
        text :String, toolTip :String, style :String = null) :Label
    {
        var label :Label = new Label();
        label.text = text;
        if (toolTip != null) {
            label.toolTip = toolTip;
        }
        if (style != null) {
            label.styleName = style;
        }
        return label;
    }

    /**
     * Creates a label with the supplied text, and optionally applies a style class to it.
     */
    public static function createLabel (text :String, style :String = null) :Label
    {
        return createTipLabel(text, null, style);
    }

    /**
     * Create an uneditable/unselectable multiline Text widget with the specified text and width.
     */
    public static function createText (
        text :String, width :int, style :String = null, html :Boolean = false) :Text
    {
        var t :Text = new Text();
        t.styleName = style;
        t.width = width;
        if (html) {
            // I want selectable = false here too, but that makes links not work. They still
            // have a hand cursor, you can still click the fucking things, but they don't
            // work. WHY ADOBE WHY YOU RANCID PIEFUCKERS?
            t.htmlText = text;
        } else {
            t.selectable = false;
            t.text = text;
        }
        return t;
    }

    /**
     * How hard would it have been for them make Spacer accept two optional arguments?
     */
    public static function createSpacer (width :int = 0, height :int = 0) :Spacer
    {
        var spacer :Spacer = new Spacer();
        spacer.width = width;
        spacer.height = height;
        return spacer;
    }

    /**
     * Return a count of how many visible (includeInLayout=true) children
     * are in the specified container.
     */
    public static function countLayoutChildren (container :Container) :int
    {
        var layoutChildren :int = container.numChildren;
        for each (var child :Object in container.getChildren()) {
            if ((child is UIComponent) && !UIComponent(child).includeInLayout) {
                layoutChildren--;
            }
        }
        return layoutChildren;
    }

    /**
     * In flex the 'visible' property controls visibility separate from whether
     * the component takes up space in the layout, which is controlled by 'includeInLayout'.
     * We usually want to set them together, so this does that for us.
     * @return the new visible setting.
     */
    public static function setVisible (component :UIComponent, visible :Boolean) :Boolean
    {
        component.visible = visible;
        component.includeInLayout = visible;
        return visible;
    }
}
}

Add a way to set the HTML text.
Oh yeah, links don't work unless the field is selectable. WHY?
Carbomb adobe.

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


/*

as3isolib - An open-source ActionScript 3.0 Isometric Library developed to assist
in creating isometrically projected content (such as games and graphics)
targeted for the Flash player platform

http://code.google.com/p/as3isolib/

Copyright (c) 2006 - 3000 J.W.Opitz, All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/
package as3isolib.display
{
	import as3isolib.bounds.IBounds;
	import as3isolib.bounds.PrimitiveBounds;
	import as3isolib.bounds.SceneBounds;
	import as3isolib.core.IIsoDisplayObject;
	import as3isolib.core.IsoDisplayObject;
	import as3isolib.core.as3isolib_internal;
	import as3isolib.display.renderers.ISceneLayoutRenderer;
	import as3isolib.display.renderers.SimpleSceneLayoutRenderer;
	import as3isolib.display.scene.IIsoScene;

	import flash.display.DisplayObjectContainer;

	use namespace as3isolib_internal;

	public class IsoGroup extends IsoDisplayObject implements IIsoScene
	{
		///////////////////////////////////////////////////////////////////////
		//	CONSTRUCTOR
		///////////////////////////////////////////////////////////////////////

		/**
		 * Constructor
		 */
		public function IsoGroup (descriptor:Object = null)
		{
			super(descriptor);
		}

		///////////////////////////////////////////////////////////////////////
		//	I ISO SCENE
		///////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		public function get hostContainer ():DisplayObjectContainer
		{
			return null;
		}

		/**
		 * @private
		 */
		public function set hostContainer (value:DisplayObjectContainer):void
		{
			//do nothing
		}

		/**
		 * @inheritDoc
		 */
		public function get invalidatedChildren ():Array
		{
			var a:Array;

			var child:IIsoDisplayObject;
			for each (child in children)
			{
				if (child.isInvalidated)
					a.push(child);
			}

			return a;
		}

		/**
		 * @inheritDoc
		 */
		override public function get isoBounds ():IBounds
		{
			return bSizeSetExplicitly ? new PrimitiveBounds(this) : new SceneBounds(this);
		}

		///////////////////////////////////////////////////////////////////////
		//	WIDTH LENGTH HEIGHT
		///////////////////////////////////////////////////////////////////////

		private var bSizeSetExplicitly:Boolean;

		/**
		 * @inheritDoc
		 */
		override public function set width (value:Number):void
		{
			super.width = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		/**
		 * @inheritDoc
		 */
		override public function set length (value:Number):void
		{
			super.length = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		/**
		 * @inheritDoc
		 */
		override public function set height (value:Number):void
		{
			super.height = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		///////////////////////////////////////////////////////////////////////
		//	ISO GROUP
		///////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		override protected function renderLogic (recursive:Boolean = true):void
		{
			super.renderLogic(recursive);

			if (bIsInvalidated)
			{
				if (!bSizeSetExplicitly)
					calculateSizeFromChildren();

				var renderer:ISceneLayoutRenderer = new SimpleSceneLayoutRenderer();
				renderer.renderScene(this);

				bIsInvalidated = false;
			}
		}

		/**
		 * @private
		 */
		protected function calculateSizeFromChildren ():void
		{
			var b:IBounds = new SceneBounds(this);

			isoWidth = b.width;
			isoLength = b.length;
			isoHeight = b.height;
		}
	}
}

/*

as3isolib - An open-source ActionScript 3.0 Isometric Library developed to assist
in creating isometrically projected content (such as games and graphics)
targeted for the Flash player platform

http://code.google.com/p/as3isolib/

Copyright (c) 2006 - 3000 J.W.Opitz, All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/
package as3isolib.display
{
	import as3isolib.bounds.IBounds;
	import as3isolib.bounds.PrimitiveBounds;
	import as3isolib.bounds.SceneBounds;
	import as3isolib.core.IIsoDisplayObject;
	import as3isolib.core.IsoDisplayObject;
	import as3isolib.core.as3isolib_internal;
	import as3isolib.display.renderers.ISceneLayoutRenderer;
	import as3isolib.display.renderers.SimpleSceneLayoutRenderer;
	import as3isolib.display.scene.IIsoScene;

	import flash.display.DisplayObjectContainer;

	use namespace as3isolib_internal;

	public class IsoGroup extends IsoDisplayObject implements IIsoScene
	{
		///////////////////////////////////////////////////////////////////////
		//	CONSTRUCTOR
		///////////////////////////////////////////////////////////////////////

		/**
		 * Constructor
		 */
		public function IsoGroup (descriptor:Object = null)
		{
			super(descriptor);
		}

		///////////////////////////////////////////////////////////////////////
		//	I ISO SCENE
		///////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc
		 */
		public function get hostContainer ():DisplayObjectContainer
		{
			return null;
		}

		/**
		 * @private
		 */
		public function set hostContainer (value:DisplayObjectContainer):void
		{
			//do nothing
		}

		/**
		 * @inheritDoc
		 */
		public function get invalidatedChildren ():Array
		{
			var a:Array;

			var child:IIsoDisplayObject;
			for each (child in children)
			{
				if (child.isInvalidated)
					a.push(child);
			}

			return a;
		}

		/**
		 * @inheritDoc
		 */
		override public function get isoBounds ():IBounds
		{
			return bSizeSetExplicitly ? new PrimitiveBounds(this) : new SceneBounds(this);
		}

		///////////////////////////////////////////////////////////////////////
		//	WIDTH LENGTH HEIGHT
		///////////////////////////////////////////////////////////////////////

		private var bSizeSetExplicitly:Boolean;

		/**
		 * @inheritDoc
		 */
		override public function set width (value:Number):void
		{
			super.width = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		/**
		 * @inheritDoc
		 */
		override public function set length (value:Number):void
		{
			super.length = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		/**
		 * @inheritDoc
		 */
		override public function set height (value:Number):void
		{
			super.height = value;

			bSizeSetExplicitly = !isNaN(value);
		}

		///////////////////////////////////////////////////////////////////////
		//	ISO GROUP
		///////////////////////////////////////////////////////////////////////

      public var renderer:ISceneLayoutRenderer = new SimpleSceneLayoutRenderer();

		/**
		 * @inheritDoc
		 */
		override protected function renderLogic (recursive:Boolean = true):void
		{
			super.renderLogic(recursive);

			if (bIsInvalidated)
			{
				if (!bSizeSetExplicitly)
					calculateSizeFromChildren();

				if (!renderer)
               renderer = new SimpleSceneLayoutRenderer();
				renderer.renderScene(this);

				bIsInvalidated = false;
			}
		}

		/**
		 * @private
		 */
		protected function calculateSizeFromChildren ():void
		{
			var b:IBounds = new SceneBounds(this);

			isoWidth = b.width;
			isoLength = b.length;
			isoHeight = b.height;
		}
	}
}

fix for IsoGroup

fix for IsoGroup


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.stream {
    import flash.events.TimerEvent;
    import flash.events.Event;
    import flash.utils.Timer;
    import flash.utils.Dictionary;

    import org.mangui.hls.event.HLSMediatime;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.constant.HLSSeekStates;
    import org.mangui.hls.constant.HLSSeekMode;
    import org.mangui.hls.utils.Log;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;

    /*
     * intermediate FLV Tag Buffer
     *  input : FLV tags retrieved from different fragment loaders (video/alt-audio...)
     *  output : provide muxed FLV tags to HLSNetStream
     */
    public class TagBuffer {
        private var _hls : HLS;
        /** Timer used to process FLV tags. **/
        private var _timer : Timer;
        private var _audioTags : Vector.<FLVData>;
        private var _videoTags : Vector.<FLVData>;
        private var _metaTags : Vector.<FLVData>;
        /** playlist duration **/
        private var _playlist_duration : Number = 0;
        /** requested start position **/
        private var _seek_position_requested : Number;
        /** real start position , retrieved from first fragment **/
        private var _seek_position_real : Number;
        /** start position of first injected tag **/
        private var _first_start_position : Number;
        private var _seek_pos_reached : Boolean;
        /** Current play position (relative position from beginning of sliding window) **/
        private var _playback_current_position : Number;
        /** playlist sliding (non null for live playlist) **/
        private var _playlist_sliding_duration : Number;
        /** buffer PTS (indexed by continuity counter)  */
        private var _buffer_pts : Dictionary;

        public function TagBuffer(hls : HLS) {
            _hls = hls;
            flushAll();
            _timer = new Timer(100, 0);
            _timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
            _hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED, _playlistDurationUpdated);
        }

        public function dispose() : void {
            flushAll();
            _hls.removeEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED, _playlistDurationUpdated);
            _timer.stop();
            _hls = null;
            _timer = null;
        }

        public function stop() : void {
            flushAll();
        }

        public function seek(position : Number) : void {
            _seek_position_requested = position;
            flushAll();
            _timer.start();
        }

        public function flushAll() : void {
            _audioTags = new Vector.<FLVData>();
            _videoTags = new Vector.<FLVData>();
            _metaTags = new Vector.<FLVData>();
            _buffer_pts = new Dictionary();
            _seek_pos_reached = false;
            _playlist_sliding_duration = 0;
            _first_start_position = -1;
        }

        public function flushAudio() : void {
            _audioTags = new Vector.<FLVData>();
        }

        public function appendTags(tags : Vector.<FLVTag>, min_pts : Number, max_pts : Number, continuity : int, start_position : Number) : void {
            for each (var tag : FLVTag in tags) {
                var position : Number = start_position + (tag.pts - min_pts) / 1000;
                var tagData : FLVData = new FLVData(tag, position, continuity);
                switch(tag.type) {
                    case FLVTag.AAC_HEADER:
                    case FLVTag.AAC_RAW:
                    case FLVTag.MP3_RAW:
                        _audioTags.push(tagData);
                        break;
                    case FLVTag.AVC_HEADER:
                    case FLVTag.AVC_NALU:
                        _videoTags.push(tagData);
                        break;
                    case FLVTag.DISCONTINUITY:
                    case FLVTag.METADATA:
                        _metaTags.push(tagData);
                        break;
                    default:
                }
            }
            /* check live playlist sliding here :
            _seek_position_real + getTotalBufferedDuration()  should be the start_position
             * /of the new fragment if the playlist was not sliding
            => live playlist sliding is the difference between the new start position  and this previous value */
            if (_hls.seekState == HLSSeekStates.SEEKED) {
                // Log.info("seek pos/getTotalBufferedDuration/start pos:" + _seek_position_requested + "/" + getTotalBufferedDuration() + "/" + start_position);
                _playlist_sliding_duration = (_first_start_position + getTotalBufferedDuration()) - start_position;
            } else {
                if (_first_start_position == -1) {
                    // remember position of first tag injected after seek. it will be used for playlist sliding computation
                    _first_start_position = start_position;
                }
            }
            // update buffer min/max table indexed with continuity counter
            if (_buffer_pts[continuity] == undefined) {
                _buffer_pts[continuity] = new BufferPTS(min_pts, max_pts);
            } else {
                (_buffer_pts[continuity] as BufferPTS).max = max_pts;
            }
            _timer.start();
        }

        /** Return current media position **/
        public function get position() : Number {
            switch(_hls.seekState) {
                case HLSSeekStates.SEEKING:
                    return  _seek_position_requested;
                case HLSSeekStates.SEEKED:
                    /** Relative playback position = (Absolute Position(seek position + play time) - playlist sliding, non null for Live Playlist) **/
                    return _seek_position_real + _hls.stream.time - _playlist_sliding_duration;
                case HLSSeekStates.IDLE:
                default:
                    return 0;
            }
        };

        public function get audioBufferLength() : Number {
            return getbuflen(_audioTags);
        }

        public function get videoBufferLength() : Number {
            return getbuflen(_videoTags);
        }

        public function get bufferLength() : Number {
            return Math.max(audioBufferLength, videoBufferLength);
        }

        /**  Timer **/
        private function _checkBuffer(e : Event) : void {
            // dispatch media time event
            _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME, new HLSMediatime(position, _playlist_duration, _hls.stream.bufferLength, _playlist_sliding_duration)));

            /* only append tags if seek position has been reached, otherwise wait for more tags to come
             * this is to ensure that accurate seeking will work appropriately
             */
            if (_seek_pos_reached || max_pos >= _seek_position_requested) {
                var flvdata : FLVData;
                var data : Vector.<FLVData> = new Vector.<FLVData>();
                while ((flvdata = shift()) != null) {
                    data.push(flvdata);
                }
                if (!_seek_pos_reached) {
                    data = seekFilterTags(data);
                    _seek_pos_reached = true;
                }

                var tags : Vector.<FLVTag> = new Vector.<FLVTag>();
                for each (flvdata in data) {
                    tags.push(flvdata.tag);
                }
                if (tags.length) {
                    (_hls.stream as HLSNetStream).appendTags(tags);
                    Log.debug("appending " + tags.length + " tags");
                }
            }
        }

        /* filter/tweak tags to seek accurately into the stream */
        private function seekFilterTags(tags : Vector.<FLVData>) : Vector.<FLVData> {
            var filteredTags : Vector.<FLVData>=  new Vector.<FLVData>();
            /* PTS of first tag that will be pushed into FLV tag buffer */
            var first_pts : Number;
            /* PTS of last video keyframe before requested seek position */
            var keyframe_pts : Number;
            /* */
            var min_offset : Number = tags[0].position;
            var min_pts : Number = tags[0].tag.pts;
            /*
             *
             *    real seek       requested seek                 Frag
             *     position           position                    End
             *        *------------------*-------------------------
             *        <------------------>
             *             seek_offset
             *
             * real seek position is the start offset of the first received fragment after seek command. (= fragment start offset).
             * seek offset is the diff between the requested seek position and the real seek position
             */

            /* if requested seek position is out of this segment bounds
             * all the segments will be pushed, first pts should be thus be min_pts
             */
            if (_seek_position_requested < min_offset) {
                _seek_position_real = min_offset;
                first_pts = min_pts;
            } else {
                /* if requested position is within segment bounds, determine real seek position depending on seek mode setting */
                if (HLSSettings.seekMode == HLSSeekMode.SEGMENT_SEEK) {
                    _seek_position_real = min_offset;
                    first_pts = min_pts;
                } else {
                    /* accurate or keyframe seeking */
                    /* seek_pts is the requested PTS seek position */
                    var seek_pts : Number = min_pts + 1000 * (_seek_position_requested - min_offset);
                    /* analyze fragment tags and look for PTS of last keyframe before seek position.*/
                    keyframe_pts = min_pts;
                    for each (var flvData : FLVData in tags) {
                        var tag : FLVTag = flvData.tag;
                        // look for last keyframe with pts <= seek_pts
                        if (tag.keyframe == true && tag.pts <= seek_pts && (tag.type == FLVTag.AVC_HEADER || tag.type == FLVTag.AVC_NALU)) {
                            keyframe_pts = tag.pts;
                        }
                    }
                    if (HLSSettings.seekMode == HLSSeekMode.KEYFRAME_SEEK) {
                        _seek_position_real = min_offset + (keyframe_pts - min_pts) / 1000;
                        first_pts = keyframe_pts;
                    } else {
                        // accurate seek, to exact requested position
                        _seek_position_real = _seek_position_requested;
                        first_pts = seek_pts;
                    }
                }
            }
            /* if in segment seeking mode : push all FLV tags */
            if (HLSSettings.seekMode == HLSSeekMode.SEGMENT_SEEK) {
                filteredTags = tags;
            } else {
                /* keyframe / accurate seeking, we need to filter out some FLV tags */
                for each (flvData in tags) {
                    tag = flvData.tag;
                    if (tag.pts >= first_pts) {
                        filteredTags.push(flvData);
                    } else {
                        switch(tag.type) {
                            case FLVTag.AAC_HEADER:
                            case FLVTag.AVC_HEADER:
                            case FLVTag.METADATA:
                            case FLVTag.DISCONTINUITY:
                                tag.pts = tag.dts = first_pts;
                                filteredTags.push(flvData);
                                break;
                            case FLVTag.AVC_NALU:
                                /* only append video tags starting from last keyframe before seek position to avoid playback artifacts
                                 *  rationale of this is that there can be multiple keyframes per segment. if we append all keyframes
                                 *  in NetStream, all of them will be displayed in a row and this will introduce some playback artifacts
                                 *  */
                                if (tag.pts >= keyframe_pts) {
                                    tag.pts = tag.dts = first_pts;
                                    filteredTags.push(flvData);
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            return filteredTags;
        }

        private function _playlistDurationUpdated(event : HLSEvent) : void {
            _playlist_duration = event.duration;
        }

        private function getbuflen(tags : Vector.<FLVData>) : Number {
            var min_pts : Number = 0;
            var max_pts : Number = 0;
            var continuity : int = -1;
            var len : Number = 0;

            for each (var data : FLVData in tags) {
                if (data.continuity != continuity) {
                    len += (max_pts - min_pts);
                    min_pts = data.tag.pts;
                    continuity = data.continuity;
                } else {
                    max_pts = data.tag.pts;
                }
            }
            len += (max_pts - min_pts);
            return len / 1000;
        }

        /** return total buffered duration since seek() call, needed to compute live playlist sliding  */
        private function getTotalBufferedDuration() : Number {
            var len : Number = 0;
            for each (var entry : BufferPTS in _buffer_pts) {
                len += (entry.max - entry.min);
            }
            return len / 1000;
        }

        /*
         *
         * return next tag from queue, using the following priority :
         * smallest continuity
         * then smallest pts
         * then metadata then video then audio tags
         */
        private function shift() : FLVData {
            if (_videoTags.length == 0 && _audioTags.length == 0 && _metaTags.length == 0)
                return null;

            var continuity : int = int.MAX_VALUE;
            // find smallest continuity counter
            if (_metaTags.length) continuity = Math.min(continuity, _metaTags[0].continuity);
            if (_videoTags.length) continuity = Math.min(continuity, _videoTags[0].continuity);
            if (_audioTags.length) continuity = Math.min(continuity, _audioTags[0].continuity);

            var pts : Number = Number.MAX_VALUE;
            // for this continuity counter, find smallest PTS
            if (_metaTags.length && _metaTags[0].continuity == continuity) pts = Math.min(pts, _metaTags[0].tag.pts);
            if (_videoTags.length && _videoTags[0].continuity == continuity) pts = Math.min(pts, _videoTags[0].tag.pts);
            if (_audioTags.length && _audioTags[0].continuity == continuity) pts = Math.min(pts, _audioTags[0].tag.pts);

            // for this continuity counter, this PTS, prioritize tags with the following order : metadata/video/audio
            if (_metaTags.length && _metaTags[0].continuity == continuity && _metaTags[0].tag.pts == pts) return _metaTags.shift();
            if (_videoTags.length && _videoTags[0].continuity == continuity && _videoTags[0].tag.pts == pts) return _videoTags.shift();
            else return _audioTags.shift();
        }

        /*
        private function get min_pos() : Number {
        var min_pos_ : Number = Number.POSITIVE_INFINITY;
        if (_metaTags.length) min_pos_ = Math.min(min_pos_, _metaTags[0].position);
        if (_videoTags.length) min_pos_ = Math.min(min_pos_, _videoTags[0].position);
        if (_audioTags.length) min_pos_ = Math.min(min_pos_, _audioTags[0].position);
        return min_pos_;
        }
         */
        private function get max_pos() : Number {
            var max_pos_ : Number = Number.NEGATIVE_INFINITY;
            if (_metaTags.length) max_pos_ = Math.max(max_pos_, _metaTags[_metaTags.length - 1].position);
            if (_videoTags.length) max_pos_ = Math.max(max_pos_, _videoTags[_videoTags.length - 1].position);
            if (_audioTags.length) max_pos_ = Math.max(max_pos_, _audioTags[_audioTags.length - 1].position);
            return max_pos_;
        }
    }
}

import org.mangui.hls.flv.FLVTag;

class FLVData {
    public var tag : FLVTag;
    public var position : Number;
    public var continuity : int;

    public function FLVData(tag : FLVTag, position : Number, continuity : int) {
        this.tag = tag;
        this.position = position;
        this.continuity = continuity;
    }
}

class BufferPTS {
    public var min : Number;
    public var max : Number;

    public function BufferPTS(min : Number, max : Number) {
        this.min = min;
        this.max = max;
    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.stream {
    import flash.events.TimerEvent;
    import flash.events.Event;
    import flash.utils.Timer;
    import flash.utils.Dictionary;

    import org.mangui.hls.event.HLSMediatime;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.constant.HLSSeekStates;
    import org.mangui.hls.constant.HLSSeekMode;
    import org.mangui.hls.utils.Log;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;

    /*
     * intermediate FLV Tag Buffer
     *  input : FLV tags retrieved from different fragment loaders (video/alt-audio...)
     *  output : provide muxed FLV tags to HLSNetStream
     */
    public class TagBuffer {
        private var _hls : HLS;
        /** Timer used to process FLV tags. **/
        private var _timer : Timer;
        private var _audioTags : Vector.<FLVData>;
        private var _videoTags : Vector.<FLVData>;
        private var _metaTags : Vector.<FLVData>;
        /** playlist duration **/
        private var _playlist_duration : Number = 0;
        /** requested start position **/
        private var _seek_position_requested : Number;
        /** real start position , retrieved from first fragment **/
        private var _seek_position_real : Number;
        /** start position of first injected tag **/
        private var _first_start_position : Number;
        private var _seek_pos_reached : Boolean;
        /** playlist sliding (non null for live playlist) **/
        private var _playlist_sliding_duration : Number;
        /** buffer PTS (indexed by continuity counter)  */
        private var _buffer_pts : Dictionary;

        public function TagBuffer(hls : HLS) {
            _hls = hls;
            flushAll();
            _timer = new Timer(100, 0);
            _timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
            _hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED, _playlistDurationUpdated);
        }

        public function dispose() : void {
            flushAll();
            _hls.removeEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED, _playlistDurationUpdated);
            _timer.stop();
            _hls = null;
            _timer = null;
        }

        public function stop() : void {
            flushAll();
        }

        public function seek(position : Number) : void {
            _seek_position_requested = position;
            flushAll();
            _timer.start();
        }

        public function flushAll() : void {
            _audioTags = new Vector.<FLVData>();
            _videoTags = new Vector.<FLVData>();
            _metaTags = new Vector.<FLVData>();
            _buffer_pts = new Dictionary();
            _seek_pos_reached = false;
            _playlist_sliding_duration = 0;
            _first_start_position = -1;
        }

        public function flushAudio() : void {
            _audioTags = new Vector.<FLVData>();
        }

        public function appendTags(tags : Vector.<FLVTag>, min_pts : Number, max_pts : Number, continuity : int, start_position : Number) : void {
            for each (var tag : FLVTag in tags) {
                var position : Number = start_position + (tag.pts - min_pts) / 1000;
                var tagData : FLVData = new FLVData(tag, position, continuity);
                switch(tag.type) {
                    case FLVTag.AAC_HEADER:
                    case FLVTag.AAC_RAW:
                    case FLVTag.MP3_RAW:
                        _audioTags.push(tagData);
                        break;
                    case FLVTag.AVC_HEADER:
                    case FLVTag.AVC_NALU:
                        _videoTags.push(tagData);
                        break;
                    case FLVTag.DISCONTINUITY:
                    case FLVTag.METADATA:
                        _metaTags.push(tagData);
                        break;
                    default:
                }
            }
            /* check live playlist sliding here :
            _seek_position_real + getTotalBufferedDuration()  should be the start_position
             * /of the new fragment if the playlist was not sliding
            => live playlist sliding is the difference between the new start position  and this previous value */
            if (_hls.seekState == HLSSeekStates.SEEKED) {
                // Log.info("seek pos/getTotalBufferedDuration/start pos:" + _seek_position_requested + "/" + getTotalBufferedDuration() + "/" + start_position);
                _playlist_sliding_duration = (_first_start_position + getTotalBufferedDuration()) - start_position;
            } else {
                if (_first_start_position == -1) {
                    // remember position of first tag injected after seek. it will be used for playlist sliding computation
                    _first_start_position = start_position;
                }
            }
            // update buffer min/max table indexed with continuity counter
            if (_buffer_pts[continuity] == undefined) {
                _buffer_pts[continuity] = new BufferPTS(min_pts, max_pts);
            } else {
                (_buffer_pts[continuity] as BufferPTS).max = max_pts;
            }
            _timer.start();
        }

        /** Return current media position **/
        public function get position() : Number {
            switch(_hls.seekState) {
                case HLSSeekStates.SEEKING:
                    return  _seek_position_requested;
                case HLSSeekStates.SEEKED:
                    /** Relative playback position = (Absolute Position(seek position + play time) - playlist sliding, non null for Live Playlist) **/
                    return _seek_position_real + _hls.stream.time - _playlist_sliding_duration;
                case HLSSeekStates.IDLE:
                default:
                    return 0;
            }
        };

        public function get audioBufferLength() : Number {
            return getbuflen(_audioTags);
        }

        public function get videoBufferLength() : Number {
            return getbuflen(_videoTags);
        }

        public function get bufferLength() : Number {
            return Math.max(audioBufferLength, videoBufferLength);
        }

        /**  Timer **/
        private function _checkBuffer(e : Event) : void {
            // dispatch media time event
            _hls.dispatchEvent(new HLSEvent(HLSEvent.MEDIA_TIME, new HLSMediatime(position, _playlist_duration, _hls.stream.bufferLength, _playlist_sliding_duration)));

            /* only append tags if seek position has been reached, otherwise wait for more tags to come
             * this is to ensure that accurate seeking will work appropriately
             */
            if (_seek_pos_reached || max_pos >= _seek_position_requested) {
                var flvdata : FLVData;
                var data : Vector.<FLVData> = new Vector.<FLVData>();
                while ((flvdata = shift()) != null) {
                    data.push(flvdata);
                }
                if (!_seek_pos_reached) {
                    data = seekFilterTags(data);
                    _seek_pos_reached = true;
                }

                var tags : Vector.<FLVTag> = new Vector.<FLVTag>();
                for each (flvdata in data) {
                    tags.push(flvdata.tag);
                }
                if (tags.length) {
                    (_hls.stream as HLSNetStream).appendTags(tags);
                    Log.debug("appending " + tags.length + " tags");
                }
            }
        }

        /* filter/tweak tags to seek accurately into the stream */
        private function seekFilterTags(tags : Vector.<FLVData>) : Vector.<FLVData> {
            var filteredTags : Vector.<FLVData>=  new Vector.<FLVData>();
            /* PTS of first tag that will be pushed into FLV tag buffer */
            var first_pts : Number;
            /* PTS of last video keyframe before requested seek position */
            var keyframe_pts : Number;
            /* */
            var min_offset : Number = tags[0].position;
            var min_pts : Number = tags[0].tag.pts;
            /*
             *
             *    real seek       requested seek                 Frag
             *     position           position                    End
             *        *------------------*-------------------------
             *        <------------------>
             *             seek_offset
             *
             * real seek position is the start offset of the first received fragment after seek command. (= fragment start offset).
             * seek offset is the diff between the requested seek position and the real seek position
             */

            /* if requested seek position is out of this segment bounds
             * all the segments will be pushed, first pts should be thus be min_pts
             */
            if (_seek_position_requested < min_offset) {
                _seek_position_real = min_offset;
                first_pts = min_pts;
            } else {
                /* if requested position is within segment bounds, determine real seek position depending on seek mode setting */
                if (HLSSettings.seekMode == HLSSeekMode.SEGMENT_SEEK) {
                    _seek_position_real = min_offset;
                    first_pts = min_pts;
                } else {
                    /* accurate or keyframe seeking */
                    /* seek_pts is the requested PTS seek position */
                    var seek_pts : Number = min_pts + 1000 * (_seek_position_requested - min_offset);
                    /* analyze fragment tags and look for PTS of last keyframe before seek position.*/
                    keyframe_pts = min_pts;
                    for each (var flvData : FLVData in tags) {
                        var tag : FLVTag = flvData.tag;
                        // look for last keyframe with pts <= seek_pts
                        if (tag.keyframe == true && tag.pts <= seek_pts && (tag.type == FLVTag.AVC_HEADER || tag.type == FLVTag.AVC_NALU)) {
                            keyframe_pts = tag.pts;
                        }
                    }
                    if (HLSSettings.seekMode == HLSSeekMode.KEYFRAME_SEEK) {
                        _seek_position_real = min_offset + (keyframe_pts - min_pts) / 1000;
                        first_pts = keyframe_pts;
                    } else {
                        // accurate seek, to exact requested position
                        _seek_position_real = _seek_position_requested;
                        first_pts = seek_pts;
                    }
                }
            }
            /* if in segment seeking mode : push all FLV tags */
            if (HLSSettings.seekMode == HLSSeekMode.SEGMENT_SEEK) {
                filteredTags = tags;
            } else {
                /* keyframe / accurate seeking, we need to filter out some FLV tags */
                for each (flvData in tags) {
                    tag = flvData.tag;
                    if (tag.pts >= first_pts) {
                        filteredTags.push(flvData);
                    } else {
                        switch(tag.type) {
                            case FLVTag.AAC_HEADER:
                            case FLVTag.AVC_HEADER:
                            case FLVTag.METADATA:
                            case FLVTag.DISCONTINUITY:
                                tag.pts = tag.dts = first_pts;
                                filteredTags.push(flvData);
                                break;
                            case FLVTag.AVC_NALU:
                                /* only append video tags starting from last keyframe before seek position to avoid playback artifacts
                                 *  rationale of this is that there can be multiple keyframes per segment. if we append all keyframes
                                 *  in NetStream, all of them will be displayed in a row and this will introduce some playback artifacts
                                 *  */
                                if (tag.pts >= keyframe_pts) {
                                    tag.pts = tag.dts = first_pts;
                                    filteredTags.push(flvData);
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            return filteredTags;
        }

        private function _playlistDurationUpdated(event : HLSEvent) : void {
            _playlist_duration = event.duration;
        }

        private function getbuflen(tags : Vector.<FLVData>) : Number {
            var min_pts : Number = 0;
            var max_pts : Number = 0;
            var continuity : int = -1;
            var len : Number = 0;

            for each (var data : FLVData in tags) {
                if (data.continuity != continuity) {
                    len += (max_pts - min_pts);
                    min_pts = data.tag.pts;
                    continuity = data.continuity;
                } else {
                    max_pts = data.tag.pts;
                }
            }
            len += (max_pts - min_pts);
            return len / 1000;
        }

        /** return total buffered duration since seek() call, needed to compute live playlist sliding  */
        private function getTotalBufferedDuration() : Number {
            var len : Number = 0;
            for each (var entry : BufferPTS in _buffer_pts) {
                len += (entry.max - entry.min);
            }
            return len / 1000;
        }

        /*
         *
         * return next tag from queue, using the following priority :
         * smallest continuity
         * then smallest pts
         * then metadata then video then audio tags
         */
        private function shift() : FLVData {
            if (_videoTags.length == 0 && _audioTags.length == 0 && _metaTags.length == 0)
                return null;

            var continuity : int = int.MAX_VALUE;
            // find smallest continuity counter
            if (_metaTags.length) continuity = Math.min(continuity, _metaTags[0].continuity);
            if (_videoTags.length) continuity = Math.min(continuity, _videoTags[0].continuity);
            if (_audioTags.length) continuity = Math.min(continuity, _audioTags[0].continuity);

            var pts : Number = Number.MAX_VALUE;
            // for this continuity counter, find smallest PTS
            if (_metaTags.length && _metaTags[0].continuity == continuity) pts = Math.min(pts, _metaTags[0].tag.pts);
            if (_videoTags.length && _videoTags[0].continuity == continuity) pts = Math.min(pts, _videoTags[0].tag.pts);
            if (_audioTags.length && _audioTags[0].continuity == continuity) pts = Math.min(pts, _audioTags[0].tag.pts);

            // for this continuity counter, this PTS, prioritize tags with the following order : metadata/video/audio
            if (_metaTags.length && _metaTags[0].continuity == continuity && _metaTags[0].tag.pts == pts) return _metaTags.shift();
            if (_videoTags.length && _videoTags[0].continuity == continuity && _videoTags[0].tag.pts == pts) return _videoTags.shift();
            else return _audioTags.shift();
        }

        /*
        private function get min_pos() : Number {
        var min_pos_ : Number = Number.POSITIVE_INFINITY;
        if (_metaTags.length) min_pos_ = Math.min(min_pos_, _metaTags[0].position);
        if (_videoTags.length) min_pos_ = Math.min(min_pos_, _videoTags[0].position);
        if (_audioTags.length) min_pos_ = Math.min(min_pos_, _audioTags[0].position);
        return min_pos_;
        }
         */
        private function get max_pos() : Number {
            var max_pos_ : Number = Number.NEGATIVE_INFINITY;
            if (_metaTags.length) max_pos_ = Math.max(max_pos_, _metaTags[_metaTags.length - 1].position);
            if (_videoTags.length) max_pos_ = Math.max(max_pos_, _videoTags[_videoTags.length - 1].position);
            if (_audioTags.length) max_pos_ = Math.max(max_pos_, _audioTags[_audioTags.length - 1].position);
            return max_pos_;
        }
    }
}

import org.mangui.hls.flv.FLVTag;

class FLVData {
    public var tag : FLVTag;
    public var position : Number;
    public var continuity : int;

    public function FLVData(tag : FLVTag, position : Number, continuity : int) {
        this.tag = tag;
        this.position = position;
        this.continuity = continuity;
    }
}

class BufferPTS {
    public var min : Number;
    public var max : Number;

    public function BufferPTS(min : Number, max : Number) {
        this.min = min;
        this.max = max;
    }
}

remove unused var

remove unused var


package classes
{
	/**
	 AGORA - an interactive and web-based argument mapping tool that stimulates reasoning,
	 reflection, critique, deliberation, and creativity in individual argument construction
	 and in collaborative or adversarial settings.
	 Copyright (C) 2011 Georgia Institute of Technology

	 This program is free software: you can redistribute it and/or modify
	 it under the terms of the GNU Affero General Public License as
	 published by the Free Software Foundation, either version 3 of the
	 License, or (at your option) any later version.

	 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
	 GNU Affero General Public License for more details.

	 You should have received a copy of the GNU Affero General Public License
	 along with this program.  If not, see <http://www.gnu.org/licenses/>.

	 */
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;

	import mx.controls.Alert;

	import org.osmf.layout.AbsoluteLayoutFacet;

	public class Language
	{
		//I will set the language to either GERMAN, RUSSIAN or ENGLISH
		//based on this, you could read the values into the variables
		//by default, the language is EN-US

		public static const GERMAN:String = "GER";
		public static const ENGLISH:String = "EN-US";
		public static const RUSSIAN:String = "RUS";
		public static var language:String = "EN-US";

		public static var xml:XML;
		private static var ready:Boolean=false;

		public static function init():void
		{
			[Embed(source="../../../translation.xml", mimeType="application/octet-stream")]
			const MyData:Class;
			var byteArray:ByteArray = new MyData() as ByteArray;
			var x:XML = new XML(byteArray.readUTFBytes(byteArray.length));
			xml = x;
			ready=true;
		}

		/**The key function. Use this to look up a label from the translation document according to the set language.*/

		public static function lookup(label:String):String{
			if(!ready){
				init();
			}
			trace("Now looking up:" + label);
			var lbl:XMLList = xml.descendants(label);
			var lang:XMLList = lbl.descendants(language);
			var output:String = lang.attribute("text");
			if(!output){
				output = "error | ошибка | fehler --- There was a problem getting the text for this item. The label was: " + label;
			}
			trace("Output is: " + output);
			return output;
		}

	}
}

package classes
{
	/**
	 AGORA - an interactive and web-based argument mapping tool that stimulates reasoning,
	 reflection, critique, deliberation, and creativity in individual argument construction
	 and in collaborative or adversarial settings.
	 Copyright (C) 2011 Georgia Institute of Technology

	 This program is free software: you can redistribute it and/or modify
	 it under the terms of the GNU Affero General Public License as
	 published by the Free Software Foundation, either version 3 of the
	 License, or (at your option) any later version.

	 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
	 GNU Affero General Public License for more details.

	 You should have received a copy of the GNU Affero General Public License
	 along with this program.  If not, see <http://www.gnu.org/licenses/>.

	 */
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;

	import mx.controls.Alert;

	import org.osmf.layout.AbsoluteLayoutFacet;

	public class Language
	{
		//I will set the language to either GERMAN, RUSSIAN or ENGLISH
		//based on this, you could read the values into the variables
		//by default, the language is EN-US

		public static const GERMAN:String = "GER";
		public static const ENGLISH:String = "EN-US";
		public static const RUSSIAN:String = "RUS";
		public static var language:String = GERMAN;

		public static var xml:XML;
		private static var ready:Boolean=false;

		public static function init():void
		{
			[Embed(source="../../../translation.xml", mimeType="application/octet-stream")]
			const MyData:Class;
			var byteArray:ByteArray = new MyData() as ByteArray;
			var x:XML = new XML(byteArray.readUTFBytes(byteArray.length));
			xml = x;
			ready=true;
		}

		/**The key function. Use this to look up a label from the translation document according to the set language.*/

		public static function lookup(label:String):String{
			if(!ready){
				init();
			}
			trace("Now looking up:" + label);
			var lbl:XMLList = xml.descendants(label);
			var lang:XMLList = lbl.descendants(language);
			var output:String = lang.attribute("text");
			if(!output){
				output = "error | ошибка | fehler --- There was a problem getting the text for this item. The label was: " + label;
			}
			trace("Output is: " + output);
			return output;
		}

	}
}

Change current language to German

Change current language to German


package
{
	import com.jonas.net.Multipart;

	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.ByteArray;

	[SWF(width="600", height="400", frameRate="30", backgroundColor="#CCCCCC")]
	public class MultipartTest extends Sprite
	{
		[Embed(source="icon480.png")]
		public var c:Class;
		public var b:Bitmap;
		public var t:TextField;

		private var url:String = "http://labs.positronic.fr/flash/multipart/upload.php";

		public function MultipartTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			var s:Sprite = new Sprite();
			s.addEventListener(MouseEvent.CLICK, onClick);
			s.buttonMode = true;
			s.y = 20;
			addChild(s);

			b = new c() as Bitmap;
			b.scaleX = b.scaleY = 0.5;
			s.addChild(b);

			t = new TextField();
			t.width = 400;
			t.multiline = t.wordWrap = true;
			t.autoSize = TextFieldAutoSize.LEFT;
			t.x = s.width+20;
			t.y = s.y;
			addChild(t);
			t.text = "Click to upload";

			if(loaderInfo.parameters["url"] != null){
				url = loaderInfo.parameters["url"];
			}
		}

		protected function onClick(event:Event):void
		{
			t.text = "loading...";

			var jpg:JPEGEncoder = new JPEGEncoder(80);

			var test:ByteArray = new ByteArray();
			test.writeMultiByte("Hello", "ascii");

			var form:Multipart = new Multipart(url);

			// add fields
			form.addField("field1", "hello");
			form.addField("field2", "world");

			// add files
			form.addFile("file1", test, "text/plain", "test.txt");
			form.addFile("file2", jpg.encode(b.bitmapData), "application/octet-stream", "test.jpg");

			//navigateToURL(form.request);

			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onComplete);
			try {
				loader.load(form.request);
			} catch (error: Error) {
				t.text = "Unable to load requested document : "+error.message;
			}
		}

		protected function onComplete (e: Event):void {
			t.text = e.target["data"];
		}
	}
}

package
{
	import com.jonas.net.Multipart;

	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.ByteArray;

	[SWF(width="600", height="400", frameRate="30", backgroundColor="#CCCCCC")]
	public class MultipartTest extends Sprite
	{
		[Embed(source="icon480.png")]
		public var c:Class;
		public var b:Bitmap;
		public var t:TextField;

		private var url:String = "http://labs.positronic.fr/flash/multipart/upload.php";

		public function MultipartTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			var s:Sprite = new Sprite();
			s.addEventListener(MouseEvent.CLICK, onClick);
			s.buttonMode = true;
			s.y = 20;
			addChild(s);

			b = new c() as Bitmap;
			b.scaleX = b.scaleY = 0.5;
			s.addChild(b);

			t = new TextField();
			t.width = 400;
			t.multiline = t.wordWrap = true;
			t.autoSize = TextFieldAutoSize.LEFT;
			t.x = s.width+20;
			t.y = s.y;
			addChild(t);
			t.text = "Click to upload";

			if(loaderInfo.parameters["url"] != null){
				url = loaderInfo.parameters["url"];
			}
		}

		protected function onClick(event:Event):void
		{
			t.text = "loading...";

			var jpg:JPEGEncoder = new JPEGEncoder(80);

			var test:ByteArray = new ByteArray();
			test.writeMultiByte("Hello", "ascii");

			var form:Multipart = new Multipart(url);

			// add fields
			form.addField("field1", "hello");
			form.addField("field2", "world");

			// add files
			form.addFile("file1", test, "text/plain", "test.txt");
			form.addFile("file2", jpg.encode(b.bitmapData), "image/jpeg", "test.jpg");

			var loader:URLLoader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onComplete);
			try {
				loader.load(form.request);
			} catch (error: Error) {
				t.text = "Unable to load requested document : "+error.message;
			}
		}

		protected function onComplete (e: Event):void {
			t.text = e.target["data"];
		}
	}
}

change mime type

change mime type