////////////////////////////////////////////////////////////////////////////////
//
//  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 {

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.getQualifiedClassName;
import flash.utils.getTimer;
import flash.utils.Timer;

/**
 *  A Test.  A Test script comprises several of these
 *  TestCases.  Each TestCase consists of a
 *  setup, body and cleanup section which are sets
 *  of TestStep-derived classes like SetProperty,
 *  RunCode, AssertPropertyValue, etc.
 *
 *  MXML Properties (not attributes)
 *  setup
 *  body
 *  cleanup
 */
public class TestCase extends EventDispatcher
{
	/**
	 *  The history of bugs that this test case has encountered
	 */
	public var bugs:Array;

	/**
	 *  The sequence of TestSteps that comprise the test setup
	 */
	public var setup:Array;

	/**
	 *  The sequence of TestSteps that comprise the test
	 */
	public var body:Array;

	/**
	 *  The sequence of TestSteps that restore the test environment
	 */
	public var cleanup:Array;

	/**
	 *  An identifier for the test
	 */
	public var testID:String;

	/**
	 *  A description of the test
	 */
	public var description:String;

	/**
	 *  keywords, summarizing the tests
	 */
	public var keywords:String;

	/**
	 *  frequency, an estimate of the tests's intersection with real-world
	 *  usage. 1 = most frequent usage; 2 somewhat common; 3 = unusual
	 */
	public var frequency:String;

	/**
	 *  The current set of steps (setup, body, cleanup) we are executing
	 */
	private var currentSteps:Array;

	/**
	 *  Which step we're currently executing (or waiting on an event for)
	 */
	private var currentIndex:int = 0;

	/**
	 *  Number of steps in currentSteps
	 */
	private var numSteps:int = 0;

	/**
	 *  The root of the SWF (SystemManager)
	 */
	private var root:DisplayObject;

	/**
	 *  The shared timer we listen to
	 */
	private var timer:Timer;

	/**
	 *  The unit tester
	 */
	private var context:UnitTester;

	/**
	 *  Whether we need to emit a runComplete event or not
	 */
	private var needCompleteEvent:Boolean = false;

	/**
	 *  If non-zero, the time when we'll give up on waiting
	 */
	private var expirationTime:int;

	/**
	 *  the last expected Error thrown by SetProperty
	 */
	public var lastError:Error;

	/**
	 *	Called by test steps looking for a timeout indicator
	 */
	public function setExpirationTime(time:int):void
	{
		expirationTime = time + UnitTester.timeout_plus;
	}

	/**
	 *  Storage for the cleanupAsserts
	 */
	private var _cleanupAsserts:Array;

	/**
	 *  Steps we have to review at the end of the body to see if
	 *  they failed or not.  These steps monitor activity like
	 *  checking for duplicate events or making sure unwanted events
	 *  don't fire.
	 */
	public function get cleanupAsserts():Array
	{
		return _cleanupAsserts;
	}

	/**
	 *  Storage for this tests's result
	 */
	private var _testResult:TestResult;

	/**
	 *  This tests's result
	 */
	public function get testResult():TestResult
	{
		_testResult.testID = testID;
		return _testResult;
	}

	/**
	 * Constructor. Create the TestResult associated with this TestCase
	 */
	public function TestCase() {

		_testResult = new TestResult();
		_testResult.testID = testID;

		_cleanupAsserts = [];
	}

	/**
	 *  Called by the UnitTester when it is time to execute
	 *  this test case.
	 *
	 *  @param root The SystemManager
	 *  @param timer The shared Timer;
	 *  @param the UnitTester that contains these tests
	 */
	public function runTest(root:DisplayObject, timer:Timer, context:UnitTester):Boolean
	{

		_testResult.beginTime = new Date().time;
		_testResult.context = context;
		this.timer = timer;
		this.timer.addEventListener("timer", timerHandler);
		this.root = root;
		this.context = context;

		if (UnitTester.hasRTE)
		{
			return true;

		}

		// do a sanity test here
		if (UnitTester.verboseMode)
		{
			var needWaitEvent:Boolean = false;
			var i:int;
			if (setup)
			{
				for (i = 0; i < setup.length; i++)
				{
					if (setup[i] is ResetComponent)
						needWaitEvent = true;
					if (needWaitEvent)
					{
						if (setup[i].waitEvent)
						{
							needWaitEvent = false;
							break;
						}
					}
				}
				if (needWaitEvent)
					TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " ResetComponent used without any setup steps using a waitEvent\n");
			}
			var allAsserts:Boolean = true;
			needWaitEvent = false;
			for (i = 0; i < body.length; i++)
			{
				if (body[i] is SetProperty || body[i] is SetStyle)
					needWaitEvent = true;
				if (!(body[i] is Assert))
					allAsserts = false;
				if (allAsserts && body[i] is AssertEvent)
				{
					TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " no non-Assert steps in body before AssertEvent\n");
				}
				if (needWaitEvent)
				{
					if (body[i].waitEvent)
					{
						needWaitEvent = false;
						break;
					}
				}
			}
			if (needWaitEvent)
				TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " tests setting values without a waitEvent\n");

		}

		return runSetup();
	}

	/**
	 *  Execute the setup portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runSetup(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (setup)
			{
				testResult.phase = TestResult.SETUP;
				currentSteps = setup;
				if (!continuing)
					currentIndex = 0;
				numSteps = setup.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runBody();
	}

	/**
	 *  Execute the body portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runBody(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (body)
			{
				testResult.phase = TestResult.BODY;
				currentSteps = body;
				if (!continuing)
					currentIndex = 0;
				numSteps = body.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runCleanup();
	}

	/**
	 *  Execute the cleanup portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runCleanup(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (cleanup)
			{
				testResult.phase = TestResult.CLEANUP;
				currentSteps = cleanup;
				if (!continuing)
					currentIndex = 0;
				numSteps = cleanup.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runComplete();
	}

	/**
	 *  Clean up when all three phases are done.  Sends an event
	 *  to the UnitTester harness to tell it that it can run
	 *  the next test case.
	 */
	private function runComplete():Boolean
	{
		var n:int = cleanupAsserts.length;
		for (var i:int = 0; i < n; i++)
		{
			var assert:Assert = cleanupAsserts[i];
			assert.cleanup();
		}

		timer.removeEventListener("timer", timerHandler);
		if (needCompleteEvent)
			dispatchEvent(new Event("runComplete"));
		return true;
	}

	/**
	 *  Go through the currentSteps, executing each one.
	 *  Returns true if no test steps required waiting.
	 *  Returns false if we have to wait for an event before
	 *  continuing.
	 */
	private function runSteps():Boolean
	{
		while (currentIndex < numSteps)
		{
			// return if a step failed
			if (testResult.hasStatus())
				return true;

			/*
			    The following lets you step each step but makes tests
			    more sensitive to which step actually sends the event.
				For example, cb.mouseDown/mouseUp, the tween starts on
				mousedown and fires its event before you step.  Another
				example is asserting with waitEvent=updateComplete.  It
				could have fired a long time before you step
			*/
			if (UnitTester.playbackControl == "pause")
			{
				UnitTester.callback = pauseHandler;
				needCompleteEvent = true;
				return false;
			}
			else if (UnitTester.playbackControl == "step")
				UnitTester.playbackControl = "pause";

			var step:TestStep = currentSteps[currentIndex];
			if (!(step is Assert))
			{
				// look at subsequent steps for Asserts and set them up early
				for (var j:int = currentIndex + 1; j < numSteps; j++)
				{
					// scan following asserts for AssertEvents and set them up early
					var nextStep:TestStep = currentSteps[j];
					if (nextStep is Assert)
					{
						Assert(nextStep).preview(root, context, this, testResult);
						// do a check to be sure folks are using AssertEventPropertyValue correctly
						if (nextStep is AssertEventPropertyValue)
						{
							// AEPV must follow an AssertEvent or another AEPV
							if (j == 0 || !(currentSteps[j-1] is AssertEvent || currentSteps[j-1] is AssertEventPropertyValue))
								TestOutput.logResult("WARNING: AssertEventPropertyValue may be missing preceding AssertEvent");
						}
						else if (nextStep is AssertError)
						{
							if (step is SetProperty)
								SetProperty(step).expectError = true;
						}
					}
					else
						break;
				}
			}
			if (UnitTester.verboseMode)
			{
				TestOutput.logResult(step.toString());
			}
			UnitTester.lastStep = step;
			UnitTester.lastStepLine = currentIndex;
			step.addEventListener("stepComplete", stepCompleteHandler);
			if (!step.execute(root, context, this, testResult))
			{
				needCompleteEvent = true;
				return false;
			}
			step.removeEventListener("stepComplete", stepCompleteHandler);
			currentIndex++;
		}
		return true;
	}

	private function stepCompleteHandler(event:Event):void
	{
		var step:TestStep = currentSteps[currentIndex];
		step.removeEventListener("stepComplete", stepCompleteHandler);
		if (UnitTester.playbackControl == "play")
			UnitTester.callback = runNextSteps;
		else
		{
			currentIndex++;
			UnitTester.callback = pauseHandler;
		}
	}

	/**
	 *  Handler for timer events to see if we've waited too long
	 */
	private function timerHandler(event:Event):void
	{
		if (expirationTime > 0)
			if (expirationTime < getTimer())
			{
				var step:TestStep = currentSteps[currentIndex];
				step.timeoutCallback();
			}
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function runNextSteps():void
	{
		currentIndex++;
		if (currentSteps == setup)
			runSetup(true);
		else if (currentSteps == body)
			runBody(true);
		else if (currentSteps == cleanup)
			runCleanup(true);
		else
			runComplete();
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function continueSteps():void
	{
		if (currentSteps == setup)
			runSetup(true);
		else if (currentSteps == body)
			runBody(true);
		else if (currentSteps == cleanup)
			runCleanup(true);
		else
			runComplete();
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function pauseHandler():void
	{
		if (UnitTester.playbackControl == "step")
			continueSteps();
		else if (UnitTester.playbackControl == "play")
			continueSteps();
		else
			UnitTester.callback = pauseHandler;
	}
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  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 {

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.getQualifiedClassName;
import flash.utils.getTimer;
import flash.utils.Timer;

/**
 *  A Test.  A Test script comprises several of these
 *  TestCases.  Each TestCase consists of a
 *  setup, body and cleanup section which are sets
 *  of TestStep-derived classes like SetProperty,
 *  RunCode, AssertPropertyValue, etc.
 *
 *  MXML Properties (not attributes)
 *  setup
 *  body
 *  cleanup
 */
public class TestCase extends EventDispatcher
{
	/**
	 *  The history of bugs that this test case has encountered
	 */
	public var bugs:Array;

	/**
	 *  The sequence of TestSteps that comprise the test setup
	 */
	public var setup:Array;

	/**
	 *  The sequence of TestSteps that comprise the test
	 */
	public var body:Array;

	/**
	 *  The sequence of TestSteps that restore the test environment
	 */
	public var cleanup:Array;

	/**
	 *  An identifier for the test
	 */
	public var testID:String;

	/**
	 *  A description of the test
	 */
	public var description:String;

	/**
	 *  keywords, summarizing the tests
	 */
	public var keywords:String;

	/**
	 *  frequency, an estimate of the tests's intersection with real-world
	 *  usage. 1 = most frequent usage; 2 somewhat common; 3 = unusual
	 */
	public var frequency:String;

	/**
	 *  The current set of steps (setup, body, cleanup) we are executing
	 */
	private var currentSteps:Array;

	/**
	 *  Which step we're currently executing (or waiting on an event for)
	 */
	private var currentIndex:int = 0;

	/**
	 *  Number of steps in currentSteps
	 */
	private var numSteps:int = 0;

	/**
	 *  The root of the SWF (SystemManager)
	 */
	private var root:DisplayObject;

	/**
	 *  The shared timer we listen to
	 */
	private var timer:Timer;

	/**
	 *  The unit tester
	 */
	private var context:UnitTester;

	/**
	 *  Whether we need to emit a runComplete event or not
	 */
	private var needCompleteEvent:Boolean = false;

	/**
	 *  If non-zero, the time when we'll give up on waiting
	 */
	public var expirationTime:int;

	/**
	 *  the last expected Error thrown by SetProperty
	 */
	public var lastError:Error;

	/**
	 *	Called by test steps looking for a timeout indicator
	 */
	public function setExpirationTime(time:int):void
	{
		expirationTime = (time > 0) ? time + UnitTester.timeout_plus : 0;
	}

	/**
	 *  Storage for the cleanupAsserts
	 */
	private var _cleanupAsserts:Array;

	/**
	 *  Steps we have to review at the end of the body to see if
	 *  they failed or not.  These steps monitor activity like
	 *  checking for duplicate events or making sure unwanted events
	 *  don't fire.
	 */
	public function get cleanupAsserts():Array
	{
		return _cleanupAsserts;
	}

	/**
	 *  Storage for this tests's result
	 */
	private var _testResult:TestResult;

	/**
	 *  This tests's result
	 */
	public function get testResult():TestResult
	{
		_testResult.testID = testID;
		return _testResult;
	}

	/**
	 * Constructor. Create the TestResult associated with this TestCase
	 */
	public function TestCase() {

		_testResult = new TestResult();
		_testResult.testID = testID;

		_cleanupAsserts = [];
	}

	/**
	 *  Called by the UnitTester when it is time to execute
	 *  this test case.
	 *
	 *  @param root The SystemManager
	 *  @param timer The shared Timer;
	 *  @param the UnitTester that contains these tests
	 */
	public function runTest(root:DisplayObject, timer:Timer, context:UnitTester):Boolean
	{

		_testResult.beginTime = new Date().time;
		_testResult.context = context;
		this.timer = timer;
		this.timer.addEventListener("timer", timerHandler);
		this.root = root;
		this.context = context;

		if (UnitTester.hasRTE)
		{
			return true;

		}

		// do a sanity test here
		if (UnitTester.verboseMode)
		{
			var needWaitEvent:Boolean = false;
			var i:int;
			if (setup)
			{
				for (i = 0; i < setup.length; i++)
				{
					if (setup[i] is ResetComponent)
						needWaitEvent = true;
					if (needWaitEvent)
					{
						if (setup[i].waitEvent)
						{
							needWaitEvent = false;
							break;
						}
					}
				}
				if (needWaitEvent)
					TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " ResetComponent used without any setup steps using a waitEvent\n");
			}
			var allAsserts:Boolean = true;
			needWaitEvent = false;
			for (i = 0; i < body.length; i++)
			{
				if (body[i] is SetProperty || body[i] is SetStyle)
					needWaitEvent = true;
				if (!(body[i] is Assert))
					allAsserts = false;
				if (allAsserts && body[i] is AssertEvent)
				{
					TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " no non-Assert steps in body before AssertEvent\n");
				}
				if (needWaitEvent)
				{
					if (body[i].waitEvent)
					{
						needWaitEvent = false;
						break;
					}
				}
			}
			if (needWaitEvent)
				TestOutput.logResult("WARNING: Test " + getQualifiedClassName(context) + "." + testID + " tests setting values without a waitEvent\n");

		}

		return runSetup();
	}

	/**
	 *  Execute the setup portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runSetup(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (setup)
			{
				testResult.phase = TestResult.SETUP;
				currentSteps = setup;
				if (!continuing)
					currentIndex = 0;
				numSteps = setup.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runBody();
	}

	/**
	 *  Execute the body portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runBody(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (body)
			{
				testResult.phase = TestResult.BODY;
				currentSteps = body;
				if (!continuing)
					currentIndex = 0;
				numSteps = body.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runCleanup();
	}

	/**
	 *  Execute the cleanup portion of the test
	 *
	 *  @param continuing If true, don't reset the counter to zero
	 *  and just continue on with the test at the currentIndex
	 */
	private function runCleanup(continuing:Boolean = false):Boolean
	{
		if (!testResult.hasStatus())
		{
			if (cleanup)
			{
				testResult.phase = TestResult.CLEANUP;
				currentSteps = cleanup;
				if (!continuing)
					currentIndex = 0;
				numSteps = cleanup.length;
				// return if we need to wait for something
				if (!runSteps())
					return false;

			}
		}
		return runComplete();
	}

	/**
	 *  Clean up when all three phases are done.  Sends an event
	 *  to the UnitTester harness to tell it that it can run
	 *  the next test case.
	 */
	private function runComplete():Boolean
	{
		var n:int = cleanupAsserts.length;
		for (var i:int = 0; i < n; i++)
		{
			var assert:Assert = cleanupAsserts[i];
			assert.cleanup();
		}

		timer.removeEventListener("timer", timerHandler);
		if (needCompleteEvent)
			dispatchEvent(new Event("runComplete"));
		return true;
	}

	/**
	 *  Go through the currentSteps, executing each one.
	 *  Returns true if no test steps required waiting.
	 *  Returns false if we have to wait for an event before
	 *  continuing.
	 */
	private function runSteps():Boolean
	{
		while (currentIndex < numSteps)
		{
			// return if a step failed
			if (testResult.hasStatus())
				return true;

			/*
			    The following lets you step each step but makes tests
			    more sensitive to which step actually sends the event.
				For example, cb.mouseDown/mouseUp, the tween starts on
				mousedown and fires its event before you step.  Another
				example is asserting with waitEvent=updateComplete.  It
				could have fired a long time before you step
			*/
			if (UnitTester.playbackControl == "pause")
			{
				UnitTester.callback = pauseHandler;
				needCompleteEvent = true;
				return false;
			}
			else if (UnitTester.playbackControl == "step")
				UnitTester.playbackControl = "pause";

			var step:TestStep = currentSteps[currentIndex];
			if (!(step is Assert))
			{
				// look at subsequent steps for Asserts and set them up early
				for (var j:int = currentIndex + 1; j < numSteps; j++)
				{
					// scan following asserts for AssertEvents and set them up early
					var nextStep:TestStep = currentSteps[j];
					if (nextStep is Assert)
					{
						Assert(nextStep).preview(root, context, this, testResult);
						// do a check to be sure folks are using AssertEventPropertyValue correctly
						if (nextStep is AssertEventPropertyValue)
						{
							// AEPV must follow an AssertEvent or another AEPV
							if (j == 0 || !(currentSteps[j-1] is AssertEvent || currentSteps[j-1] is AssertEventPropertyValue))
								TestOutput.logResult("WARNING: AssertEventPropertyValue may be missing preceding AssertEvent");
						}
						else if (nextStep is AssertError)
						{
							if (step is SetProperty)
								SetProperty(step).expectError = true;
						}
					}
					else
						break;
				}
			}
			if (UnitTester.verboseMode)
			{
				TestOutput.logResult(step.toString());
			}
			UnitTester.lastStep = step;
			UnitTester.lastStepLine = currentIndex;
			step.addEventListener("stepComplete", stepCompleteHandler);
			if (!step.execute(root, context, this, testResult))
			{
				needCompleteEvent = true;
				return false;
			}
			step.removeEventListener("stepComplete", stepCompleteHandler);
			currentIndex++;
		}
		return true;
	}

	private function stepCompleteHandler(event:Event):void
	{
		var step:TestStep = currentSteps[currentIndex];
		step.removeEventListener("stepComplete", stepCompleteHandler);
		if (UnitTester.playbackControl == "play")
			UnitTester.callback = runNextSteps;
		else
		{
			currentIndex++;
			UnitTester.callback = pauseHandler;
		}
	}

	/**
	 *  Handler for timer events to see if we've waited too long
	 */
	private function timerHandler(event:Event):void
	{
		if (expirationTime > 0)
			if (expirationTime < getTimer())
			{
				var step:TestStep = currentSteps[currentIndex];
				step.timeoutCallback();
			}
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function runNextSteps():void
	{
		currentIndex++;
		if (currentSteps == setup)
			runSetup(true);
		else if (currentSteps == body)
			runBody(true);
		else if (currentSteps == cleanup)
			runCleanup(true);
		else
			runComplete();
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function continueSteps():void
	{
		if (currentSteps == setup)
			runSetup(true);
		else if (currentSteps == body)
			runBody(true);
		else if (currentSteps == cleanup)
			runCleanup(true);
		else
			runComplete();
	}

	/**
	 *  Determines which set of steps (setup, body, cleanup) to run next
	 */
	private function pauseHandler():void
	{
		if (UnitTester.playbackControl == "step")
			continueSteps();
		else if (UnitTester.playbackControl == "play")
			continueSteps();
		else
			UnitTester.callback = pauseHandler;
	}
}

}

Fix random timeouts in mobile tests that use step_timeout/timeout_plus.

git-svn-id: f8d5f742b420a6a114b58659a4f85f335318e53c@1433777 13f79535-47bb-0310-9956-ffa450edef68


/* 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 org.mangui.hls.controller.BufferThresholdController;
    import org.mangui.hls.event.HLSPlayMetrics;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.constant.HLSSeekStates;
    import org.mangui.hls.constant.HLSPlayStates;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.utils.Hex;

    import flash.events.Event;
    import flash.events.NetStatusEvent;
    import flash.events.TimerEvent;
    import flash.net.*;
    import flash.utils.*;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    /** Class that overrides standard flash.net.NetStream class, keeps the buffer filled, handles seek and play state
     *
     * play state transition :
     * 				FROM								TO								condition
     *  HLSPlayStates.IDLE              	HLSPlayStates.PLAYING_BUFFERING     play()/play2()/seek() called
     *  HLSPlayStates.PLAYING_BUFFERING  	HLSPlayStates.PLAYING  				buflen > minBufferLength
     *  HLSPlayStates.PAUSED_BUFFERING  	HLSPlayStates.PAUSED  				buflen > minBufferLength
     *  HLSPlayStates.PLAYING  				HLSPlayStates.PLAYING_BUFFERING  	buflen < lowBufferLength
     *  HLSPlayStates.PAUSED  				HLSPlayStates.PAUSED_BUFFERING  	buflen < lowBufferLength
     */
    public class HLSNetStream extends NetStream {
        /** Reference to the framework controller. **/
        private var _hls : HLS;
        /** reference to buffer threshold controller */
        private var _bufferThresholdController : BufferThresholdController;
        /** FLV Tag Buffer . **/
        private var _streamBuffer : StreamBuffer;
        /** means that last fragment of a VOD playlist has been loaded */
        private var _reached_vod_end : Boolean;
        /** Timer used to check buffer and position. **/
        private var _timer : Timer;
        /** Current playback state. **/
        private var _playbackState : String;
        /** Current seek state. **/
        private var _seekState : String;
        /** current playback level **/
        private var _playbackLevel : int;
        /** Netstream client proxy */
        private var _client : HLSNetStreamClient;

        /** Create the buffer. **/
        public function HLSNetStream(connection : NetConnection, hls : HLS, streamBuffer : StreamBuffer) : void {
            super(connection);
            super.bufferTime = 0.1;
            _hls = hls;
            _bufferThresholdController = new BufferThresholdController(hls);
            _streamBuffer = streamBuffer;
            _hls.addEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, _lastVODFragmentLoadedHandler);
            _playbackState = HLSPlayStates.IDLE;
            _seekState = HLSSeekStates.IDLE;
            _timer = new Timer(100, 0);
            _timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
            _client = new HLSNetStreamClient();
            _client.registerCallback("onHLSFragmentChange", onHLSFragmentChange);
            _client.registerCallback("onID3Data", onID3Data);
            super.client = _client;
        };

        public function onHLSFragmentChange(level : int, seqnum : int, cc : int, audio_only : Boolean, program_date : Number, width : int, height : int, ... tags) : void {
            CONFIG::LOGGING {
                Log.debug("playing fragment(level/sn/cc):" + level + "/" + seqnum + "/" + cc);
            }
            _playbackLevel = level;
            var tag_list : Array = new Array();
            for (var i : uint = 0; i < tags.length; i++) {
                tag_list.push(tags[i]);
                CONFIG::LOGGING {
                    Log.debug("custom tag:" + tags[i]);
                }
            }
            _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_PLAYING, new HLSPlayMetrics(level, seqnum, cc, audio_only, program_date, width, height, tag_list)));
        }

        // function is called by SCRIPT in FLV
        public function onID3Data(data : ByteArray) : void {
            var dump : String = "unset";

            // we dump the content as hex to get it to the Javascript in the browser.
            // from lots of searching, we could use base64, but even then, the decode would
            // not be native, so hex actually seems more efficient
            dump = Hex.fromArray(data);

            CONFIG::LOGGING {
                Log.debug("id3:" + dump);
            }
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ID3_UPDATED, dump));
        }

        /** timer function, check/update NetStream state, and append tags if needed **/
        private function _checkBuffer(e : Event) : void {
            var buffer : Number = this.bufferLength;
            // Log.info("netstream/total:" + super.bufferLength + "/" + this.bufferLength);
            // Set playback state. no need to check buffer status if seeking
            if (_seekState != HLSSeekStates.SEEKING) {
                // check low buffer condition
                if (buffer <= 0.1) {
                    if (_reached_vod_end) {
                        // Last tag done? Then append sequence end.
                        super.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
                        super.appendBytes(new ByteArray());
                        // reach end of playlist + playback complete (as buffer is empty).
                        // stop timer, report event and switch to IDLE mode.
                        _timer.stop();
                        CONFIG::LOGGING {
                            Log.debug("reached end of VOD playlist, notify playback complete");
                        }
                        _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_COMPLETE));
                        _setPlaybackState(HLSPlayStates.IDLE);
                        _setSeekState(HLSSeekStates.IDLE);
                        return;
                    } else {
                        // buffer <= 0.1 and not EOS, pause playback
                        super.pause();
                    }
                }
                // if buffer len is below lowBufferLength, get into buffering state
                if (!_reached_vod_end && buffer < _bufferThresholdController.lowBufferLength) {
                    if (_playbackState == HLSPlayStates.PLAYING) {
                        // low buffer condition and play state. switch to play buffering state
                        _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
                    } else if (_playbackState == HLSPlayStates.PAUSED) {
                        // low buffer condition and pause state. switch to paused buffering state
                        _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
                    }
                }
                // if buffer len is above minBufferLength, get out of buffering state
                if (buffer >= _bufferThresholdController.minBufferLength || _reached_vod_end) {
                    if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
                        CONFIG::LOGGING {
                            Log.debug("resume playback");
                        }
                        // resume playback in case it was paused, this can happen if buffer was in really low condition (less than 0.1s)
                        super.resume();
                        _setPlaybackState(HLSPlayStates.PLAYING);
                    } else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
                        _setPlaybackState(HLSPlayStates.PAUSED);
                    }
                }
            }
        };

        /** Return the current playback state. **/
        public function get playbackState() : String {
            return _playbackState;
        };

        /** Return the current seek state. **/
        public function get seekState() : String {
            return _seekState;
        };

        /** Return the current playback quality level **/
        public function get playbackLevel() : int {
            return _playbackLevel;
        };

        /** append tags to NetStream **/
        public function appendTags(tags : Vector.<FLVTag>) : void {
            if (_seekState == HLSSeekStates.SEEKING) {
                /* this is our first injection after seek(),
                let's flush netstream now
                this is to avoid black screen during seek command */
                super.close();
                CONFIG::FLASH_11_1 {
                    try {
                        super.useHardwareDecoder = HLSSettings.useHardwareVideoDecoder;
                    } catch(e : Error) {
                    }
                }
                super.play(null);
                super.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
                // immediatly pause NetStream, it will be resumed when enough data will be buffered in the NetStream
                super.pause();
            }
            // append all tags
            for each (var tagBuffer : FLVTag in tags) {
                try {
                    if (tagBuffer.type == FLVTag.DISCONTINUITY) {
                        super.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
                        super.appendBytes(FLVTag.getHeader());
                    }
                    super.appendBytes(tagBuffer.data);
                } catch (error : Error) {
                    var hlsError : HLSError = new HLSError(HLSError.TAG_APPENDING_ERROR, null, tagBuffer.type + ": " + error.message);
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
                }
            }
            if (_seekState == HLSSeekStates.SEEKING) {
                // dispatch event to mimic NetStream behaviour
                dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, {code:"NetStream.Seek.Notify", level:"status"}));
                _setSeekState(HLSSeekStates.SEEKED);
            }
        };

        private function _lastVODFragmentLoadedHandler(event : HLSEvent) : void {
            CONFIG::LOGGING {
                Log.debug("last fragment loaded");
            }
            _reached_vod_end = true;
        }

        /** Change playback state. **/
        private function _setPlaybackState(state : String) : void {
            if (state != _playbackState) {
                CONFIG::LOGGING {
                    Log.debug('[PLAYBACK_STATE] from ' + _playbackState + ' to ' + state);
                }
                _playbackState = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_STATE, _playbackState));
            }
        };

        /** Change seeking state. **/
        private function _setSeekState(state : String) : void {
            if (state != _seekState) {
                CONFIG::LOGGING {
                    Log.debug('[SEEK_STATE] from ' + _seekState + ' to ' + state);
                }
                _seekState = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.SEEK_STATE, _seekState));
            }
        };

        override public function play(...args) : void {
            var _playStart : Number;
            if (args.length >= 2) {
                _playStart = Number(args[1]);
            } else {
                _playStart = -1;
            }
            CONFIG::LOGGING {
                Log.info("HLSNetStream:play(" + _playStart + ")");
            }
            seek(_playStart);
            _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
        }

        override public function play2(param : NetStreamPlayOptions) : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:play2(" + param.start + ")");
            }
            seek(param.start);
            _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
        }

        /** Pause playback. **/
        override public function pause() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:pause");
            }
            if (_playbackState == HLSPlayStates.PLAYING) {
                super.pause();
                _setPlaybackState(HLSPlayStates.PAUSED);
            } else if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
                super.pause();
                _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
            }
        };

        /** Resume playback. **/
        override public function resume() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:resume");
            }
            if (_playbackState == HLSPlayStates.PAUSED) {
                super.resume();
                _setPlaybackState(HLSPlayStates.PLAYING);
            } else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
                // dont resume NetStream here, it will be resumed by Timer. this avoids resuming playback while seeking is in progress
                _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
            }
        };

        /** get Buffer Length  **/
        override public function get bufferLength() : Number {
            return netStreamBufferLength + _streamBuffer.bufferLength;
        };

        public function get netStreamBufferLength() : Number {
            if (_seekState == HLSSeekStates.SEEKING) {
                return 0;
            } else {
                return super.bufferLength;
            }
        };

        /** Start playing data in the buffer. **/
        override public function seek(position : Number) : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:seek(" + position + ")");
            }
            _streamBuffer.seek(position);
            _reached_vod_end = false;

            _setSeekState(HLSSeekStates.SEEKING);
            /* if HLS was in paused state before seeking,
             * switch to paused buffering state
             * otherwise, switch to playing buffering state
             */
            switch(_playbackState) {
                case HLSPlayStates.PAUSED:
                case HLSPlayStates.PAUSED_BUFFERING:
                    _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
                    break;
                case HLSPlayStates.PLAYING:
                case HLSPlayStates.PLAYING_BUFFERING:
                    _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
                    break;
                default:
                    break;
            }
            /* always pause NetStream while seeking, even if we are in play state
             * in that case, NetStream will be resumed after first fragment loading
             */
            super.pause();
            _timer.start();
        };

        public override function set client(client : Object) : void {
            _client.delegate = client;
        };

        public override function get client() : Object {
            return _client.delegate;
        }

        /** Stop playback. **/
        override public function close() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:close");
            }
            super.close();
            _streamBuffer.stop();
            _timer.stop();
            _setPlaybackState(HLSPlayStates.IDLE);
            _setSeekState(HLSSeekStates.IDLE);
        };

        public function dispose_() : void {
            close();
            _bufferThresholdController.dispose();
            _hls.removeEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, _lastVODFragmentLoadedHandler);
        }
    }
}

/* 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 org.mangui.hls.controller.BufferThresholdController;
    import org.mangui.hls.event.HLSPlayMetrics;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.constant.HLSSeekStates;
    import org.mangui.hls.constant.HLSPlayStates;
    import org.mangui.hls.flv.FLVTag;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.utils.Hex;

    import flash.events.Event;
    import flash.events.NetStatusEvent;
    import flash.events.TimerEvent;
    import flash.net.*;
    import flash.utils.*;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    /** Class that overrides standard flash.net.NetStream class, keeps the buffer filled, handles seek and play state
     *
     * play state transition :
     * 				FROM								TO								condition
     *  HLSPlayStates.IDLE              	HLSPlayStates.PLAYING_BUFFERING     play()/play2()/seek() called
     *  HLSPlayStates.PLAYING_BUFFERING  	HLSPlayStates.PLAYING  				buflen > minBufferLength
     *  HLSPlayStates.PAUSED_BUFFERING  	HLSPlayStates.PAUSED  				buflen > minBufferLength
     *  HLSPlayStates.PLAYING  				HLSPlayStates.PLAYING_BUFFERING  	buflen < lowBufferLength
     *  HLSPlayStates.PAUSED  				HLSPlayStates.PAUSED_BUFFERING  	buflen < lowBufferLength
     */
    public class HLSNetStream extends NetStream {
        /** Reference to the framework controller. **/
        private var _hls : HLS;
        /** reference to buffer threshold controller */
        private var _bufferThresholdController : BufferThresholdController;
        /** FLV Tag Buffer . **/
        private var _streamBuffer : StreamBuffer;
        /** means that last fragment of a VOD playlist has been loaded */
        private var _reached_vod_end : Boolean;
        /** Timer used to check buffer and position. **/
        private var _timer : Timer;
        /** Current playback state. **/
        private var _playbackState : String;
        /** Current seek state. **/
        private var _seekState : String;
        /** current playback level **/
        private var _playbackLevel : int;
        /** Netstream client proxy */
        private var _client : HLSNetStreamClient;

        /** Create the buffer. **/
        public function HLSNetStream(connection : NetConnection, hls : HLS, streamBuffer : StreamBuffer) : void {
            super(connection);
            super.bufferTime = 0.1;
            _hls = hls;
            _bufferThresholdController = new BufferThresholdController(hls);
            _streamBuffer = streamBuffer;
            _hls.addEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, _lastVODFragmentLoadedHandler);
            _playbackState = HLSPlayStates.IDLE;
            _seekState = HLSSeekStates.IDLE;
            _timer = new Timer(100, 0);
            _timer.addEventListener(TimerEvent.TIMER, _checkBuffer);
            _client = new HLSNetStreamClient();
            _client.registerCallback("onHLSFragmentChange", onHLSFragmentChange);
            _client.registerCallback("onID3Data", onID3Data);
            super.client = _client;
        };

        public function onHLSFragmentChange(level : int, seqnum : int, cc : int, audio_only : Boolean, program_date : Number, width : int, height : int, ... tags) : void {
            CONFIG::LOGGING {
                Log.debug("playing fragment(level/sn/cc):" + level + "/" + seqnum + "/" + cc);
            }
            _playbackLevel = level;
            var tag_list : Array = new Array();
            for (var i : uint = 0; i < tags.length; i++) {
                tag_list.push(tags[i]);
                CONFIG::LOGGING {
                    Log.debug("custom tag:" + tags[i]);
                }
            }
            _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_PLAYING, new HLSPlayMetrics(level, seqnum, cc, audio_only, program_date, width, height, tag_list)));
        }

        // function is called by SCRIPT in FLV
        public function onID3Data(data : ByteArray) : void {
            var dump : String = "unset";

            // we dump the content as hex to get it to the Javascript in the browser.
            // from lots of searching, we could use base64, but even then, the decode would
            // not be native, so hex actually seems more efficient
            dump = Hex.fromArray(data);

            CONFIG::LOGGING {
                Log.debug("id3:" + dump);
            }
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ID3_UPDATED, dump));
        }

        /** timer function, check/update NetStream state, and append tags if needed **/
        private function _checkBuffer(e : Event) : void {
            var buffer : Number = this.bufferLength;
            // Log.info("netstream/total:" + super.bufferLength + "/" + this.bufferLength);
            // Set playback state. no need to check buffer status if seeking
            if (_seekState != HLSSeekStates.SEEKING) {
                // check low buffer condition
                if (buffer <= 0.1) {
                    if (_reached_vod_end) {
                        // Last tag done? Then append sequence end.
                        super.appendBytesAction(NetStreamAppendBytesAction.END_SEQUENCE);
                        super.appendBytes(new ByteArray());
                        // reach end of playlist + playback complete (as buffer is empty).
                        // stop timer, report event and switch to IDLE mode.
                        _timer.stop();
                        CONFIG::LOGGING {
                            Log.debug("reached end of VOD playlist, notify playback complete");
                        }
                        _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_COMPLETE));
                        _setPlaybackState(HLSPlayStates.IDLE);
                        _setSeekState(HLSSeekStates.IDLE);
                        return;
                    } else {
                        // buffer <= 0.1 and not EOS, pause playback
                        super.pause();
                    }
                }
                // if buffer len is below lowBufferLength, get into buffering state
                if (!_reached_vod_end && buffer < _bufferThresholdController.lowBufferLength) {
                    if (_playbackState == HLSPlayStates.PLAYING) {
                        // low buffer condition and play state. switch to play buffering state
                        _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
                    } else if (_playbackState == HLSPlayStates.PAUSED) {
                        // low buffer condition and pause state. switch to paused buffering state
                        _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
                    }
                }
                // if buffer len is above minBufferLength, get out of buffering state
                if (buffer >= _bufferThresholdController.minBufferLength || _reached_vod_end) {
                    if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
                        CONFIG::LOGGING {
                            Log.debug("resume playback");
                        }
                        // resume playback in case it was paused, this can happen if buffer was in really low condition (less than 0.1s)
                        super.resume();
                        _setPlaybackState(HLSPlayStates.PLAYING);
                    } else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
                        _setPlaybackState(HLSPlayStates.PAUSED);
                    }
                }
            }
        };

        /** Return the current playback state. **/
        public function get playbackState() : String {
            return _playbackState;
        };

        /** Return the current seek state. **/
        public function get seekState() : String {
            return _seekState;
        };

        /** Return the current playback quality level **/
        public function get playbackLevel() : int {
            return _playbackLevel;
        };

        /** append tags to NetStream **/
        public function appendTags(tags : Vector.<FLVTag>) : void {
            if (_seekState == HLSSeekStates.SEEKING) {
                /* this is our first injection after seek(),
                let's flush netstream now
                this is to avoid black screen during seek command */
                super.close();
                CONFIG::FLASH_11_1 {
                    try {
                        super.useHardwareDecoder = HLSSettings.useHardwareVideoDecoder;
                    } catch(e : Error) {
                    }
                }
                super.play(null);
                super.appendBytesAction(NetStreamAppendBytesAction.RESET_SEEK);
                // immediatly pause NetStream, it will be resumed when enough data will be buffered in the NetStream
                super.pause();
            }
            // append all tags
            for each (var tagBuffer : FLVTag in tags) {
                try {
                    if (tagBuffer.type == FLVTag.DISCONTINUITY) {
                        super.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN);
                        super.appendBytes(FLVTag.getHeader());
                    }
                    super.appendBytes(tagBuffer.data);
                } catch (error : Error) {
                    var hlsError : HLSError = new HLSError(HLSError.TAG_APPENDING_ERROR, null, tagBuffer.type + ": " + error.message);
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
                }
            }
            if (_seekState == HLSSeekStates.SEEKING) {
                // dispatch event to mimic NetStream behaviour
                dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, {code:"NetStream.Seek.Notify", level:"status"}));
                _setSeekState(HLSSeekStates.SEEKED);
            }
        };

        private function _lastVODFragmentLoadedHandler(event : HLSEvent) : void {
            CONFIG::LOGGING {
                Log.debug("last fragment loaded");
            }
            _reached_vod_end = true;
        }

        /** Change playback state. **/
        private function _setPlaybackState(state : String) : void {
            if (state != _playbackState) {
                CONFIG::LOGGING {
                    Log.debug('[PLAYBACK_STATE] from ' + _playbackState + ' to ' + state);
                }
                _playbackState = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYBACK_STATE, _playbackState));
            }
        };

        /** Change seeking state. **/
        private function _setSeekState(state : String) : void {
            if (state != _seekState) {
                CONFIG::LOGGING {
                    Log.debug('[SEEK_STATE] from ' + _seekState + ' to ' + state);
                }
                _seekState = state;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.SEEK_STATE, _seekState));
            }
        };

        override public function play(...args) : void {
            var _playStart : Number;
            if (args.length >= 2) {
                _playStart = Number(args[1]);
            } else {
                _playStart = -1;
            }
            CONFIG::LOGGING {
                Log.info("HLSNetStream:play(" + _playStart + ")");
            }
            seek(_playStart);
            _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
        }

        override public function play2(param : NetStreamPlayOptions) : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:play2(" + param.start + ")");
            }
            seek(param.start);
            _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
        }

        /** Pause playback. **/
        override public function pause() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:pause");
            }
            if (_playbackState == HLSPlayStates.PLAYING) {
                super.pause();
                _setPlaybackState(HLSPlayStates.PAUSED);
            } else if (_playbackState == HLSPlayStates.PLAYING_BUFFERING) {
                super.pause();
                _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
            }
        };

        /** Resume playback. **/
        override public function resume() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:resume");
            }
            if (_playbackState == HLSPlayStates.PAUSED) {
                super.resume();
                _setPlaybackState(HLSPlayStates.PLAYING);
            } else if (_playbackState == HLSPlayStates.PAUSED_BUFFERING) {
                // dont resume NetStream here, it will be resumed by Timer. this avoids resuming playback while seeking is in progress
                _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
            }
        };

        /** get Buffer Length  **/
        override public function get bufferLength() : Number {
            return netStreamBufferLength + _streamBuffer.bufferLength;
        };

        public function get netStreamBufferLength() : Number {
            if (_seekState == HLSSeekStates.SEEKING) {
                return 0;
            } else {
                return super.bufferLength;
            }
        };

        /** Start playing data in the buffer. **/
        override public function seek(position : Number) : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:seek(" + position + ")");
            }
            _streamBuffer.seek(position);
            _reached_vod_end = false;

            _setSeekState(HLSSeekStates.SEEKING);
            /* if HLS was in paused state before seeking,
             * switch to paused buffering state
             * otherwise, switch to playing buffering state
             */
            switch(_playbackState) {
                case HLSPlayStates.PAUSED:
                case HLSPlayStates.PAUSED_BUFFERING:
                    _setPlaybackState(HLSPlayStates.PAUSED_BUFFERING);
                    break;
                case HLSPlayStates.PLAYING:
                case HLSPlayStates.PLAYING_BUFFERING:
                    _setPlaybackState(HLSPlayStates.PLAYING_BUFFERING);
                    break;
                default:
                    break;
            }
            /* always pause NetStream while seeking, even if we are in play state
             * in that case, NetStream will be resumed during next call to appendTags()
             */
            super.pause();
            _timer.start();
        };

        public override function set client(client : Object) : void {
            _client.delegate = client;
        };

        public override function get client() : Object {
            return _client.delegate;
        }

        /** Stop playback. **/
        override public function close() : void {
            CONFIG::LOGGING {
                Log.info("HLSNetStream:close");
            }
            super.close();
            _streamBuffer.stop();
            _timer.stop();
            _setPlaybackState(HLSPlayStates.IDLE);
            _setSeekState(HLSSeekStates.IDLE);
        };

        public function dispose_() : void {
            close();
            _bufferThresholdController.dispose();
            _hls.removeEventListener(HLSEvent.LAST_VOD_FRAGMENT_LOADED, _lastVODFragmentLoadedHandler);
        }
    }
}

adjust comments

adjust comments


package org.servebox.cafe.core.command
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;

	import org.servebox.cafe.core.signal.SignalAggregator;

	public class AbstractStateCommand extends EventDispatcher implements IStateCommand
	{
		public function AbstractStateCommand(target:IEventDispatcher=null)
		{
			super(target);
		}

		[Autowired]
		public var signalAggregator : SignalAggregator;

		private var _parameters : Array;

		public function get executable():Boolean
		{
			return true;
		}

		public function set executable(value:Boolean):void
		{
		}

		public function get parameters():Array
		{
			return _parameters;
		}

		public function set parameters(value:Array):void
		{
			_parameters = value;
		}

		protected function getParameter( key : String ) : IParameterObject
		{
			var paramToReturn : IParameterObject;
			for each ( var param : IParameterObject in _parameters )
			{
				if ( param.key == key )
				{
					paramToReturn = param;
				}
			}
			if ( !paramToReturn )
			{
				throw new Error("No parameter " + key + " in " + this + " command, check command binding ");
			}
			return paramToReturn;
		}

		public function execute( e : Event = null ) : void
		{
		}
	}
}

package org.servebox.cafe.core.command
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;

	import org.servebox.cafe.core.signal.SignalAggregator;

	public class AbstractStateCommand extends EventDispatcher implements IStateCommand
	{
		public function AbstractStateCommand(target:IEventDispatcher=null)
		{
			super(target);
		}

		[Autowired]
		public var signalAggregator : SignalAggregator;

		private var _parameters : Array;

		public function get executable():Boolean
		{
			return true;
		}

		public function set executable(value:Boolean):void
		{
		}

		public function get parameters():Array
		{
			if ( _parameters == null )
			{
				_parameters = new Array();
			}
			return _parameters;
		}

		public function set parameters(value:Array):void
		{
			_parameters = value;
		}

		protected function getParameter( key : String ) : IParameterObject
		{
			var paramToReturn : IParameterObject;
			for each ( var param : IParameterObject in _parameters )
			{
				if ( param.key == key )
				{
					paramToReturn = param;
				}
			}
			if ( !paramToReturn )
			{
				throw new Error("No parameter " + key + " in " + this + " command, check command binding ");
			}
			return paramToReturn;
		}

		public function addParameter( key : String , value : Object ) : void
		{
			var param : AbstractParameterObject = new AbstractParameterObject( key, value );
			parameters.push( param );
		}

		public function execute( e : Event = null ) : void
		{
		}
	}
}

Add addParameter method.

Add addParameter method.


package aerys.minko.render.shader.compiler.graph
{
	import aerys.minko.Minko;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.Signature;
	import aerys.minko.render.shader.compiler.graph.nodes.AbstractNode;
	import aerys.minko.render.shader.compiler.graph.nodes.vertex.Extract;
	import aerys.minko.render.shader.compiler.graph.nodes.vertex.Instruction;
	import aerys.minko.render.shader.compiler.graph.nodes.vertex.Interpolate;
	import aerys.minko.render.shader.compiler.graph.nodes.vertex.Overwriter;
	import aerys.minko.render.shader.compiler.graph.nodes.vertex.VariadicExtract;
	import aerys.minko.render.shader.compiler.graph.visitors.*;
	import aerys.minko.render.shader.compiler.register.Components;
	import aerys.minko.render.shader.compiler.sequence.AgalInstruction;
	import aerys.minko.type.log.DebugLevel;
	import aerys.minko.render.geometry.stream.format.VertexComponent;

	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.Endian;

	/**
	 * @private
	 * @author Romain Gilliotte
	 *
	 */
	public class ShaderGraph
	{
		private static const SPLITTER				: SplitterVisitor						= new SplitterVisitor();
		private static const REMOVE_EXTRACT			: RemoveExtractsVisitor					= new RemoveExtractsVisitor();
		private static const MERGER					: MergeVisitor							= new MergeVisitor();
		private static const OVERWRITER_CLEANER		: OverwriterCleanerVisitor				= new OverwriterCleanerVisitor();
		private static const RESOLVE_CONSTANT		: ResolveConstantComputationVisitor		= new ResolveConstantComputationVisitor();
		private static const CONSTANT_PACKER		: ConstantPackerVisitor					= new ConstantPackerVisitor();
		private static const CONSTANT_GROUPER		: ConstantGrouperVisitor				= new ConstantGrouperVisitor();
		private static const RESOLVE_PARAMETRIZED	: ResolveParametrizedComputationVisitor	= new ResolveParametrizedComputationVisitor();
		private static const REMOVE_USELESS			: RemoveUselessComputation				= new RemoveUselessComputation();
		private static const COPY_INSERTER			: CopyInserterVisitor					= new CopyInserterVisitor();
		private static const ALLOCATOR				: AllocationVisitor						= new AllocationVisitor();
		private static const INTERPOLATE_FINDER		: InterpolateFinder						= new InterpolateFinder();
		private static const WRITE_DOT				: WriteDot								= new WriteDot();
		private static const MATRIX_TRANSFORMATION	: MatrixTransformationGrouper			= new MatrixTransformationGrouper();

		private var _position				: AbstractNode;
		private var _positionComponents		: uint;
		private var _interpolates			: Vector.<AbstractNode>;
		private var _color					: AbstractNode;
		private var _colorComponents		: uint;
		private var _kills					: Vector.<AbstractNode>;
		private var _killComponents			: Vector.<uint>;
		private var _computableConstants	: Object;

		private var _isCompiled				: Boolean;

		private var _vertexSequence			: Vector.<AgalInstruction>;
		private var _fragmentSequence		: Vector.<AgalInstruction>;
		private var _bindings				: Object;

		private var _vertexComponents		: Vector.<VertexComponent>;
		private var _vertexIndices			: Vector.<uint>;
		private var _vsConstants			: Vector.<Number>;
		private var _fsConstants			: Vector.<Number>;
		private var _textures				: Vector.<ITextureResource>;

		public function get position() : AbstractNode
		{
			return _position;
		}
		public function set position(v : AbstractNode)	: void
		{
			_position = v;
		}

		public function get interpolates() : Vector.<AbstractNode>
		{
			return _interpolates;
		}

		public function get color() : AbstractNode
		{
			return _color;
		}
		public function set color(v : AbstractNode)	: void
		{
			_color = v;
		}

		public function get kills() : Vector.<AbstractNode>
		{
			return _kills;
		}

		public function get positionComponents() : uint
		{
			return _positionComponents;
		}
		public function set positionComponents(v : uint) : void
		{
			_positionComponents = v;
		}

		public function get colorComponents() : uint
		{
			return _colorComponents;
		}
		public function set colorComponents(v : uint) : void
		{
			_colorComponents = v;
		}

		public function get killComponents() : Vector.<uint>
		{
			return _killComponents;
		}

		public function get computableConstants() : Object
		{
			return _computableConstants;
		}

		public function ShaderGraph(position	: AbstractNode,
									color		: AbstractNode,
									kills		: Vector.<AbstractNode>)
		{
			_isCompiled				= false;

			_position				= position;
			_positionComponents		= Components.createContinuous(0, 0, 4, position.size);
			_interpolates			= new Vector.<AbstractNode>();
			_color					= color;
			_colorComponents		= Components.createContinuous(0, 0, 4, color.size);
			_kills					= kills;
			_killComponents			= new Vector.<uint>();
			_computableConstants	= new Object();

			var numKills : uint = kills.length;
			for (var killId : uint = 0; killId < numKills; ++killId)
				_killComponents[killId] = Components.createContinuous(0, 0, 1, 1);
		}

		public function generateProgram(name : String, signature : Signature) : Program3DResource
		{
			if (!_isCompiled)
				compile();

			if (Minko.debugLevel & DebugLevel.SHADER_AGAL)
				Minko.log(DebugLevel.SHADER_AGAL, generateAGAL(name));

			var vsProgram	: ByteArray = computeBinaryProgram(_vertexSequence, true);
			var fsProgram	: ByteArray = computeBinaryProgram(_fragmentSequence, false);

			var program : Program3DResource = new Program3DResource(
				name,
				signature,
				vsProgram,
				fsProgram,
				_vertexComponents,
				_vertexIndices,
				_vsConstants,
				_fsConstants,
				_textures,
				_bindings
			);

			return program;
		}

		public function generateAGAL(name : String) : String
		{
			var instruction	: AgalInstruction;
			var shader		: String = name + "\n";

			if (!_isCompiled)
				compile();

			shader += "- vertex shader\n";
			for each (instruction in _vertexSequence)
				shader += instruction.getAgal(true);

			shader += "- fragment shader\n";
			for each (instruction in _fragmentSequence)
				shader += instruction.getAgal(false);

			return shader;
		}

		private function compile() : void
		{
			// execute consecutive visitors to optimize the shader graph.
			// Warning: the order matters, do not swap lines.
			MERGER					.process(this);	// merge duplicate nodes
			REMOVE_EXTRACT			.process(this);	// remove all extract nodes
			OVERWRITER_CLEANER		.process(this);	// remove nested overwriters
			RESOLVE_CONSTANT		.process(this);	// resolve constant computation
			CONSTANT_PACKER			.process(this);	// pack constants [0,0,0,1] => [0,1].xxxy
//			REMOVE_USELESS			.process(this);	// remove some useless operations (add 0, mul 0, mul 1...)
			RESOLVE_PARAMETRIZED	.process(this);	// replace computations that depend on parameters by evalexp parameters
//			MATRIX_TRANSFORMATION	.process(this);	// replace ((vector * matrix1) * matrix2) by vector * (matrix1 * matrix2) to save registers on GPU
			COPY_INSERTER			.process(this);	// ensure there are no operations between constants
			SPLITTER				.process(this);	// clone nodes that are shared between vertex and fragment shader
			CONSTANT_GROUPER		.process(this);	// group constants [0,1] & [0,2] => [0, 1, 2]

			if (Minko.debugLevel & DebugLevel.SHADER_DOTTY)
			{
				WRITE_DOT.process(this);
				Minko.log(DebugLevel.SHADER_DOTTY, WRITE_DOT.result);
				WRITE_DOT.clear();
			}

			// generate final program
			INTERPOLATE_FINDER		.process(this);	// find interpolate nodes. We may skip that in the future.
			ALLOCATOR				.process(this);	// allocate memory and generate final code.

			// retrieve program
			_vertexSequence		= ALLOCATOR.vertexSequence;
			_fragmentSequence	= ALLOCATOR.fragmentSequence;
			_bindings			= ALLOCATOR.parameterBindings;
			_vertexComponents	= ALLOCATOR.vertexComponents;
			_vertexIndices		= ALLOCATOR.vertexIndices;
			_vsConstants		= ALLOCATOR.vertexConstants;
			_fsConstants		= ALLOCATOR.fragmentConstants;
			_textures			= ALLOCATOR.textures;

			ALLOCATOR.clear();

			_isCompiled = true;
		}

		private function computeBinaryProgram(sequence			: Vector.<AgalInstruction>,
											  isVertexShader	: Boolean) : ByteArray
		{
			var program : ByteArray = new ByteArray();

			program.endian = Endian.LITTLE_ENDIAN;
			program.writeByte(0xa0);					// tag version
			program.writeUnsignedInt(1);				// AGAL version, big endian, bit pattern will be 0x01000000
			program.writeByte(0xa1);					// tag program id
			program.writeByte(isVertexShader ? 0 : 1);	// vertex or fragment

			for each (var instruction : AgalInstruction in sequence)
				instruction.getBytecode(program);

			return program;
		}
	}
}

package aerys.minko.render.shader.compiler.graph
{
	import aerys.minko.Minko;
	import aerys.minko.render.geometry.stream.format.VertexComponent;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.Signature;
	import aerys.minko.render.shader.compiler.graph.nodes.AbstractNode;
	import aerys.minko.render.shader.compiler.graph.visitors.*;
	import aerys.minko.render.shader.compiler.register.Components;
	import aerys.minko.render.shader.compiler.sequence.AgalInstruction;
	import aerys.minko.type.log.DebugLevel;

	import flash.utils.ByteArray;
	import flash.utils.Endian;

	/**
	 * @private
	 * @author Romain Gilliotte
	 *
	 */
	public class ShaderGraph
	{
		private static const SPLITTER				: SplitterVisitor						= new SplitterVisitor();
		private static const REMOVE_EXTRACT			: RemoveExtractsVisitor					= new RemoveExtractsVisitor();
		private static const MERGER					: MergeVisitor							= new MergeVisitor();
		private static const OVERWRITER_CLEANER		: OverwriterCleanerVisitor				= new OverwriterCleanerVisitor();
		private static const RESOLVE_CONSTANT		: ResolveConstantComputationVisitor		= new ResolveConstantComputationVisitor();
		private static const CONSTANT_PACKER		: ConstantPackerVisitor					= new ConstantPackerVisitor();
		private static const CONSTANT_GROUPER		: ConstantGrouperVisitor				= new ConstantGrouperVisitor();
		private static const RESOLVE_PARAMETRIZED	: ResolveParametrizedComputationVisitor	= new ResolveParametrizedComputationVisitor();
		private static const REMOVE_USELESS			: RemoveUselessComputation				= new RemoveUselessComputation();
		private static const COPY_INSERTER			: CopyInserterVisitor					= new CopyInserterVisitor();
		private static const ALLOCATOR				: AllocationVisitor						= new AllocationVisitor();
		private static const INTERPOLATE_FINDER		: InterpolateFinder						= new InterpolateFinder();
		private static const WRITE_DOT				: WriteDot								= new WriteDot();
		private static const MATRIX_TRANSFORMATION	: MatrixTransformationGrouper			= new MatrixTransformationGrouper();

		private var _position				: AbstractNode;
		private var _positionComponents		: uint;
		private var _interpolates			: Vector.<AbstractNode>;
		private var _color					: AbstractNode;
		private var _colorComponents		: uint;
		private var _kills					: Vector.<AbstractNode>;
		private var _killComponents			: Vector.<uint>;
		private var _computableConstants	: Object;

		private var _isCompiled				: Boolean;

		private var _vertexSequence			: Vector.<AgalInstruction>;
		private var _fragmentSequence		: Vector.<AgalInstruction>;
		private var _bindings				: Object;

		private var _vertexComponents		: Vector.<VertexComponent>;
		private var _vertexIndices			: Vector.<uint>;
		private var _vsConstants			: Vector.<Number>;
		private var _fsConstants			: Vector.<Number>;
		private var _textures				: Vector.<ITextureResource>;

		public function get position() : AbstractNode
		{
			return _position;
		}
		public function set position(v : AbstractNode)	: void
		{
			_position = v;
		}

		public function get interpolates() : Vector.<AbstractNode>
		{
			return _interpolates;
		}

		public function get color() : AbstractNode
		{
			return _color;
		}
		public function set color(v : AbstractNode)	: void
		{
			_color = v;
		}

		public function get kills() : Vector.<AbstractNode>
		{
			return _kills;
		}

		public function get positionComponents() : uint
		{
			return _positionComponents;
		}
		public function set positionComponents(v : uint) : void
		{
			_positionComponents = v;
		}

		public function get colorComponents() : uint
		{
			return _colorComponents;
		}
		public function set colorComponents(v : uint) : void
		{
			_colorComponents = v;
		}

		public function get killComponents() : Vector.<uint>
		{
			return _killComponents;
		}

		public function get computableConstants() : Object
		{
			return _computableConstants;
		}

		public function ShaderGraph(position	: AbstractNode,
									color		: AbstractNode,
									kills		: Vector.<AbstractNode>)
		{
			_isCompiled				= false;

			_position				= position;
			_positionComponents		= Components.createContinuous(0, 0, 4, position.size);
			_interpolates			= new Vector.<AbstractNode>();
			_color					= color;
			_colorComponents		= Components.createContinuous(0, 0, 4, color.size);
			_kills					= kills;
			_killComponents			= new Vector.<uint>();
			_computableConstants	= new Object();

			var numKills : uint = kills.length;
			for (var killId : uint = 0; killId < numKills; ++killId)
				_killComponents[killId] = Components.createContinuous(0, 0, 1, 1);
		}

		public function generateProgram(name : String, signature : Signature) : Program3DResource
		{
			if (!_isCompiled)
				compile();

			if (Minko.debugLevel & DebugLevel.SHADER_AGAL)
				Minko.log(DebugLevel.SHADER_AGAL, generateAGAL(name));

			var vsProgram	: ByteArray = computeBinaryProgram(_vertexSequence, true);
			var fsProgram	: ByteArray = computeBinaryProgram(_fragmentSequence, false);

			var program : Program3DResource = new Program3DResource(
				name,
				signature,
				vsProgram,
				fsProgram,
				_vertexComponents,
				_vertexIndices,
				_vsConstants,
				_fsConstants,
				_textures,
				_bindings
			);

			return program;
		}

		public function generateAGAL(name : String) : String
		{
			var instruction	: AgalInstruction;
			var shader		: String = name + "\n";

			if (!_isCompiled)
				compile();

			shader += "- vertex shader\n";
			for each (instruction in _vertexSequence)
				shader += instruction.getAgal(true);

			shader += "- fragment shader\n";
			for each (instruction in _fragmentSequence)
				shader += instruction.getAgal(false);

			return shader;
		}

		private function compile() : void
		{
			// execute consecutive visitors to optimize the shader graph.
			// Warning: the order matters, do not swap lines.
			MERGER					.process(this);	// merge duplicate nodes
			REMOVE_EXTRACT			.process(this);	// remove all extract nodes
			OVERWRITER_CLEANER		.process(this);	// remove nested overwriters
			RESOLVE_CONSTANT		.process(this);	// resolve constant computation
			CONSTANT_PACKER			.process(this);	// pack constants [0,0,0,1] => [0,1].xxxy
//			REMOVE_USELESS			.process(this);	// remove some useless operations (add 0, mul 0, mul 1...)
			RESOLVE_PARAMETRIZED	.process(this);	// replace computations that depend on parameters by evalexp parameters
//			MATRIX_TRANSFORMATION	.process(this);	// replace ((vector * matrix1) * matrix2) by vector * (matrix1 * matrix2) to save registers on GPU
			COPY_INSERTER			.process(this);	// ensure there are no operations between constants
			SPLITTER				.process(this);	// clone nodes that are shared between vertex and fragment shader
			CONSTANT_GROUPER		.process(this);	// group constants [0,1] & [0,2] => [0, 1, 2]

			if (Minko.debugLevel & DebugLevel.SHADER_DOTTY)
			{
				WRITE_DOT.process(this);
				Minko.log(DebugLevel.SHADER_DOTTY, WRITE_DOT.result);
				WRITE_DOT.clear();
			}

			// generate final program
			INTERPOLATE_FINDER		.process(this);	// find interpolate nodes. We may skip that in the future.
			ALLOCATOR				.process(this);	// allocate memory and generate final code.

			// retrieve program
			_vertexSequence		= ALLOCATOR.vertexSequence;
			_fragmentSequence	= ALLOCATOR.fragmentSequence;
			_bindings			= ALLOCATOR.parameterBindings;
			_vertexComponents	= ALLOCATOR.vertexComponents;
			_vertexIndices		= ALLOCATOR.vertexIndices;
			_vsConstants		= ALLOCATOR.vertexConstants;
			_fsConstants		= ALLOCATOR.fragmentConstants;
			_textures			= ALLOCATOR.textures;

			ALLOCATOR.clear();

			_isCompiled = true;
		}

		private function computeBinaryProgram(sequence			: Vector.<AgalInstruction>,
											  isVertexShader	: Boolean) : ByteArray
		{
			var program : ByteArray = new ByteArray();

			program.endian = Endian.LITTLE_ENDIAN;
			program.writeByte(0xa0);					// tag version
			program.writeUnsignedInt(1);				// AGAL version, big endian, bit pattern will be 0x01000000
			program.writeByte(0xa1);					// tag program id
			program.writeByte(isVertexShader ? 0 : 1);	// vertex or fragment

			for each (var instruction : AgalInstruction in sequence)
				instruction.getBytecode(program);

			return program;
		}
	}
}

remove useless imports

remove useless imports