/* -*- java-mode -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is [Open Source Virtual Machine.].
 *
 * The Initial Developer of the Original Code is
 * Adobe System Incorporated.
 * Portions created by the Initial Developer are Copyright (C) 2004-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adobe AS3 Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/* Generate peephole optimization state tables from a description.

     peepspec ::= peep*
     peep     ::= pattern guard? action
     pattern  ::= "pattern" ident (";" ident)* eol
     guard    ::= expr
     action   ::= expr (";" expr)
     expr     ::= token+
     token    ::= any C++ token valid in an expression
                | $integer.integer
     ident    ::= C++ identifier
     eol      ::= newline or end-of-file

   The idea is that the identifiers in the pattern are opcode values that
   are resolved at compile time and which drive the peephole optimizer
   state machine.  The guard is evaluated in case there are side conditions
   apart from the opcode values; it must be a C++ expression.  The action
   represents individual instruction words that replace the instructions
   matched by the pattern.

   The syntax $n.m refers to the m'th word of the n'th matched instruction.

   The guard will normally be evaluated zero or one times, but it would
   be good not to depend on that.

   For example (this one has only one instruction in the action)

     pattern  OP_getlocal ; OP_getlocal ; OP_getlocal
     guard    $0.1 < 1024 && $1.1 < 1024 && $2.1 < 1024
     action   OP_get2locals ; (($2.1 << 20) | ($1.1 << 10) | $0.1)

   Continuation lines are allowed, they start with '*' in the first column.

   Comment lines start with //.  Comment lines and blank lines are ignored
   everywhere before continuation lines are resolved.

   Restrictions:

   - The action cannot introduce more words than the instruction originally
     occupied  (it would not be hard to lift this restriction).

   - Lookupswitch may not appear in these patterns

   - Any PC-relative branch instruction must be the last in the pattern as
     well as in the replacement code

 */

package peephole
{
    import avmplus.*;

    class Pattern
    {
	var P, G, A;
	function Pattern(P, G, A) {
	    this.P = P;
	    this.G = G;
	    this.A = A;
	}
    }

    function assert(cond) {
        if (!cond)
            throw new Error("Assertion failed");
    }

    function cleanup(lines) {
	var j = 0;
	var first = true;
	for ( var i=0, limit=lines.length ; i < limit ; i++ ) {
	    if (/^\s*$/.test(lines[i]))
		continue;
	    if (/^\s*\/\//.test(lines[i]))
		continue;
	    assert(/^(\*|pattern\s+|guard\s+|action\s+)/.test(lines[i]));
	    if (lines[i].charAt(0) == '*') {
		assert(!first);
		lines[j] += lines[i].substring(1);
		continue;
	    }
	    lines[j++] = lines[i];
	    first = false;
	}
	lines.length = j;
	return lines;
    }

    function patterns(lines) {
	var output = [];
	var P = null;
	var G = null;
	var A = null;
	var res;
	for ( var i=0, limit=lines.length ; i < limit ; i++ ) {
	    var L = lines[i];
	    if (P == null) {
		res = /^pattern\s+(.*)$/.exec(L);
		assert(res != null);
		P = res[1].split(/\s*;\s*/);
		continue;
	    }

	    if (G == null) {
		res = /^guard\s+(.*)$/.exec(L);
		if (res != null) {
		    G = res[1];
		    continue;
		}
	    }

	    if (A == null) {
		res = /^action\s+(.*)$/.exec(L);
		assert(res != null);
		A = res[1].split(/\s*;\s*/);
	    }

	    assert( P != null && A != null );
	    output.push(new Pattern(P, G, A));
	    P = G = A = null;
	}
	return output;
    }

    function printT(x) {
	var s = "{";
	for ( var i in x ) {
	    if (i == "toString") continue;
	    s += i + ": " + x[i] + ", ";
	}
	s += "}";
	return s;
    }

    function build(patterns) {
	var T = { toString: function () { return printT(this); } };
	for ( var i=0, ilimit=patterns.length ; i < ilimit ; i++ ) {
	    var p = patterns[i];
	    var container = T;
	    for ( var j=0, jlimit=p.P.length ; j < jlimit ; j++ ) {
		if (!(p.P[j] in container))
		    container[p.P[j]] = { toString: function () { return printT(this); } };
		container = container[p.P[j]];
	    }
	    if (!("actions" in container))
		container.actions = [];
	    container.actions.push(p);
	}
	return T;
    }

    function generate(toplevel) {
	var initial = [];
	for ( var n in toplevel ) {
	    if (n == "actions" || n == "toString")
		continue;
	    initial.push({name: n, state: expand(toplevel[n])});
	}
	return initial;
    }

    function display(container, sofar) {
	if (container.actions) {
	    var s = "";
	    for ( var p=sofar ; p != null ; p=p.next )
		s = s + p.name + " ";
	    print(s);
	}
	for (var n in container) {
	    if (n == "actions" || n == "toString")
		continue;
	    display(container[n], { name: n, next: sofar });
	}
    }

    if (System.argv.length != 2) {
        print("Usage: peephole inputfile outputfile");
        System.exit(1);
    }

    var T = build(patterns(cleanup(File.read(System.argv[0]).split("\n"))));
    print(String(T));
    display(T, null);
}

/* -*- java-mode -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is [Open Source Virtual Machine.].
 *
 * The Initial Developer of the Original Code is
 * Adobe System Incorporated.
 * Portions created by the Initial Developer are Copyright (C) 2004-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adobe AS3 Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/* Generate peephole optimization state tables from a description.

     peepspec ::= peep*
     peep     ::= pattern guard? action
     pattern  ::= "pattern" ident (";" ident)* eol
     guard    ::= expr
     action   ::= expr (";" expr)
     expr     ::= token+
     token    ::= any C++ token valid in an expression
                | $integer.integer
     ident    ::= C++ identifier
     eol      ::= newline or end-of-file

   Identifiers in the pattern must be opcode names; these are resolved at
   compile time and the peephole optimizer state machine uses them to
   transition from one state to another.  The guard is evaluated in case
   there are side conditions apart from the opcode values; the guard must
   be a C++ expression.  The action represents individual instruction
   words that replace the instructions matched by the pattern; each word
   should be a C++ expression.

   The syntax $n.m refers to the m'th word of the n'th matched instruction.
   It can be used in the guard and action.

   The guard will be evaluated zero or one times per match.  (The current
   implementation only invokes a guard after longer matches have failed,
   so zero times is typical - you can't track state machine progress by
   having side effects in the guard.)

   For example (this one has only one instruction in the action):

     pattern  OP_getlocal ; OP_getlocal ; OP_getlocal
     guard    $0.1 < 1024 && $1.1 < 1024 && $2.1 < 1024
     action   OP_get3locals ; ($2.1 << 20) | ($1.1 << 10) | $0.1

   Continuation lines are allowed, they start with '*' in the first column.
   The continuation line, less its '*', is pasted onto the end of the
   preceding line.

   Comment lines start with // (possibly preceded by blanks).  Comment lines
   and blank lines are ignored everywhere before continuation lines are
   resolved.  Comments can't follow patterns, guards, or actions on the
   same line.

   Restrictions (not checked by this program):

   - The action cannot introduce more words than the instruction originally
     occupied  (it would not be hard to lift this restriction).

   - Lookupswitch must not appear in these patterns.

   - Any PC-relative branch instruction must be the last in the pattern or
     in the replacement code.

 */

package peephole
{
    import avmplus.*;

    class Pattern
    {
	var P, G, A;
	function Pattern(P, G, A) {
	    this.P = P;
	    this.G = G;
	    this.A = A;
	}
    }

    function assert(cond) {
        if (!cond)
            throw new Error("Assertion failed");
    }

    function preprocess(lines) {
	var j = 0;
	var first = true;
	for ( var i=0, limit=lines.length ; i < limit ; i++ ) {
	    if (/^\s*$/.test(lines[i]))
		continue;
	    if (/^\s*\/\//.test(lines[i]))
		continue;
	    assert(/^(\*|pattern\s+|guard\s+|action\s+)/.test(lines[i]));
	    if (lines[i].charAt(0) == '*') {
		assert(!first);
		lines[j] += lines[i].substring(1);
		continue;
	    }
	    lines[j++] = lines[i];
	    first = false;
	}
	lines.length = j;
	return lines;
    }

    function parse(lines) {
	var output = [];
	var P = null;
	var G = null;
	var A = null;
	var res;
	for ( var i=0, limit=lines.length ; i < limit ; i++ ) {
	    var L = lines[i].replace(/\$([0-9]+)\.([0-9]+)/g, "I[$1][$2]");
	    if (P == null) {
		res = /^pattern\s+(.*)$/.exec(L);
		assert(res != null);
		P = res[1].split(/\s*;\s*/);
		continue;
	    }

	    if (G == null) {
		res = /^guard\s+(.*)$/.exec(L);
		if (res != null) {
		    G = res[1];
		    continue;
		}
	    }

	    if (A == null) {
		res = /^action\s+(.*)$/.exec(L);
		assert(res != null);
		A = res[1].split(/\s*;\s*/);
	    }

	    assert( P != null && A != null );
	    output.push(new Pattern(P, G, A));
	    P = G = A = null;
	}
	return output;
    }

    function build(patterns) {
	var T = {};
	for ( var i=0, ilimit=patterns.length ; i < ilimit ; i++ ) {
	    var p = patterns[i];
	    var container = T;
	    for ( var j=0, jlimit=p.P.length ; j < jlimit ; j++ ) {
		if (!(p.P[j] in container))
		    container[p.P[j]] = {};
		container = container[p.P[j]];
	    }
	    if (!("actions" in container))
		container.actions = [];
	    container.actions.push(p);
	}
	return T;
    }

    function generate(toplevel) {
	var initial = [];
	for ( var n in toplevel ) {
	    if (n == "actions" || n == "toString")
		continue;
	    initial.push({name: n, state: expand(toplevel[n])});
	}
	return initial;
    }

    function display(container, sofar) {
	if (container.actions) {
	    var s = "";
	    for ( var p=sofar ; p != null ; p=p.next )
		s = s + p.name + " ";
	    print(s);
	}
	for (var n in container) {
	    if (n == "actions" || n == "toString")
		continue;
	    display(container[n], { name: n, next: sofar });
	}
    }

    if (System.argv.length != 2) {
        print("Usage: peephole inputfile outputfile");
        System.exit(1);
    }

    var T = build(parse(preprocess(File.read(System.argv[0]).split("\n"))));
    display(T, null);
}

Work in progress

Work in progress


////////////////////////////////////////////////////////////////////////////////
//
//  tandem. explore your world.
//  Copyright (c) 2007–2008 Daniel Gasienica (daniel@gasienica.ch)
//
//  tandem 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.
//
//  tandem 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 tandem. If not, see <http://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////////////
package tandem.model
{

import com.adobe.webapis.flickr.FlickrService;
import com.adobe.webapis.flickr.User;

import flash.events.EventDispatcher;

public class ApplicationModel extends EventDispatcher
{
    //--------------------------------------------------------------------------
    //
    //  Class Constants
    //
    //--------------------------------------------------------------------------

    // If you don't have this file, please complete settings-sample.as and rename it
    include "../settings.as"

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    public var service : FlickrService
    public var photos : Array = []
    public var user : User = new User()

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    /**
     *  Constructor.
     */
    public function ApplicationModel()
    {
        if ( instance != null )
            throw new Error( "Hey, I'm a singleton, please don't instantiate me!" )

        instance = this
    }

    //--------------------------------------------------------------------------
    //
    //  Method: Access
    //
    //--------------------------------------------------------------------------

    private static var instance : ApplicationModel

    public static function getInstance() : ApplicationModel
    {
        if ( instance == null )
            instance = new ApplicationModel()

            return instance
    }
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  tandem. explore your world.
//  Copyright (c) 2007–2008 Daniel Gasienica (daniel@gasienica.ch)
//
//  tandem 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.
//
//  tandem 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 tandem. If not, see <http://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////////////
package tandem.model
{

import com.adobe.webapis.flickr.FlickrService;
import com.adobe.webapis.flickr.User;

import flash.events.EventDispatcher;

public class ApplicationModel extends EventDispatcher
{
    //--------------------------------------------------------------------------
    //
    //  Class Constants
    //
    //--------------------------------------------------------------------------

    // If you don't have this file, please complete settings-sample.as and rename it
    include "../settings.as"

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    public var service : FlickrService
    public var photos : Array = []
    public var user : User = new User()

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------
    /**
     *  Constructor.
     */
    public function ApplicationModel(enforcer:SingletonEnforcer)
    {
        if (!enforcer)
            throw new Error("Can’t instantiate singleton directly." )

        instance = this
    }

    //--------------------------------------------------------------------------
    //
    //  Method: Access
    //
    //--------------------------------------------------------------------------

    private static var instance : ApplicationModel

    public static function getInstance() : ApplicationModel
    {
        if ( instance == null )
            instance = new ApplicationModel(new SingletonEnforcer())

            return instance
    }
}

}

class SingletonEnforcer {}

Fix singleton.

Fix singleton.


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You 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 org.apache.flex.net.dataConverters
{
	import flash.events.Event;
	import flash.events.IEventDispatcher;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.data.ICollection;
	import org.apache.flex.events.EventDispatcher;
    import org.apache.flex.net.IInputParser;
    import org.apache.flex.net.IItemConverter;

    /**
     *  The LazyCollection class implements a collection
     *  whose items require conversion from a source data format
     *  to some other data type.  For example, converting
     *  SOAP or JSON to ActionScript data classes.
     *  The Flex SDK used to convert all of the data items
     *  when the source data arrived, which, for very large data sets
     *  or complex data classes, could lock up the user interface.
     *  The lazy collection converts items as they are fetched from
     *  the collection, resulting in significant performance savings
     *  in many cases.  Note that, if you need to compute a summary of
     *  data in the collection when the source data arrives, the
     *  computation can still lock up the user interface as you will
     *  have to visit and convert every data item.  Of course, it is
     *  possible to compute that summary in a worker or pseudo-thread.
     *  The LazyCollection class is designed to be a bead that attaches
     *  to a data retrieval strand that dispatches an Event.COMPLETE and
     *  has a "data" property that gets passed to the input parser.
     *
     *  This LazyCollection does not support adding/removing items from
     *  the collection or sending data back to the source.  Subclasses
     *  have that additional functionality.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class LazyCollection extends EventDispatcher implements IBead, ICollection
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function LazyCollection()
		{
			super();
		}

		private var _inputParser:IInputParser;

        /**
         *  A lazy collection uses an IInputParser to convert the source data items
         *  into an array of data items.  This is required in order to determine
         *  the length of the collection.  This conversion happens as the source
         *  data arrives so it needs to be fast to avoid locking up the UI.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get inputParser():IInputParser
		{
			return _inputParser;
		}

        /**
         *  @private
         */
		public function set inputParser(value:IInputParser):void
		{
			if (_inputParser != value)
			{
                _inputParser = value;
				dispatchEvent(new Event("inputParserChanged"));
			}
		}

        private var _itemConverter:IItemConverter;

        /**
         *  A lazy collection uses an IItemConverter to convert the source data items
         *  into the desired data type.  The converter is only called as items
         *  are fetched from the collection.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get itemConverter():IItemConverter
        {
            return _itemConverter;
        }

        /**
         *  @private
         */
        public function set itemConverter(value:IItemConverter):void
        {
            if (_itemConverter != value)
            {
                _itemConverter = value;
                dispatchEvent(new Event("itemConverterChanged"));
            }
        }

        private var _id:String;

        /**
         *  @copy org.apache.flex.core.UIBase#id
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function get id():String
		{
			return _id;
		}

        /**
         *  @private
         */
		public function set id(value:String):void
		{
			if (_id != value)
			{
				_id = value;
				dispatchEvent(new Event("idChanged"));
			}
		}

        private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.UIBase#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function set strand(value:IStrand):void
        {
            _strand = value;
            IEventDispatcher(_strand).addEventListener(Event.COMPLETE, completeHandler);
        }

        /**
         *  The array of raw data needing conversion.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected var rawData:Array;

        /**
         *  The array of desired data types. This array is sparse and
         *  unconverted items are therefore undefined in the array.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected var data:Array;

        private function completeHandler(event:Event):void
        {
            rawData = inputParser.parseItems(_strand["data"]);
            data = new Array(rawData.length);
        }

        /**
         *  Fetches an item from the collection, converting it first if necessary.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function getItemAt(index:int):Object
        {
            if (data[index] == undefined)
            {
                data[index] = itemConverter.convertItem(rawData[index]);
            }
            return data[index];
        }
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You 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 org.apache.flex.net.dataConverters
{
	import flash.events.Event;
	import flash.events.IEventDispatcher;

	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.data.ICollection;
	import org.apache.flex.events.EventDispatcher;
    import org.apache.flex.net.IInputParser;
    import org.apache.flex.net.IItemConverter;

    /**
     *  The LazyCollection class implements a collection
     *  whose items require conversion from a source data format
     *  to some other data type.  For example, converting
     *  SOAP or JSON to ActionScript data classes.
     *  The Flex SDK used to convert all of the data items
     *  when the source data arrived, which, for very large data sets
     *  or complex data classes, could lock up the user interface.
     *  The lazy collection converts items as they are fetched from
     *  the collection, resulting in significant performance savings
     *  in many cases.  Note that, if you need to compute a summary of
     *  data in the collection when the source data arrives, the
     *  computation can still lock up the user interface as you will
     *  have to visit and convert every data item.  Of course, it is
     *  possible to compute that summary in a worker or pseudo-thread.
     *  The LazyCollection class is designed to be a bead that attaches
     *  to a data retrieval strand that dispatches an Event.COMPLETE and
     *  has a "data" property that gets passed to the input parser.
     *
     *  This LazyCollection does not support adding/removing items from
     *  the collection or sending data back to the source.  Subclasses
     *  have that additional functionality.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class LazyCollection extends EventDispatcher implements IBead, ICollection
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function LazyCollection()
		{
			super();
		}

		private var _inputParser:IInputParser;

        /**
         *  A lazy collection uses an IInputParser to convert the source data items
         *  into an array of data items.  This is required in order to determine
         *  the length of the collection.  This conversion happens as the source
         *  data arrives so it needs to be fast to avoid locking up the UI.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get inputParser():IInputParser
		{
			return _inputParser;
		}

        /**
         *  @private
         */
		public function set inputParser(value:IInputParser):void
		{
			if (_inputParser != value)
			{
                _inputParser = value;
				dispatchEvent(new Event("inputParserChanged"));
			}
		}

        private var _itemConverter:IItemConverter;

        /**
         *  A lazy collection uses an IItemConverter to convert the source data items
         *  into the desired data type.  The converter is only called as items
         *  are fetched from the collection.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get itemConverter():IItemConverter
        {
            return _itemConverter;
        }

        /**
         *  @private
         */
        public function set itemConverter(value:IItemConverter):void
        {
            if (_itemConverter != value)
            {
                _itemConverter = value;
                dispatchEvent(new Event("itemConverterChanged"));
            }
        }

        private var _id:String;

        /**
         *  @copy org.apache.flex.core.UIBase#id
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function get id():String
		{
			return _id;
		}

        /**
         *  @private
         */
		public function set id(value:String):void
		{
			if (_id != value)
			{
				_id = value;
				dispatchEvent(new Event("idChanged"));
			}
		}

        private var _strand:IStrand;

        /**
         *  @copy org.apache.flex.core.UIBase#strand
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function set strand(value:IStrand):void
        {
            _strand = value;
            IEventDispatcher(_strand).addEventListener(Event.COMPLETE, completeHandler);
        }

        /**
         *  The array of raw data needing conversion.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected var rawData:Array;

        /**
         *  The array of desired data types. This array is sparse and
         *  unconverted items are therefore undefined in the array.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        protected var data:Array;

        private function completeHandler(event:Event):void
        {
            rawData = inputParser.parseItems(_strand["data"]);
            data = new Array(rawData.length);
        }

        /**
         *  Fetches an item from the collection, converting it first if necessary.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function getItemAt(index:int):Object
        {
            if (data[index] == undefined)
            {
                data[index] = itemConverter.convertItem(rawData[index]);
            }
            return data[index];
        }

        /**
         *  The number of items.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get length():int
        {
            return rawData ? rawData.length : 0;
        }

	}
}

add length prop

add length prop


/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the at.matthew.httpstreaming package.
 *
 * The Initial Developer of the Original Code is
 * Matthew Kaufman.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

package org.denivip.osmf.net.httpstreaming.hls
{
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;

	import com.hurlant.util.Hex;
	import org.denivip.osmf.utility.decrypt.AES;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;

	[Event(name="notifySegmentDuration", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]
	[Event(name="notifyTimeBias", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]

	public class HTTPStreamingMP2TSFileHandler extends HTTPStreamingFileHandlerBase
	{
		private var _syncFound:Boolean;
		private var _pmtPID:uint;
		private var _audioPID:uint;
		private var _videoPID:uint;
		private var _mp3AudioPID:uint;
		private var _audioPES:HTTPStreamingMP2PESAudio;
		private var _videoPES:HTTPStreamingMP2PESVideo;
		private var _mp3audioPES:HTTPStreamingMp3Audio2ToPESAudio;

		private var _cachedOutputBytes:ByteArray;
		private var alternatingYieldCounter:int = 0;

		private var _key:HTTPStreamingM3U8IndexKey = null;
		private var _iv:ByteArray = null;
		private var _decryptBuffer:ByteArray = new ByteArray;

		// AES-128 specific variables
		private var _decryptAES:AES = null;

		public function HTTPStreamingMP2TSFileHandler()
		{
			_audioPES = new HTTPStreamingMP2PESAudio;
			_videoPES = new HTTPStreamingMP2PESVideo;
			_mp3audioPES = new HTTPStreamingMp3Audio2ToPESAudio;
		}

		override public function beginProcessFile(seek:Boolean, seekTime:Number):void
		{
			_syncFound = false;
		}

		override public function get inputBytesNeeded():Number
		{
			return _syncFound ? 187 : 1;
		}

		override public function processFileSegment(input:IDataInput):ByteArray
		{
			var bytesAvailableStart:uint = input.bytesAvailable;
			var output:ByteArray;

			if (_cachedOutputBytes !== null) {
				output = _cachedOutputBytes;
				_cachedOutputBytes = null;
			}
			else {
				output = new ByteArray();
			}

			while (true) {
				if(!_syncFound)
				{
					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 16) {
								if (_decryptBuffer.bytesAvailable < 1) {
									break;
								}
							} else {
								if (!decryptToBuffer(input, 16)) {
									break;
								}
							}
							if (_decryptBuffer.readByte() == 0x47) {
								_syncFound = true;
							}
						}
					} else {
						if(input.bytesAvailable < 1)
							break;

						if(input.readByte() == 0x47)
							_syncFound = true;
					}
				}
				else
				{
					_syncFound = false;
					var packet:ByteArray = new ByteArray();

					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 176) {
								if (_decryptBuffer.bytesAvailable < 187) {
									break;
								}
							} else {
								var bytesLeft:uint = input.bytesAvailable - 176;
								if (bytesLeft > 0 && bytesLeft < 15) {
									if (!decryptToBuffer(input, input.bytesAvailable)) {
										break;
									}
								} else {
									if (!decryptToBuffer(input, 176)) {
										break;
									}
								}
							}
							_decryptBuffer.readBytes(packet, 0, 187);
						}
					} else {
						if(input.bytesAvailable < 187)
							break;

						input.readBytes(packet, 0, 187);
					}

					var result:ByteArray = processPacket(packet);
					if (result !== null) {
						output.writeBytes(result);
					}

					if (bytesAvailableStart - input.bytesAvailable > 10000) {
						alternatingYieldCounter = (alternatingYieldCounter + 1) & 0x03;
						if (alternatingYieldCounter /*& 0x01 === 1*/) {
							_cachedOutputBytes = output;
							return null;
						}
						break;
					}
				}
			}
			output.position = 0;

			return output.length === 0 ? null : output;
		}

		private function decryptToBuffer(input:IDataInput, blockSize:int):Boolean{
			if (_key) {
				// Clear buffer
				if (_decryptBuffer.bytesAvailable == 0) {
					_decryptBuffer.clear();
				}

				if (_key.type == "AES-128" && blockSize % 16 == 0 && _key.key) {
					if (!_decryptAES) {
						_decryptAES = new AES(_key.key);
						_decryptAES.pad = "none";
						_decryptAES.iv = _iv;
					}

					// Save buffer position
					var currentPosition:uint = _decryptBuffer.position;
					_decryptBuffer.position += _decryptBuffer.bytesAvailable;

					// Save block to decrypt
					var decrypt:ByteArray = new ByteArray;
					input.readBytes(decrypt, 0, blockSize);
					// Save new IV from ciphertext
					var newIv:ByteArray = new ByteArray;
					decrypt.position += (decrypt.bytesAvailable-16);
					decrypt.readBytes(newIv, 0, 16);
					decrypt.position = 0;
					// Decrypt
					if (input.bytesAvailable == 0) {
						_decryptAES.pad = "pkcs7";
					}
					_decryptAES.decrypt(decrypt);
					decrypt.position = 0;
					// Write into buffer
					_decryptBuffer.writeBytes(decrypt);
					_decryptBuffer.position = currentPosition;
					// Update AES IV
					_decryptAES.iv = newIv;

					return true;
				}
			}

			return false;
		}

		override public function endProcessFile(input:IDataInput):ByteArray
		{
			_decryptBuffer.clear();
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
			return null;
		}

		public function resetCache():void{
			_cachedOutputBytes = null;
			alternatingYieldCounter = 0;
			_decryptBuffer.clear();
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
		}

		public function set initialOffset(offset:Number):void{
			offset *= 1000; // convert to ms
			_videoPES.initialTimestamp = offset;
			_audioPES.initialTimestamp = offset;
			_mp3audioPES.initialTimestamp = offset;
		}

		public function set key(key:HTTPStreamingM3U8IndexKey):void {
			_key = key;
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
		}

		public function set iv(iv:String):void {
			if (iv) {
				_iv = Hex.toArray(iv);
			}
		}

		private function processPacket(packet:ByteArray):ByteArray
		{
			// decode rest of transport stream prefix (after the 0x47 flag byte)

			// top of second byte
			var value:uint = packet.readUnsignedByte();

			//var tei:Boolean = Boolean(value & 0x80);	// error indicator
			var pusi:Boolean = Boolean(value & 0x40);	// payload unit start indication
			//var tpri:Boolean = Boolean(value & 0x20);	// transport priority indication

			// bottom of second byte and all of third
			value <<= 8;
			value += packet.readUnsignedByte();

			var pid:uint = value & 0x1fff;	// packet ID

			// fourth byte
			value = packet.readUnsignedByte();
			//var scramblingControl:uint = (value >> 6) & 0x03;	// scrambling control bits
			var hasAF:Boolean = Boolean(value & 0x20);	// has adaptation field
			var hasPD:Boolean = Boolean(value & 0x10);	// has payload data
			//var ccount:uint = value & 0x0f;		// continuty count

			// technically hasPD without hasAF is an error, see spec

			if(hasAF)
			{
				// process adaptation field
				// don't care about flags
				// don't care about clocks here
                var af:uint = packet.readUnsignedByte();
				packet.position += af;	// skip to end
			}

            return hasPD ? processES(pid, pusi, packet) : null;
		}

		private function processES(pid:uint, pusi:Boolean, packet:ByteArray):ByteArray
		{
			var output:ByteArray = null;
			if(pid == 0)	// PAT
			{
				if(pusi)
					processPAT(packet);
			}
			else if(pid == _pmtPID)
			{
				if(pusi)
					processPMT(packet);
			}
			else if(pid == _audioPID)
			{
				output = _audioPES.processES(pusi, packet);
			}
			else if(pid == _videoPID)
			{
				output = _videoPES.processES(pusi, packet);
			}
			else if(pid == _mp3AudioPID)
			{
				output = _mp3audioPES.processES(pusi, packet);
			}

			return output;
		}

		private function processPAT(packet:ByteArray):void
		{
			packet.readUnsignedByte();   // pointer:uint
			packet.readUnsignedByte();   // tableID:uint
			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring misc and reserved bits

			packet.position += 5; // skip tsid + version/cni + sec# + last sec#
			remaining -= 5;

			while(remaining > 4)
			{
				packet.readUnsignedShort(); // program number
				_pmtPID = packet.readUnsignedShort() & 0x1fff; // 13 bits
				remaining -= 4;

				//return; // immediately after reading the first pmt ID, if we don't we get the LAST one
			}

			// and ignore the CRC (4 bytes)
		}

		private function processPMT(packet:ByteArray):void
		{
			packet.readUnsignedByte();  // pointer:uint
			var tableID:uint = packet.readUnsignedByte();

			if (tableID != 0x02)
			{
				CONFIG::LOGGING
				{
					logger.warn("PAT pointed to PMT that isn't PMT");
				}
				return; // don't try to parse it
			}

			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring section syntax and reserved

			packet.position += 7; // skip program num, rserved, version, cni, section num, last section num, reserved, PCR PID
			remaining -= 7;

			var piLen:uint = packet.readUnsignedShort() & 0x0fff;
			remaining -= 2;

			packet.position += piLen; // skip program info
			remaining -= piLen;

			while(remaining > 4)
			{
				var type:uint = packet.readUnsignedByte();
				var pid:uint = packet.readUnsignedShort() & 0x1fff;
				var esiLen:uint = packet.readUnsignedShort() & 0x0fff;
				remaining -= 5;

				packet.position += esiLen;
				remaining -= esiLen;

				switch(type)
				{
					case 0x1b: // H.264 video
						_videoPID = pid;
						break;
					case 0x0f: // AAC Audio / ADTS
						_audioPID = pid;
						break;

					case 0x03: // MP3 Audio  (3 & 4)
					case 0x04:
						_mp3AudioPID = pid;
						break;

					default:
						CONFIG::LOGGING
						{
							logger.error("unsupported type "+type.toString(16)+" in PMT");
						}
						break;
				}
			}

			// and ignore CRC
		}

		override public function flushFileSegment(input:IDataInput):ByteArray
		{
			var flvBytes:ByteArray = new ByteArray();
			var flvBytesVideo:ByteArray = _videoPES.processES(false, null, true);
			var flvBytesAudio:ByteArray = _audioPES.processES(false, null, true);

			if(flvBytesVideo)
				flvBytes.readBytes(flvBytesVideo);
			if(flvBytesAudio)
				flvBytes.readBytes(flvBytesAudio);

			return flvBytes;
		}

		CONFIG::LOGGING
		{
			private var logger:Logger = Log.getLogger('org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingMP2TSFileHandler') as Logger;
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the at.matthew.httpstreaming package.
 *
 * The Initial Developer of the Original Code is
 * Matthew Kaufman.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

package org.denivip.osmf.net.httpstreaming.hls
{
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;

	import com.hurlant.util.Hex;
	import org.denivip.osmf.utility.decrypt.AES;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;

	[Event(name="notifySegmentDuration", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]
	[Event(name="notifyTimeBias", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]

	public class HTTPStreamingMP2TSFileHandler extends HTTPStreamingFileHandlerBase
	{
		private var _syncFound:Boolean;
		private var _pmtPID:uint;
		private var _audioPID:uint;
		private var _videoPID:uint;
		private var _mp3AudioPID:uint;
		private var _audioPES:HTTPStreamingMP2PESAudio;
		private var _videoPES:HTTPStreamingMP2PESVideo;
		private var _mp3audioPES:HTTPStreamingMp3Audio2ToPESAudio;

		private var _cachedOutputBytes:ByteArray;
		private var alternatingYieldCounter:int = 0;

		private var _key:HTTPStreamingM3U8IndexKey = null;
		private var _iv:ByteArray = null;
		private var _decryptBuffer:ByteArray = new ByteArray;

		// AES-128 specific variables
		private var _decryptAES:AES = null;

		public function HTTPStreamingMP2TSFileHandler()
		{
			_audioPES = new HTTPStreamingMP2PESAudio;
			_videoPES = new HTTPStreamingMP2PESVideo;
			_mp3audioPES = new HTTPStreamingMp3Audio2ToPESAudio;
		}

		override public function beginProcessFile(seek:Boolean, seekTime:Number):void
		{
			_syncFound = false;
		}

		override public function get inputBytesNeeded():Number
		{
			return _syncFound ? 187 : 1;
		}

		override public function processFileSegment(input:IDataInput):ByteArray
		{
			var bytesAvailableStart:uint = input.bytesAvailable;
			var output:ByteArray;

			if (_cachedOutputBytes !== null) {
				output = _cachedOutputBytes;
				_cachedOutputBytes = null;
			}
			else {
				output = new ByteArray();
			}

			while (true) {
				if(!_syncFound)
				{
					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 16) {
								if (_decryptBuffer.bytesAvailable < 1) {
									break;
								}
							} else {
								if (!decryptToBuffer(input, 16)) {
									break;
								}
							}
							if (_decryptBuffer.readByte() == 0x47) {
								_syncFound = true;
							}
						}
					} else {
						if(input.bytesAvailable < 1)
							break;

						if(input.readByte() == 0x47)
							_syncFound = true;
					}
				}
				else
				{
					var packet:ByteArray = new ByteArray();

					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 176) {
								if (_decryptBuffer.bytesAvailable < 187) {
									break;
								}
							} else {
								var bytesLeft:uint = input.bytesAvailable - 176;
								if (bytesLeft > 0 && bytesLeft < 15) {
									if (!decryptToBuffer(input, input.bytesAvailable)) {
										break;
									}
								} else {
									if (!decryptToBuffer(input, 176)) {
										break;
									}
								}
							}
							_decryptBuffer.readBytes(packet, 0, 187);
						}
					} else {
						if(input.bytesAvailable < 187)
							break;

						input.readBytes(packet, 0, 187);
					}

					_syncFound = false;
					var result:ByteArray = processPacket(packet);
					if (result !== null) {
						output.writeBytes(result);
					}

					if (bytesAvailableStart - input.bytesAvailable > 10000) {
						alternatingYieldCounter = (alternatingYieldCounter + 1) & 0x03;
						if (alternatingYieldCounter /*& 0x01 === 1*/) {
							_cachedOutputBytes = output;
							return null;
						}
						break;
					}
				}
			}
			output.position = 0;

			return output.length === 0 ? null : output;
		}

		private function decryptToBuffer(input:IDataInput, blockSize:int):Boolean{
			if (_key) {
				// Clear buffer
				if (_decryptBuffer.bytesAvailable == 0) {
					_decryptBuffer.clear();
				}

				if (_key.type == "AES-128" && blockSize % 16 == 0 && _key.key) {
					if (!_decryptAES) {
						_decryptAES = new AES(_key.key);
						_decryptAES.pad = "none";
						_decryptAES.iv = _iv;
					}

					// Save buffer position
					var currentPosition:uint = _decryptBuffer.position;
					_decryptBuffer.position += _decryptBuffer.bytesAvailable;

					// Save block to decrypt
					var decrypt:ByteArray = new ByteArray;
					input.readBytes(decrypt, 0, blockSize);
					// Save new IV from ciphertext
					var newIv:ByteArray = new ByteArray;
					decrypt.position += (decrypt.bytesAvailable-16);
					decrypt.readBytes(newIv, 0, 16);
					decrypt.position = 0;
					// Decrypt
					if (input.bytesAvailable == 0) {
						_decryptAES.pad = "pkcs7";
					}
					_decryptAES.decrypt(decrypt);
					decrypt.position = 0;
					// Write into buffer
					_decryptBuffer.writeBytes(decrypt);
					_decryptBuffer.position = currentPosition;
					// Update AES IV
					_decryptAES.iv = newIv;

					return true;
				}
			}

			return false;
		}

		override public function endProcessFile(input:IDataInput):ByteArray
		{
			_decryptBuffer.clear();
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
			return null;
		}

		public function resetCache():void{
			_cachedOutputBytes = null;
			alternatingYieldCounter = 0;
			_decryptBuffer.clear();
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
		}

		public function set initialOffset(offset:Number):void{
			offset *= 1000; // convert to ms
			_videoPES.initialTimestamp = offset;
			_audioPES.initialTimestamp = offset;
			_mp3audioPES.initialTimestamp = offset;
		}

		public function set key(key:HTTPStreamingM3U8IndexKey):void {
			_key = key;
			if (_decryptAES) {
				_decryptAES.destroy();
			}
			_decryptAES = null;
		}

		public function set iv(iv:String):void {
			if (iv) {
				_iv = Hex.toArray(iv);
			}
		}

		private function processPacket(packet:ByteArray):ByteArray
		{
			// decode rest of transport stream prefix (after the 0x47 flag byte)

			// top of second byte
			var value:uint = packet.readUnsignedByte();

			//var tei:Boolean = Boolean(value & 0x80);	// error indicator
			var pusi:Boolean = Boolean(value & 0x40);	// payload unit start indication
			//var tpri:Boolean = Boolean(value & 0x20);	// transport priority indication

			// bottom of second byte and all of third
			value <<= 8;
			value += packet.readUnsignedByte();

			var pid:uint = value & 0x1fff;	// packet ID

			// fourth byte
			value = packet.readUnsignedByte();
			//var scramblingControl:uint = (value >> 6) & 0x03;	// scrambling control bits
			var hasAF:Boolean = Boolean(value & 0x20);	// has adaptation field
			var hasPD:Boolean = Boolean(value & 0x10);	// has payload data
			//var ccount:uint = value & 0x0f;		// continuty count

			// technically hasPD without hasAF is an error, see spec

			if(hasAF)
			{
				// process adaptation field
				// don't care about flags
				// don't care about clocks here
                var af:uint = packet.readUnsignedByte();
				packet.position += af;	// skip to end
			}

            return hasPD ? processES(pid, pusi, packet) : null;
		}

		private function processES(pid:uint, pusi:Boolean, packet:ByteArray):ByteArray
		{
			var output:ByteArray = null;
			if(pid == 0)	// PAT
			{
				if(pusi)
					processPAT(packet);
			}
			else if(pid == _pmtPID)
			{
				if(pusi)
					processPMT(packet);
			}
			else if(pid == _audioPID)
			{
				output = _audioPES.processES(pusi, packet);
			}
			else if(pid == _videoPID)
			{
				output = _videoPES.processES(pusi, packet);
			}
			else if(pid == _mp3AudioPID)
			{
				output = _mp3audioPES.processES(pusi, packet);
			}

			return output;
		}

		private function processPAT(packet:ByteArray):void
		{
			packet.readUnsignedByte();   // pointer:uint
			packet.readUnsignedByte();   // tableID:uint
			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring misc and reserved bits

			packet.position += 5; // skip tsid + version/cni + sec# + last sec#
			remaining -= 5;

			while(remaining > 4)
			{
				packet.readUnsignedShort(); // program number
				_pmtPID = packet.readUnsignedShort() & 0x1fff; // 13 bits
				remaining -= 4;

				//return; // immediately after reading the first pmt ID, if we don't we get the LAST one
			}

			// and ignore the CRC (4 bytes)
		}

		private function processPMT(packet:ByteArray):void
		{
			packet.readUnsignedByte();  // pointer:uint
			var tableID:uint = packet.readUnsignedByte();

			if (tableID != 0x02)
			{
				CONFIG::LOGGING
				{
					logger.warn("PAT pointed to PMT that isn't PMT");
				}
				return; // don't try to parse it
			}

			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring section syntax and reserved

			packet.position += 7; // skip program num, rserved, version, cni, section num, last section num, reserved, PCR PID
			remaining -= 7;

			var piLen:uint = packet.readUnsignedShort() & 0x0fff;
			remaining -= 2;

			packet.position += piLen; // skip program info
			remaining -= piLen;

			while(remaining > 4)
			{
				var type:uint = packet.readUnsignedByte();
				var pid:uint = packet.readUnsignedShort() & 0x1fff;
				var esiLen:uint = packet.readUnsignedShort() & 0x0fff;
				remaining -= 5;

				packet.position += esiLen;
				remaining -= esiLen;

				switch(type)
				{
					case 0x1b: // H.264 video
						_videoPID = pid;
						break;
					case 0x0f: // AAC Audio / ADTS
						_audioPID = pid;
						break;

					case 0x03: // MP3 Audio  (3 & 4)
					case 0x04:
						_mp3AudioPID = pid;
						break;

					default:
						CONFIG::LOGGING
						{
							logger.error("unsupported type "+type.toString(16)+" in PMT");
						}
						break;
				}
			}

			// and ignore CRC
		}

		override public function flushFileSegment(input:IDataInput):ByteArray
		{
			var flvBytes:ByteArray = new ByteArray();
			var flvBytesVideo:ByteArray = _videoPES.processES(false, null, true);
			var flvBytesAudio:ByteArray = _audioPES.processES(false, null, true);

			if(flvBytesVideo)
				flvBytes.readBytes(flvBytesVideo);
			if(flvBytesAudio)
				flvBytes.readBytes(flvBytesAudio);

			return flvBytes;
		}

		CONFIG::LOGGING
		{
			private var logger:Logger = Log.getLogger('org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingMP2TSFileHandler') as Logger;
		}
	}
}

Fix bug where syncFound was flagged as false incorrectly

This was causing byte-level sync issues with throttled (and potentially
other) streams


package goplayer
{
  import flash.display.DisplayObject
  import flash.display.InteractiveObject
  import flash.display.Sprite
  import flash.events.Event
  import flash.events.MouseEvent
  import flash.geom.Point
  import flash.geom.Rectangle
  import flash.text.TextField

	public class AbstractStandardSkin extends AbstractSkin
  {
    private var chromeFader : Fader
    private var largePlayButtonFader : Fader
    private var bufferingIndicatorFader : Fader

    private var hoveringChrome : Boolean = false
    private var showRemainingTime : Boolean = false
    private var changingVolume : Boolean = false

    private var volumeSliderFillMaxHeight : Number
    private var volumeSliderFillMinY : Number

    override protected function initialize() : void
    {
      super.initialize()

      chromeFader = new Fader(chrome, Duration.seconds(.3))
      largePlayButtonFader = new Fader
        (largePlayButton, Duration.seconds(1))
      bufferingIndicatorFader = new Fader
        (bufferingIndicator, Duration.seconds(.3))

      seekBarTooltip.visible = false
      seekBar.mouseChildren = false
      seekBar.buttonMode = true

      bufferingIndicator.mouseEnabled = false

      volumeSliderFillMaxHeight = volumeSliderFill.height
      volumeSliderFillMinY = volumeSliderFill.y
      volumeSlider.mouseChildren = false
      volumeSlider.buttonMode = true

      volumeSliderThumbGuide.visible = false

      volumeSlider.visible = false

      chrome.visible = enableChrome

      addEventListeners()
    }

    override public function update() : void
    {
      super.update()

      chromeFader.targetAlpha = showChrome ? 1 : 0

      upperPanel.visible = showTitle
      titleField.text = titleText

      if (!changingVolume && !hoveringVolumeControl)
        volumeSlider.visible = false

      playButton.visible = !playing
      pauseButton.visible = playing
      muteButton.visible = !muted
      unmuteButton.visible = muted

      volumeSliderThumb.y = volumeSliderThumbY
      volumeSliderFill.height = volumeSliderFillMaxHeight * volume
      volumeSliderFill.y = volumeSliderFillMinY
        + volumeSliderFillMaxHeight * (1 - volume)

      leftTimeField.text = elapsedTimeText
      rightTimeField.text = showRemainingTime
        ? remainingTimeText : totalTimeText

      seekBarBackground.width = seekBarWidth
      seekBarBuffer.width = seekBarWidth * bufferRatio
      seekBarPlayhead.width = seekBarWidth * playheadRatio

      seekBarTooltip.x = seekBar.mouseX
      seekBarTooltipField.text = seekTooltipText

      largePlayButtonFader.targetAlpha = showLargePlayButton ? 1 : 0
      largePlayButton.mouseEnabled = showLargePlayButton

      bufferingIndicatorFader.targetAlpha = bufferingUnexpectedly ? 1 : 0
    }

    private function get showLargePlayButton() : Boolean
    { return !running }

    override protected function get showChrome() : Boolean
    { return super.showChrome || hoveringChrome }

    private function get volumeSliderMouseVolume() : Number
    {
      return 1 - (volumeSlider.mouseY - volumeSliderThumbGuide.y)
        / volumeSliderThumbGuide.height
    }

    private function get volumeSliderThumbY() : Number
    {
      return volumeSliderThumbGuide.y
        + volumeSliderThumbGuide.height * (1 - volume)
    }

    private function get seekTooltipText() : String
    { return getDurationByRatio(seekBarMouseRatio).mss }

    private function get seekBarMouseRatio() : Number
    { return seekBar.mouseX / seekBarWidth }

    private function get hoveringVolumeControl() : Boolean
    { return volumeControlBounds.contains(mouseX, mouseY) }

    private function get volumeControlBounds() : Rectangle
    {
      const result : Rectangle = $volumeControlBounds

      result.inflate(10, 20)

      return result
    }

    private function get $volumeControlBounds() : Rectangle
    {
      return muteButton.getBounds(this)
        .union(volumeSlider.getBounds(this))
    }

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

    private function addEventListeners() : void
    {
      onclick(largePlayButton, handlePlayButtonClicked)
      onclick(playButton, handlePlayButtonClicked)
      onclick(pauseButton, handlePauseButtonClicked)
      onclick(muteButton, handleMuteButtonClicked)
      onclick(unmuteButton, handleUnmuteButtonClicked)
      onclick(enableFullscreenButton, handleEnableFullscreenButtonClicked)
      onmousemove(seekBar, handleSeekBarMouseMove)
      onrollover(seekBar, handleSeekBarRollOver)
      onrollout(seekBar, handleSeekBarRollOut)
      onclick(seekBar, handleSeekBarClicked)
      onmousedown(volumeSlider, handleVolumeSliderMouseDown)
      onrollover(muteButton, handleVolumeButtonRollOver)
      onrollover(unmuteButton, handleVolumeButtonRollOver)
      onrollover(chrome, handleChromeRollOver)
      onrollout(chrome, handleChromeRollOut)
      onclick(rightTimeField, handleRightTimeFieldClicked)
    }

    private function handleChromeRollOver() : void
    { hoveringChrome = true, update() }

    private function handleChromeRollOut() : void
    { hoveringChrome = false, update() }

    private function handleRightTimeFieldClicked() : void
    { showRemainingTime = !showRemainingTime }

    private function handlePlayButtonClicked() : void
    { backend.handleUserPlay() }

    private function handlePauseButtonClicked() : void
    { backend.handleUserPause() }

    private function handleMuteButtonClicked() : void
    { backend.handleUserMute() }

    private function handleUnmuteButtonClicked() : void
    { backend.handleUserUnmute() }

    private function handleEnableFullscreenButtonClicked() : void
    { backend.handleUserToggleFullscreen() }

    private function handleSeekBarMouseMove() : void
    { update() }

    private function handleSeekBarRollOver() : void
    { seekBarTooltip.visible = true }

    private function handleSeekBarRollOut() : void
    { seekBarTooltip.visible = false }

    private function handleSeekBarClicked() : void
    { backend.handleUserSeek(seekBarMouseRatio) }

    private function handleVolumeSliderMouseDown() : void
    {
      changingVolume = true
      backend.handleUserSetVolume(volumeSliderMouseVolume)
      stage.addEventListener
        (MouseEvent.MOUSE_MOVE, handleVolumeSliderMouseMove);
      stage.addEventListener
        (MouseEvent.MOUSE_UP, handleVolumeSliderMouseUp);
      stage.addEventListener
        (Event.MOUSE_LEAVE, handleVolumeSliderMouseLeftStage);
    }

    private function handleVolumeSliderMouseMove(event : MouseEvent) : void
    { backend.handleUserSetVolume(volumeSliderMouseVolume) }

    private function handleVolumeSliderMouseUp(event : MouseEvent) : void
    { removeVolumeSliderEventListeners() }

    private function handleVolumeSliderMouseLeftStage(event : Event) : void
    { removeVolumeSliderEventListeners() }

    private function removeVolumeSliderEventListeners() : void
    {
      stage.removeEventListener
        (MouseEvent.MOUSE_MOVE, handleVolumeSliderMouseMove);
      stage.removeEventListener
        (MouseEvent.MOUSE_UP, handleVolumeSliderMouseUp);
      stage.removeEventListener
        (Event.MOUSE_LEAVE, handleVolumeSliderMouseLeftStage);
      changingVolume = false
    }

    private function handleVolumeButtonRollOver() : void
    { volumeSlider.visible = true }

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

    protected function get seekBarWidth() : Number
    { throw new Error }

    protected function get largePlayButton() : InteractiveObject
    { return undefinedPart("largePlayButton") }
    protected function get bufferingIndicator() : InteractiveObject
    { return undefinedPart("bufferingIndicator") }

    protected function get chrome() : Sprite
    { return undefinedPart("chrome") }

    protected function get upperPanel() : Sprite
    { return undefinedPart("upperPanel") }

    protected function get titleField() : TextField
    { return undefinedPart("titleField") }

    protected function get controlBar() : Sprite
    { return undefinedPart("controlBar") }

    protected function get leftTimeField() : TextField
    { return undefinedPart("leftTimeField") }
    protected function get playButton() : DisplayObject
    { return undefinedPart("playButton") }
    protected function get pauseButton() : DisplayObject
    { return undefinedPart("pauseButton") }

    protected function get seekBarBackground() : DisplayObject
    { return undefinedPart("seekBarBackground") }

    protected function get seekBar() : Sprite
    { return undefinedPart("seekBar") }
    protected function get seekBarGroove() : DisplayObject
    { return undefinedPart("seekBarGroove") }
    protected function get seekBarBuffer() : DisplayObject
    { return undefinedPart("seekBarBuffer") }
    protected function get seekBarPlayhead() : DisplayObject
    { return undefinedPart("seekBarPlayhead") }
    protected function get seekBarTooltip() : DisplayObject
    { return undefinedPart("seekBarTooltip") }
    protected function get seekBarTooltipField() : TextField
    { return undefinedPart("seekBarTooltipField") }

    protected function get rightTimeField() : TextField
    { return undefinedPart("rightTimeField") }
    protected function get muteButton() : DisplayObject
    { return undefinedPart("muteButton") }
    protected function get unmuteButton() : DisplayObject
    { return undefinedPart("unmuteButton") }
    protected function get enableFullscreenButton() : DisplayObject
    { return undefinedPart("enableFullscreenButton") }

    protected function get volumeSlider() : Sprite
    { return undefinedPart("volumeSlider") }
    protected function get volumeSliderThumb() : DisplayObject
    { return undefinedPart("volumeSliderThumb") }
    protected function get volumeSliderThumbGuide() : DisplayObject
    { return undefinedPart("volumeSliderThumbGuide") }
    protected function get volumeSliderFill() : DisplayObject
    { return undefinedPart("volumeSliderFill") }
  }
}

package goplayer
{
  import flash.display.DisplayObject
  import flash.display.InteractiveObject
  import flash.display.Sprite
  import flash.events.Event
  import flash.events.MouseEvent
  import flash.geom.Point
  import flash.geom.Rectangle
  import flash.text.TextField

	public class AbstractStandardSkin extends AbstractSkin
  {
    private var chromeFader : Fader
    private var largePlayButtonFader : Fader
    private var bufferingIndicatorFader : Fader

    private var hoveringChrome : Boolean = false
    private var showRemainingTime : Boolean = false
    private var changingVolume : Boolean = false

    private var volumeSliderFillMaxHeight : Number
    private var volumeSliderFillMinY : Number

    override protected function initialize() : void
    {
      super.initialize()

      chromeFader = new Fader(chrome, Duration.seconds(.3))
      largePlayButtonFader = new Fader
        (largePlayButton, Duration.seconds(1))
      bufferingIndicatorFader = new Fader
        (bufferingIndicator, Duration.seconds(.3))

      seekBarTooltip.visible = false
      seekBar.mouseChildren = false
      seekBar.buttonMode = true

      bufferingIndicator.mouseEnabled = false

      volumeSliderFillMaxHeight = volumeSliderFill.height
      volumeSliderFillMinY = volumeSliderFill.y
      volumeSlider.mouseChildren = false
      volumeSlider.buttonMode = true

      volumeSliderThumbGuide.visible = false

      volumeSlider.visible = false

      chrome.visible = enableChrome

      addEventListeners()
    }

    override public function update() : void
    {
      super.update()

      chromeFader.targetAlpha = showChrome ? 1 : 0

      upperPanel.visible = showTitle
      titleField.text = titleText

      if (!changingVolume && !hoveringVolumeControl)
        volumeSlider.visible = false

      playButton.visible = !playing
      pauseButton.visible = playing
      muteButton.visible = !muted
      unmuteButton.visible = muted

      volumeSliderThumb.y = volumeSliderThumbY
      volumeSliderFill.height = volumeSliderFillMaxHeight * volume
      volumeSliderFill.y = volumeSliderFillMinY
        + volumeSliderFillMaxHeight * (1 - volume)

      leftTimeField.text = elapsedTimeText
      rightTimeField.text = showRemainingTime
        ? remainingTimeText : totalTimeText

      seekBarBackground.width = seekBarWidth
      seekBarGroove.width = seekBarWidth
      seekBarBuffer.width = seekBarWidth * bufferRatio
      seekBarPlayhead.width = seekBarWidth * playheadRatio

      seekBarTooltip.x = seekBar.mouseX
      seekBarTooltipField.text = seekTooltipText

      largePlayButtonFader.targetAlpha = showLargePlayButton ? 1 : 0
      largePlayButton.mouseEnabled = showLargePlayButton

      bufferingIndicatorFader.targetAlpha = bufferingUnexpectedly ? 1 : 0
    }

    private function get showLargePlayButton() : Boolean
    { return !running }

    override protected function get showChrome() : Boolean
    { return super.showChrome || hoveringChrome }

    private function get volumeSliderMouseVolume() : Number
    {
      return 1 - (volumeSlider.mouseY - volumeSliderThumbGuide.y)
        / volumeSliderThumbGuide.height
    }

    private function get volumeSliderThumbY() : Number
    {
      return volumeSliderThumbGuide.y
        + volumeSliderThumbGuide.height * (1 - volume)
    }

    private function get seekTooltipText() : String
    { return getDurationByRatio(seekBarMouseRatio).mss }

    private function get seekBarMouseRatio() : Number
    { return seekBar.mouseX / seekBarWidth }

    private function get hoveringVolumeControl() : Boolean
    { return volumeControlBounds.contains(mouseX, mouseY) }

    private function get volumeControlBounds() : Rectangle
    {
      const result : Rectangle = $volumeControlBounds

      result.inflate(10, 20)

      return result
    }

    private function get $volumeControlBounds() : Rectangle
    {
      return muteButton.getBounds(this)
        .union(volumeSlider.getBounds(this))
    }

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

    private function addEventListeners() : void
    {
      onclick(largePlayButton, handlePlayButtonClicked)
      onclick(playButton, handlePlayButtonClicked)
      onclick(pauseButton, handlePauseButtonClicked)
      onclick(muteButton, handleMuteButtonClicked)
      onclick(unmuteButton, handleUnmuteButtonClicked)
      onclick(enableFullscreenButton, handleEnableFullscreenButtonClicked)
      onmousemove(seekBar, handleSeekBarMouseMove)
      onrollover(seekBar, handleSeekBarRollOver)
      onrollout(seekBar, handleSeekBarRollOut)
      onclick(seekBar, handleSeekBarClicked)
      onmousedown(volumeSlider, handleVolumeSliderMouseDown)
      onrollover(muteButton, handleVolumeButtonRollOver)
      onrollover(unmuteButton, handleVolumeButtonRollOver)
      onrollover(chrome, handleChromeRollOver)
      onrollout(chrome, handleChromeRollOut)
      onclick(rightTimeField, handleRightTimeFieldClicked)
    }

    private function handleChromeRollOver() : void
    { hoveringChrome = true, update() }

    private function handleChromeRollOut() : void
    { hoveringChrome = false, update() }

    private function handleRightTimeFieldClicked() : void
    { showRemainingTime = !showRemainingTime }

    private function handlePlayButtonClicked() : void
    { backend.handleUserPlay() }

    private function handlePauseButtonClicked() : void
    { backend.handleUserPause() }

    private function handleMuteButtonClicked() : void
    { backend.handleUserMute() }

    private function handleUnmuteButtonClicked() : void
    { backend.handleUserUnmute() }

    private function handleEnableFullscreenButtonClicked() : void
    { backend.handleUserToggleFullscreen() }

    private function handleSeekBarMouseMove() : void
    { update() }

    private function handleSeekBarRollOver() : void
    { seekBarTooltip.visible = true }

    private function handleSeekBarRollOut() : void
    { seekBarTooltip.visible = false }

    private function handleSeekBarClicked() : void
    { backend.handleUserSeek(seekBarMouseRatio) }

    private function handleVolumeSliderMouseDown() : void
    {
      changingVolume = true
      backend.handleUserSetVolume(volumeSliderMouseVolume)
      stage.addEventListener
        (MouseEvent.MOUSE_MOVE, handleVolumeSliderMouseMove);
      stage.addEventListener
        (MouseEvent.MOUSE_UP, handleVolumeSliderMouseUp);
      stage.addEventListener
        (Event.MOUSE_LEAVE, handleVolumeSliderMouseLeftStage);
    }

    private function handleVolumeSliderMouseMove(event : MouseEvent) : void
    { backend.handleUserSetVolume(volumeSliderMouseVolume) }

    private function handleVolumeSliderMouseUp(event : MouseEvent) : void
    { removeVolumeSliderEventListeners() }

    private function handleVolumeSliderMouseLeftStage(event : Event) : void
    { removeVolumeSliderEventListeners() }

    private function removeVolumeSliderEventListeners() : void
    {
      stage.removeEventListener
        (MouseEvent.MOUSE_MOVE, handleVolumeSliderMouseMove);
      stage.removeEventListener
        (MouseEvent.MOUSE_UP, handleVolumeSliderMouseUp);
      stage.removeEventListener
        (Event.MOUSE_LEAVE, handleVolumeSliderMouseLeftStage);
      changingVolume = false
    }

    private function handleVolumeButtonRollOver() : void
    { volumeSlider.visible = true }

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

    protected function get seekBarWidth() : Number
    { throw new Error }

    protected function get largePlayButton() : InteractiveObject
    { return undefinedPart("largePlayButton") }
    protected function get bufferingIndicator() : InteractiveObject
    { return undefinedPart("bufferingIndicator") }

    protected function get chrome() : Sprite
    { return undefinedPart("chrome") }

    protected function get upperPanel() : Sprite
    { return undefinedPart("upperPanel") }

    protected function get titleField() : TextField
    { return undefinedPart("titleField") }

    protected function get controlBar() : Sprite
    { return undefinedPart("controlBar") }

    protected function get leftTimeField() : TextField
    { return undefinedPart("leftTimeField") }
    protected function get playButton() : DisplayObject
    { return undefinedPart("playButton") }
    protected function get pauseButton() : DisplayObject
    { return undefinedPart("pauseButton") }

    protected function get seekBarBackground() : DisplayObject
    { return undefinedPart("seekBarBackground") }

    protected function get seekBar() : Sprite
    { return undefinedPart("seekBar") }
    protected function get seekBarGroove() : DisplayObject
    { return undefinedPart("seekBarGroove") }
    protected function get seekBarBuffer() : DisplayObject
    { return undefinedPart("seekBarBuffer") }
    protected function get seekBarPlayhead() : DisplayObject
    { return undefinedPart("seekBarPlayhead") }
    protected function get seekBarTooltip() : DisplayObject
    { return undefinedPart("seekBarTooltip") }
    protected function get seekBarTooltipField() : TextField
    { return undefinedPart("seekBarTooltipField") }

    protected function get rightTimeField() : TextField
    { return undefinedPart("rightTimeField") }
    protected function get muteButton() : DisplayObject
    { return undefinedPart("muteButton") }
    protected function get unmuteButton() : DisplayObject
    { return undefinedPart("unmuteButton") }
    protected function get enableFullscreenButton() : DisplayObject
    { return undefinedPart("enableFullscreenButton") }

    protected function get volumeSlider() : Sprite
    { return undefinedPart("volumeSlider") }
    protected function get volumeSliderThumb() : DisplayObject
    { return undefinedPart("volumeSliderThumb") }
    protected function get volumeSliderThumbGuide() : DisplayObject
    { return undefinedPart("volumeSliderThumbGuide") }
    protected function get volumeSliderFill() : DisplayObject
    { return undefinedPart("volumeSliderFill") }
  }
}

Fix bug: Seek bar groove width was not being updated.


package aerys.minko.type.animation.timeline
{
	import aerys.minko.scene.node.IScene;
	import aerys.minko.type.math.Matrix4x4;

	public class MatrixLinearTimeline implements ITimeline
	{
		private var _targetName		: String;
		private var _propertyName	: String;

		private var _timeTable		: Vector.<uint>
		private var _values			: Vector.<Matrix4x4>;

		public function get targetName()	: String	{ return _targetName; }
		public function get propertyName()	: String	{ return _propertyName; }
		public function get duration()		: uint		{ return _timeTable[_timeTable.length - 1]; }

		public function MatrixLinearTimeline(targetName		: String,
											 propertyName	: String,
											 timeTable		: Vector.<uint>,
											 matrices		: Vector.<Matrix4x4>)
		{
			_targetName		= targetName;
			_propertyName	= propertyName;
			_timeTable		= timeTable;
			_values			= matrices;
		}

		public function updateAt(t : uint, scene : IScene) : void
		{
			var timeId		: uint = getIndexForTime(t);
			var timeCount	: uint = _timeTable.length;

			// change matrix value.
			var out : Matrix4x4 = scene[_propertyName];
			if (!out)
				throw new Error(_propertyName + ' property was not found on scene node named ' + _targetName);

			if (timeId == 0)
			{
				Matrix4x4.copy(_values[0], out);
			}
			else if (timeId == timeCount)
			{
				Matrix4x4.copy(_values[timeCount - 1], out);
			}
			else
			{
				var previousTime		: Number	= _timeTable[timeId - 1];
				var nextTime			: Number	= _timeTable[timeId];
				var interpolationRatio	: Number	= (t - previousTime) / (nextTime - previousTime);

				var previousMatrix		: Matrix4x4 = _values[timeId - 1];
				var nextMatrix			: Matrix4x4 = _values[timeId];

				Matrix4x4.copy(previousMatrix, out);
				previousMatrix.interpolateTo(nextMatrix, 1 - interpolationRatio);
			}
		}

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

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

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

			return upperTimeId;
		}

		public function clone() : ITimeline
		{
			return new MatrixLinearTimeline(_targetName, _propertyName, _timeTable.slice(), _values.slice());
		}

		public function reverse() : void
		{
			_timeTable.reverse();
			_values.reverse();
		}
	}
}

package aerys.minko.type.animation.timeline
{
	import aerys.minko.scene.node.IScene;
	import aerys.minko.type.math.Matrix4x4;

	public class MatrixLinearTimeline implements ITimeline
	{
		private var _targetName		: String;
		private var _propertyName	: String;

		private var _timeTable		: Vector.<uint>
		private var _values			: Vector.<Matrix4x4>;

		public function get targetName()	: String	{ return _targetName; }
		public function get propertyName()	: String	{ return _propertyName; }
		public function get duration()		: uint		{ return _timeTable[_timeTable.length - 1]; }

		public function MatrixLinearTimeline(targetName		: String,
											 propertyName	: String,
											 timeTable		: Vector.<uint>,
											 matrices		: Vector.<Matrix4x4>)
		{
			_targetName		= targetName;
			_propertyName	= propertyName;
			_timeTable		= timeTable;
			_values			= matrices;
		}

		public function updateAt(t : uint, scene : IScene) : void
		{
			var timeId		: uint = getIndexForTime(t);
			var timeCount	: uint = _timeTable.length;

			// change matrix value.
			var out : Matrix4x4 = scene[_propertyName];
			if (!out)
				throw new Error(_propertyName + ' property was not found on scene node named ' + _targetName);

			if (timeId == 0)
			{
				Matrix4x4.copy(_values[0], out);
			}
			else if (timeId == timeCount)
			{
				Matrix4x4.copy(_values[timeCount - 1], out);
			}
			else
			{
				var previousTime		: Number	= _timeTable[timeId - 1];
				var nextTime			: Number	= _timeTable[timeId];
				var interpolationRatio	: Number	= (t - previousTime) / (nextTime - previousTime);

				var previousMatrix		: Matrix4x4 = _values[timeId - 1];
				var nextMatrix			: Matrix4x4 = _values[timeId];

				Matrix4x4.copy(previousMatrix, out);
				out.interpolateTo(nextMatrix, 1 - interpolationRatio);
			}
		}

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

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

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

			return upperTimeId;
		}

		public function clone() : ITimeline
		{
			return new MatrixLinearTimeline(_targetName, _propertyName, _timeTable.slice(), _values.slice());
		}

		public function reverse() : void
		{
			_timeTable.reverse();
			_values.reverse();
		}
	}
}

Fix bug in MatrixLinearTimeline that was messing with animations over the time