////////////////////////////////////////////////////////////////////////////////
//
//  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 mx.core
{

import mx.resources.ResourceManager;

[ResourceBundle("core")]

/**
 *  This class controls the backward-compatibility of the framework.
 *  With every new release, some aspects of the framework such as behaviors,
 *  styles, and default settings, are changed which can affect your application.
 *  By setting the compatibilityVersion property, the behavior can be changed
 *  to match previous releases.
 *  This is a 'global' flag; you cannot apply one version to one component or group of components
 *  and a different version to another component or group of components.
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class FlexVersion
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class constants
    //
    //--------------------------------------------------------------------------

    /**
     *  The current released version of the Flex SDK, encoded as a uint.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const CURRENT_VERSION:uint = 0x040B0000;

	/**
	 *  The compatibilityVersion value of Flex 4.11,
	 *  encoded numerically as a uint.
	 *  Code can compare this constant against
	 *  the compatibilityVersion
	 *  to implement version-specific behavior.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 11
	 *  @playerversion AIR 3
	 *  @productversion Apache Flex 4.11
	 */
	public static const VERSION_4_11:uint = 0x040B0000;

	/**
	 *  The compatibilityVersion value of Flex 4.10,
	 *  encoded numerically as a uint.
	 *  Code can compare this constant against
	 *  the compatibilityVersion
	 *  to implement version-specific behavior.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 11
	 *  @playerversion AIR 3
	 *  @productversion Apache Flex 4.10
	 */
	public static const VERSION_4_10:uint = 0x040A0000;

    /**
     *  The compatibilityVersion value of Flex 4.9,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Apache Flex 4.9
     */
    public static const VERSION_4_9:uint = 0x04090000;

    /**
     *  The compatibilityVersion value of Flex 4.8,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Apache Flex 4.8
     */
    public static const VERSION_4_8:uint = 0x04080000;

    /**
     *  The compatibilityVersion value of Flex 4.6,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    public static const VERSION_4_6:uint = 0x04060000;

    /**
     *  The compatibilityVersion value of Flex 4.5,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_4_5:uint = 0x04050000;

    /**
     *  The compatibilityVersion value of Flex 4.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_4_0:uint = 0x04000000;

    /**
     *  The compatibilityVersion value of Flex 3.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_3_0:uint = 0x03000000;

    /**
     *  The compatibilityVersion value of Flex 2.0.1,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_2_0_1:uint = 0x02000001;

    /**
     *  The compatibilityVersion value of Flex 2.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_2_0:uint = 0x02000000;

    /**
     *  A String passed as a parameter
     *  to the compatibilityErrorFunction() method
     *  if the compatibility version has already been set.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_ALREADY_SET:String = "versionAlreadySet";
        // Also used as resource string, so be careful changing it.

    /**
     *  A String passed as a parameter
     *  to the compatibilityErrorFunction() method
     *  if the compatibility version has already been read.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_ALREADY_READ:String = "versionAlreadyRead";
        // Also used as resource string, so be careful changing it.

    //--------------------------------------------------------------------------
    //
    //  Class properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  compatibilityErrorFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for the compatibilityErrorFunction property.
     */
    private static var _compatibilityErrorFunction:Function;

    /**
     *  A function that gets called when the compatibility version
     *  is set more than once, or set after it has been read.
     *  If this function is not set, the SDK throws an error.
     *  If set, File calls this function, but it is
     *  up to the developer to decide how to handle the call.
     *  This function will also be called if the function is set more than once.
     *  The function takes two parameters: the first is a uint
     *  which is the version that was attempted to be set; the second
     *  is a string that is the reason it failed, either
     *  VERSION_ALREADY_SET or VERSION_ALREADY_READ.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityErrorFunction():Function
    {
        return _compatibilityErrorFunction;
    }

    /**
     *  @private
     */
    public static function set compatibilityErrorFunction(value:Function):void
    {
        _compatibilityErrorFunction = value;
    }

    //----------------------------------
    //  compatibilityVersion
    //----------------------------------

    /**
     *  @private
     *  Storage for the compatibilityVersion property.
     */
    private static var _compatibilityVersion:uint = CURRENT_VERSION;

    /**
     *  @private
     */
    private static var compatibilityVersionChanged:Boolean = false;

    /**
     *  @private
     */
    private static var compatibilityVersionRead:Boolean = false;

    /**
     *  The current version that the framework maintains compatibility for.
     *  This defaults to CURRENT_VERSION.
     *  It can be changed only once; changing it a second time
     *  results in a call to the compatibilityErrorFunction() method
     *  if it exists, or results in a runtime error.
     *  Changing it after the compatibilityVersion property has been read results in an error
     *  because code that is dependent on the version has already run.
     *  There are no notifications; the assumption is that this is set only once, and this it is set
     *  early enough that no code that depends on it has run yet.
     *
     *  @default FlexVersion.CURRENT_VERSION
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityVersion():uint
    {
        compatibilityVersionRead = true;

        return _compatibilityVersion;
    }

    /**
     *  @private
     */
    public static function set compatibilityVersion(value:uint):void
    {
        if (value == _compatibilityVersion)
            return;

        var s:String;

        if (compatibilityVersionChanged)
        {
            if (compatibilityErrorFunction == null)
            {
                s = ResourceManager.getInstance().getString(
                    "core", VERSION_ALREADY_SET);
                throw new Error(s);
            }
            else
                compatibilityErrorFunction(value, VERSION_ALREADY_SET);
        }

        if (compatibilityVersionRead)
        {
            if (compatibilityErrorFunction == null)
            {
                s = ResourceManager.getInstance().getString(
                    "core", VERSION_ALREADY_READ);
                throw new Error(s);
            }
            else
                compatibilityErrorFunction(value, VERSION_ALREADY_READ);
        }

        _compatibilityVersion = value;
        compatibilityVersionChanged = true;
    }

    //----------------------------------
    //  compatibilityVersionString
    //----------------------------------

    /**
     *  The compatibility version, as a string of the form "X.X.X".
     *  This is a pass-through to the compatibilityVersion
     *  property, which converts the number to and from a more
     *  human-readable String version.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityVersionString():String
    {
        var major:uint = (compatibilityVersion >> 24) & 0xFF;
        var minor:uint = (compatibilityVersion >> 16) & 0xFF;
        var update:uint = compatibilityVersion & 0xFFFF;

        return major.toString() + "." +
               minor.toString() + "." +
               update.toString();
    }

    /**
     *  @private
     */
    public static function set compatibilityVersionString(value:String):void
    {
        var pieces:Array = value.split(".");

        var major:uint = parseInt(pieces[0]);
        var minor:uint = parseInt(pieces[1]);
        var update:uint = parseInt(pieces[2]);

        compatibilityVersion = (major << 24) + (minor << 16) + update;
    }

    /**
     *  @private
     *  A back door for changing the compatibility version.
     *  This is provided for Flash Builder's Design View,
     *  which needs to be able to change compatibility mode.
     *  In general, we won't support late changes to compatibility,
     *  because the framework won't watch for changes.
     *  Design View will need to set this early during initialization.
     */
    mx_internal static function changeCompatibilityVersionString(
                                        value:String):void
    {
        var pieces:Array = value.split(".");

        var major:uint = parseInt(pieces[0]);
        var minor:uint = parseInt(pieces[1]);
        var update:uint = parseInt(pieces[2]);

        _compatibilityVersion = (major << 24) + (minor << 16) + update;
    }
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  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 mx.core
{

import mx.resources.ResourceManager;

[ResourceBundle("core")]

/**
 *  This class controls the backward-compatibility of the framework.
 *  With every new release, some aspects of the framework such as behaviors,
 *  styles, and default settings, are changed which can affect your application.
 *  By setting the compatibilityVersion property, the behavior can be changed
 *  to match previous releases.
 *  This is a 'global' flag; you cannot apply one version to one component or group of components
 *  and a different version to another component or group of components.
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public class FlexVersion
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Class constants
    //
    //--------------------------------------------------------------------------

    /**
     *  The current released version of the Flex SDK, encoded as a uint.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const CURRENT_VERSION:uint = 0x040C0000;

	/**
	 *  The compatibilityVersion value of Flex 4.12,
	 *  encoded numerically as a uint.
	 *  Code can compare this constant against
	 *  the compatibilityVersion
	 *  to implement version-specific behavior.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 11
	 *  @playerversion AIR 3
	 *  @productversion Apache Flex 4.12
	 */
	public static const VERSION_4_12:uint = 0x040C0000;

	/**
	 *  The compatibilityVersion value of Flex 4.11,
	 *  encoded numerically as a uint.
	 *  Code can compare this constant against
	 *  the compatibilityVersion
	 *  to implement version-specific behavior.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 11
	 *  @playerversion AIR 3
	 *  @productversion Apache Flex 4.11
	 */
	public static const VERSION_4_11:uint = 0x040B0000;

	/**
	 *  The compatibilityVersion value of Flex 4.10,
	 *  encoded numerically as a uint.
	 *  Code can compare this constant against
	 *  the compatibilityVersion
	 *  to implement version-specific behavior.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 11
	 *  @playerversion AIR 3
	 *  @productversion Apache Flex 4.10
	 */
	public static const VERSION_4_10:uint = 0x040A0000;

    /**
     *  The compatibilityVersion value of Flex 4.9,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Apache Flex 4.9
     */
    public static const VERSION_4_9:uint = 0x04090000;

    /**
     *  The compatibilityVersion value of Flex 4.8,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Apache Flex 4.8
     */
    public static const VERSION_4_8:uint = 0x04080000;

    /**
     *  The compatibilityVersion value of Flex 4.6,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 11
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    public static const VERSION_4_6:uint = 0x04060000;

    /**
     *  The compatibilityVersion value of Flex 4.5,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_4_5:uint = 0x04050000;

    /**
     *  The compatibilityVersion value of Flex 4.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_4_0:uint = 0x04000000;

    /**
     *  The compatibilityVersion value of Flex 3.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_3_0:uint = 0x03000000;

    /**
     *  The compatibilityVersion value of Flex 2.0.1,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_2_0_1:uint = 0x02000001;

    /**
     *  The compatibilityVersion value of Flex 2.0,
     *  encoded numerically as a uint.
     *  Code can compare this constant against
     *  the compatibilityVersion
     *  to implement version-specific behavior.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_2_0:uint = 0x02000000;

    /**
     *  A String passed as a parameter
     *  to the compatibilityErrorFunction() method
     *  if the compatibility version has already been set.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_ALREADY_SET:String = "versionAlreadySet";
        // Also used as resource string, so be careful changing it.

    /**
     *  A String passed as a parameter
     *  to the compatibilityErrorFunction() method
     *  if the compatibility version has already been read.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static const VERSION_ALREADY_READ:String = "versionAlreadyRead";
        // Also used as resource string, so be careful changing it.

    //--------------------------------------------------------------------------
    //
    //  Class properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  compatibilityErrorFunction
    //----------------------------------

    /**
     *  @private
     *  Storage for the compatibilityErrorFunction property.
     */
    private static var _compatibilityErrorFunction:Function;

    /**
     *  A function that gets called when the compatibility version
     *  is set more than once, or set after it has been read.
     *  If this function is not set, the SDK throws an error.
     *  If set, File calls this function, but it is
     *  up to the developer to decide how to handle the call.
     *  This function will also be called if the function is set more than once.
     *  The function takes two parameters: the first is a uint
     *  which is the version that was attempted to be set; the second
     *  is a string that is the reason it failed, either
     *  VERSION_ALREADY_SET or VERSION_ALREADY_READ.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityErrorFunction():Function
    {
        return _compatibilityErrorFunction;
    }

    /**
     *  @private
     */
    public static function set compatibilityErrorFunction(value:Function):void
    {
        _compatibilityErrorFunction = value;
    }

    //----------------------------------
    //  compatibilityVersion
    //----------------------------------

    /**
     *  @private
     *  Storage for the compatibilityVersion property.
     */
    private static var _compatibilityVersion:uint = CURRENT_VERSION;

    /**
     *  @private
     */
    private static var compatibilityVersionChanged:Boolean = false;

    /**
     *  @private
     */
    private static var compatibilityVersionRead:Boolean = false;

    /**
     *  The current version that the framework maintains compatibility for.
     *  This defaults to CURRENT_VERSION.
     *  It can be changed only once; changing it a second time
     *  results in a call to the compatibilityErrorFunction() method
     *  if it exists, or results in a runtime error.
     *  Changing it after the compatibilityVersion property has been read results in an error
     *  because code that is dependent on the version has already run.
     *  There are no notifications; the assumption is that this is set only once, and this it is set
     *  early enough that no code that depends on it has run yet.
     *
     *  @default FlexVersion.CURRENT_VERSION
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityVersion():uint
    {
        compatibilityVersionRead = true;

        return _compatibilityVersion;
    }

    /**
     *  @private
     */
    public static function set compatibilityVersion(value:uint):void
    {
        if (value == _compatibilityVersion)
            return;

        var s:String;

        if (compatibilityVersionChanged)
        {
            if (compatibilityErrorFunction == null)
            {
                s = ResourceManager.getInstance().getString(
                    "core", VERSION_ALREADY_SET);
                throw new Error(s);
            }
            else
                compatibilityErrorFunction(value, VERSION_ALREADY_SET);
        }

        if (compatibilityVersionRead)
        {
            if (compatibilityErrorFunction == null)
            {
                s = ResourceManager.getInstance().getString(
                    "core", VERSION_ALREADY_READ);
                throw new Error(s);
            }
            else
                compatibilityErrorFunction(value, VERSION_ALREADY_READ);
        }

        _compatibilityVersion = value;
        compatibilityVersionChanged = true;
    }

    //----------------------------------
    //  compatibilityVersionString
    //----------------------------------

    /**
     *  The compatibility version, as a string of the form "X.X.X".
     *  This is a pass-through to the compatibilityVersion
     *  property, which converts the number to and from a more
     *  human-readable String version.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function get compatibilityVersionString():String
    {
        var major:uint = (compatibilityVersion >> 24) & 0xFF;
        var minor:uint = (compatibilityVersion >> 16) & 0xFF;
        var update:uint = compatibilityVersion & 0xFFFF;

        return major.toString() + "." +
               minor.toString() + "." +
               update.toString();
    }

    /**
     *  @private
     */
    public static function set compatibilityVersionString(value:String):void
    {
        var pieces:Array = value.split(".");

        var major:uint = parseInt(pieces[0]);
        var minor:uint = parseInt(pieces[1]);
        var update:uint = parseInt(pieces[2]);

        compatibilityVersion = (major << 24) + (minor << 16) + update;
    }

    /**
     *  @private
     *  A back door for changing the compatibility version.
     *  This is provided for Flash Builder's Design View,
     *  which needs to be able to change compatibility mode.
     *  In general, we won't support late changes to compatibility,
     *  because the framework won't watch for changes.
     *  Design View will need to set this early during initialization.
     */
    mx_internal static function changeCompatibilityVersionString(
                                        value:String):void
    {
        var pieces:Array = value.split(".");

        var major:uint = parseInt(pieces[0]);
        var minor:uint = parseInt(pieces[1]);
        var update:uint = parseInt(pieces[2]);

        _compatibilityVersion = (major << 24) + (minor << 16) + update;
    }
}

}

Make 4.12 the new version

Make 4.12 the new version


/* 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.loader {
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.utils.clearTimeout;
    import flash.utils.getTimer;
    import flash.utils.setTimeout;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.constant.HLSPlayStates;
    import org.mangui.hls.constant.HLSTypes;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.Level;
    import org.mangui.hls.playlist.AltAudioTrack;
    import org.mangui.hls.playlist.DataUri;
    import org.mangui.hls.playlist.Manifest;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    public class LevelLoader {
        /** Reference to the hls framework controller. **/
        private var _hls : HLS;
        /** levels vector. **/
        private var _levels : Vector.<Level>;
        /** Object that fetches the manifest. **/
        private var _urlloader : URLLoader;
        /** Link to the M3U8 file. **/
        private var _url : String;
        /** are all playlists filled ? **/
        private var _canStart : Boolean;
        /** Timeout ID for reloading live playlists. **/
        private var _timeoutID : uint;
        /** Streaming type (live, ondemand). **/
        private var _type : String;
        /** last reload manifest time **/
        private var _reloadPlaylistTimer : uint;
        /** current load level **/
        private var _loadLevel : int;
        /** reference to manifest being loaded **/
        private var _manifestLoading : Manifest;
        /** is this loader closed **/
        private var _closed : Boolean = false;
        /* playlist retry timeout */
        private var _retryTimeout : Number;
        private var _retryCount : int;
        /* alt audio tracks */
        private var _altAudioTracks : Vector.<AltAudioTrack>;
        /* manifest load metrics */
        private var _metrics : HLSLoadMetrics;

        /** Setup the loader. **/
        public function LevelLoader(hls : HLS) {
            _hls = hls;
            _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
            _hls.addEventListener(HLSEvent.LEVEL_SWITCH, _levelSwitchHandler);
            _levels = new Vector.<Level>();
        };

        public function dispose() : void {
            _close();
            if(_urlloader) {
                _urlloader.removeEventListener(Event.COMPLETE, _loadCompleteHandler);
                _urlloader.removeEventListener(ProgressEvent.PROGRESS, _loadProgressHandler);
                _urlloader.removeEventListener(IOErrorEvent.IO_ERROR, _errorHandler);
                _urlloader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _errorHandler);
                _urlloader = null;
            }
            _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
            _hls.removeEventListener(HLSEvent.LEVEL_SWITCH, _levelSwitchHandler);
        }

        /** Loading failed; return errors. **/
        private function _errorHandler(event : ErrorEvent) : void {
            var txt : String;
            var code : int;
            if (event is SecurityErrorEvent) {
                code = HLSError.MANIFEST_LOADING_CROSSDOMAIN_ERROR;
                txt = "Cannot load M3U8: crossdomain access denied:" + event.text;
            } else if (event is IOErrorEvent && _levels.length && (HLSSettings.manifestLoadMaxRetry == -1 || _retryCount < HLSSettings.manifestLoadMaxRetry)) {
                CONFIG::LOGGING {
                    Log.warn("I/O Error while trying to load Playlist, retry in " + _retryTimeout + " ms");
                }
                _timeoutID = setTimeout(_loadActiveLevelPlaylist, _retryTimeout);
                /* exponential increase of retry timeout, capped to manifestLoadMaxRetryTimeout */
                _retryTimeout = Math.min(HLSSettings.manifestLoadMaxRetryTimeout, 2 * _retryTimeout);
                _retryCount++;
                return;
            } else {
                code = HLSError.MANIFEST_LOADING_IO_ERROR;
                txt = "Cannot load M3U8: " + event.text;
            }
            var hlsError : HLSError = new HLSError(code, _url, txt);
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
        };

        /** Return the current manifest. **/
        public function get levels() : Vector.<Level> {
            return _levels;
        };

        /** Return the stream type. **/
        public function get type() : String {
            return _type;
        };

        public function get altAudioTracks() : Vector.<AltAudioTrack> {
            return _altAudioTracks;
        }

        /** Load the manifest file. **/
        public function load(url : String) : void {
            if(!_urlloader) {
                //_urlloader = new URLLoader();
                var urlLoaderClass : Class = _hls.URLloader as Class;
                _urlloader = (new urlLoaderClass()) as URLLoader;
                _urlloader.addEventListener(Event.COMPLETE, _loadCompleteHandler);
                _urlloader.addEventListener(ProgressEvent.PROGRESS, _loadProgressHandler);
                _urlloader.addEventListener(IOErrorEvent.IO_ERROR, _errorHandler);
                _urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _errorHandler);
            }
            _close();
            _closed = false;
            _url = url;
            _levels = new Vector.<Level>();
            _canStart = false;
            _reloadPlaylistTimer = getTimer();
            _retryTimeout = 1000;
            _retryCount = 0;
            _altAudioTracks = null;
            _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_LOADING, url));
            _metrics = new HLSLoadMetrics(HLSLoaderTypes.MANIFEST);
            _metrics.loading_request_time = getTimer();
            if (DataUri.isDataUri(url)) {
                CONFIG::LOGGING {
                    Log.debug("Identified main manifest <" + url + "> as a data URI.");
                }
                _metrics.loading_begin_time = getTimer();
                var data : String = new DataUri(url).extractData();
                _metrics.loading_end_time = getTimer();
                _parseManifest(data || "");
            } else {
                _urlloader.load(new URLRequest(url));
            }
        };

        /** loading progress handler, use to determine loading latency **/
        private function _loadProgressHandler(event : Event) : void {
            if(_metrics.loading_begin_time == 0) {
                _metrics.loading_begin_time = getTimer();
            }
        };

        /** Manifest loaded; check and parse it **/
        private function _loadCompleteHandler(event : Event) : void {
             _metrics.loading_end_time = getTimer();
            // successful loading, reset retry counter
            _retryTimeout = 1000;
            _retryCount = 0;
            _parseManifest(String(_urlloader.data));
        };

        /** parse a playlist **/
        private function _parseLevelPlaylist(string : String, url : String, level : int, metrics : HLSLoadMetrics) : void {
            if (string != null && string.length != 0) {
                CONFIG::LOGGING {
                    Log.debug("level " + level + " playlist:\n" + string);
                }
                var frags : Vector.<Fragment> = Manifest.getFragments(string, url, level);
                // set fragment and update sequence number range
                _levels[level].updateFragments(frags);
                _levels[level].targetduration = Manifest.getTargetDuration(string);
                _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, _levels[level].duration));
            }

            // Check whether the stream is live or not finished yet
            if (Manifest.hasEndlist(string)) {
                _type = HLSTypes.VOD;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_ENDLIST, level));
            } else {
                _type = HLSTypes.LIVE;
                /* in order to determine playlist reload timer,
                    check playback position against playlist duration.
                    if we are near the edge of a live playlist, reload playlist quickly
                    to discover quicker new fragments and avoid buffer starvation.
                */
                var _reloadInterval : Number = 1000*Math.min((_levels[level].duration - _hls.position)/2,_levels[level].averageduration);
                // avoid spamming the server if we are at the edge ... wait 500ms between 2 reload at least
                var timeout : Number = Math.max(500, _reloadPlaylistTimer + _reloadInterval - getTimer());
                CONFIG::LOGGING {
                    Log.debug("Level " + level + " Live Playlist parsing finished: reload in " + timeout.toFixed(0) + " ms");
                }
                _timeoutID = setTimeout(_loadActiveLevelPlaylist, timeout);
            }
            if (!_canStart) {
                _canStart = (_levels[level].fragments.length > 0);
                if (_canStart) {
                    CONFIG::LOGGING {
                        Log.debug("first level filled with at least 1 fragment, notify event");
                    }
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_LOADED, _levels, _metrics));
                }
            }
            metrics.id  = _levels[level].start_seqnum;
            metrics.id2 = _levels[level].end_seqnum;
            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADED, metrics));
            _manifestLoading = null;
        };

        /** Parse First Level Playlist **/
        private function _parseManifest(string : String) : void {
            // Check for M3U8 playlist or manifest.
            if (string.indexOf(Manifest.HEADER) == 0) {
                // 1 level playlist, create unique level and parse playlist
                if (string.indexOf(Manifest.FRAGMENT) > 0) {
                    var level : Level = new Level();
                    level.url = _url;
                    _levels.push(level);
                    _metrics.parsing_end_time = getTimer();
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_PARSED, _levels));
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADING, 0));
                    CONFIG::LOGGING {
                        Log.debug("1 Level Playlist, load it");
                    }
                    _loadLevel = 0;
                    _metrics.type = HLSLoaderTypes.LEVEL_MAIN;
                    _parseLevelPlaylist(string, _url, 0,_metrics);
                } else if (string.indexOf(Manifest.LEVEL) > 0) {
                    CONFIG::LOGGING {
                        Log.debug("adaptive playlist:\n" + string);
                    }
                    // adaptative playlist, extract levels from playlist, get them and parse them
                    _levels = Manifest.extractLevels(string, _url);

                    var levelsLength : int = _levels.length;
                    if (levelsLength == 0) {
                        hlsError = new HLSError(HLSError.MANIFEST_PARSING_ERROR, _url, "No level found in Manifest");
                        _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
                    }
                    _metrics.parsing_end_time = getTimer();
                    _loadLevel = -1;
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_PARSED, _levels));
                    if (string.indexOf(Manifest.ALTERNATE_AUDIO) > 0) {
                        CONFIG::LOGGING {
                            Log.debug("alternate audio level found");
                        }
                        // parse alternate audio tracks
                        _altAudioTracks = Manifest.extractAltAudioTracks(string, _url);
                        CONFIG::LOGGING {
                            if (_altAudioTracks.length > 0) {
                                Log.debug(_altAudioTracks.length + " alternate audio tracks found");
                            }
                        }
                    }
                }
            } else {
                var hlsError : HLSError = new HLSError(HLSError.MANIFEST_PARSING_ERROR, _url, "Manifest is not a valid M3U8 file");
                _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
            }
        };

        /** load/reload active M3U8 playlist **/
        private function _loadActiveLevelPlaylist() : void {
            if (_closed) {
                return;
            }
            _reloadPlaylistTimer = getTimer();
            // load active M3U8 playlist only
            _manifestLoading = new Manifest();
            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADING, _loadLevel));
            _manifestLoading.loadPlaylist(_hls,_levels[_loadLevel].url, _parseLevelPlaylist, _errorHandler, _loadLevel, _type, HLSSettings.flushLiveURLCache);
        };

        /** When level switch occurs, assess the need of (re)loading new level playlist **/
        private function _levelSwitchHandler(event : HLSEvent) : void {
            if (_loadLevel != event.level) {
                _loadLevel = event.level;
                CONFIG::LOGGING {
                    Log.debug("switch to level " + _loadLevel);
                }
                if (_type == HLSTypes.LIVE || _levels[_loadLevel].fragments.length == 0) {
                    _closed = false;
                    CONFIG::LOGGING {
                        Log.debug("(re)load Playlist");
                    }
                    if(_manifestLoading) {
                       _manifestLoading.close();
                       _manifestLoading = null;
                    }
                    clearTimeout(_timeoutID);
                    _timeoutID = setTimeout(_loadActiveLevelPlaylist, 0);
                }
            }
        };

        private function _close() : void {
            CONFIG::LOGGING {
                Log.debug("cancel any manifest load in progress");
            }
            _closed = true;
            clearTimeout(_timeoutID);
            try {
                _urlloader.close();
                if (_manifestLoading) {
                    _manifestLoading.close();
                }
            } catch(e : Error) {
            }
        }

        /** When the framework idles out, stop reloading manifest **/
        private function _stateHandler(event : HLSEvent) : void {
            if (event.state == HLSPlayStates.IDLE) {
                _close();
            }
        };
    }
}

/* 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.loader {
    import flash.events.ErrorEvent;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.utils.clearTimeout;
    import flash.utils.getTimer;
    import flash.utils.setTimeout;
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.constant.HLSPlayStates;
    import org.mangui.hls.constant.HLSTypes;
    import org.mangui.hls.event.HLSError;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.model.Fragment;
    import org.mangui.hls.model.Level;
    import org.mangui.hls.playlist.AltAudioTrack;
    import org.mangui.hls.playlist.DataUri;
    import org.mangui.hls.playlist.Manifest;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    public class LevelLoader {
        /** Reference to the hls framework controller. **/
        private var _hls : HLS;
        /** levels vector. **/
        private var _levels : Vector.<Level>;
        /** Object that fetches the manifest. **/
        private var _urlloader : URLLoader;
        /** Link to the M3U8 file. **/
        private var _url : String;
        /** are all playlists filled ? **/
        private var _canStart : Boolean;
        /** Timeout ID for reloading live playlists. **/
        private var _timeoutID : uint;
        /** Streaming type (live, ondemand). **/
        private var _type : String;
        /** last reload manifest time **/
        private var _reloadPlaylistTimer : uint;
        /** current load level **/
        private var _loadLevel : int;
        /** reference to manifest being loaded **/
        private var _manifestLoading : Manifest;
        /** is this loader closed **/
        private var _closed : Boolean = false;
        /* playlist retry timeout */
        private var _retryTimeout : Number;
        private var _retryCount : int;
        /* alt audio tracks */
        private var _altAudioTracks : Vector.<AltAudioTrack>;
        /* manifest load metrics */
        private var _metrics : HLSLoadMetrics;

        /** Setup the loader. **/
        public function LevelLoader(hls : HLS) {
            _hls = hls;
            _hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
            _hls.addEventListener(HLSEvent.LEVEL_SWITCH, _levelSwitchHandler);
            _levels = new Vector.<Level>();
        };

        public function dispose() : void {
            _close();
            if(_urlloader) {
                _urlloader.removeEventListener(Event.COMPLETE, _loadCompleteHandler);
                _urlloader.removeEventListener(ProgressEvent.PROGRESS, _loadProgressHandler);
                _urlloader.removeEventListener(IOErrorEvent.IO_ERROR, _errorHandler);
                _urlloader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, _errorHandler);
                _urlloader = null;
            }
            _hls.removeEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
            _hls.removeEventListener(HLSEvent.LEVEL_SWITCH, _levelSwitchHandler);
        }

        /** Loading failed; return errors. **/
        private function _errorHandler(event : ErrorEvent) : void {
            var txt : String;
            var code : int;
            if (event is SecurityErrorEvent) {
                code = HLSError.MANIFEST_LOADING_CROSSDOMAIN_ERROR;
                txt = "Cannot load M3U8: crossdomain access denied:" + event.text;
            } else if (event is IOErrorEvent && _levels.length && (HLSSettings.manifestLoadMaxRetry == -1 || _retryCount < HLSSettings.manifestLoadMaxRetry)) {
                CONFIG::LOGGING {
                    Log.warn("I/O Error while trying to load Playlist, retry in " + _retryTimeout + " ms");
                }
                _timeoutID = setTimeout(_loadActiveLevelPlaylist, _retryTimeout);
                /* exponential increase of retry timeout, capped to manifestLoadMaxRetryTimeout */
                _retryTimeout = Math.min(HLSSettings.manifestLoadMaxRetryTimeout, 2 * _retryTimeout);
                _retryCount++;
                return;
            } else {
                code = HLSError.MANIFEST_LOADING_IO_ERROR;
                txt = "Cannot load M3U8: " + event.text;
            }
            var hlsError : HLSError = new HLSError(code, _url, txt);
            _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
        };

        /** Return the current manifest. **/
        public function get levels() : Vector.<Level> {
            return _levels;
        };

        /** Return the stream type. **/
        public function get type() : String {
            return _type;
        };

        public function get altAudioTracks() : Vector.<AltAudioTrack> {
            return _altAudioTracks;
        }

        /** Load the manifest file. **/
        public function load(url : String) : void {
            if(!_urlloader) {
                //_urlloader = new URLLoader();
                var urlLoaderClass : Class = _hls.URLloader as Class;
                _urlloader = (new urlLoaderClass()) as URLLoader;
                _urlloader.addEventListener(Event.COMPLETE, _loadCompleteHandler);
                _urlloader.addEventListener(ProgressEvent.PROGRESS, _loadProgressHandler);
                _urlloader.addEventListener(IOErrorEvent.IO_ERROR, _errorHandler);
                _urlloader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _errorHandler);
            }
            _close();
            _closed = false;
            _url = url;
            _levels = new Vector.<Level>();
            _canStart = false;
            _reloadPlaylistTimer = getTimer();
            _retryTimeout = 1000;
            _retryCount = 0;
            _altAudioTracks = null;
            _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_LOADING, url));
            _metrics = new HLSLoadMetrics(HLSLoaderTypes.MANIFEST);
            _metrics.loading_request_time = getTimer();
            if (DataUri.isDataUri(url)) {
                CONFIG::LOGGING {
                    Log.debug("Identified main manifest <" + url + "> as a data URI.");
                }
                _metrics.loading_begin_time = getTimer();
                var data : String = new DataUri(url).extractData();
                _metrics.loading_end_time = getTimer();
                _parseManifest(data || "");
            } else {
                _urlloader.load(new URLRequest(url));
            }
        };

        /** loading progress handler, use to determine loading latency **/
        private function _loadProgressHandler(event : Event) : void {
            if(_metrics.loading_begin_time == 0) {
                _metrics.loading_begin_time = getTimer();
            }
        };

        /** Manifest loaded; check and parse it **/
        private function _loadCompleteHandler(event : Event) : void {
             _metrics.loading_end_time = getTimer();
            // successful loading, reset retry counter
            _retryTimeout = 1000;
            _retryCount = 0;
            _parseManifest(String(_urlloader.data));
        };

        /** parse a playlist **/
        private function _parseLevelPlaylist(string : String, url : String, level : int, metrics : HLSLoadMetrics) : void {
            if (string != null && string.length != 0) {
                // successful loading, reset retry counter
                _retryTimeout = 1000;
                _retryCount = 0;
                CONFIG::LOGGING {
                    Log.debug("level " + level + " playlist:\n" + string);
                }
                var frags : Vector.<Fragment> = Manifest.getFragments(string, url, level);
                // set fragment and update sequence number range
                _levels[level].updateFragments(frags);
                _levels[level].targetduration = Manifest.getTargetDuration(string);
                _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, _levels[level].duration));
            }

            // Check whether the stream is live or not finished yet
            if (Manifest.hasEndlist(string)) {
                _type = HLSTypes.VOD;
                _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_ENDLIST, level));
            } else {
                _type = HLSTypes.LIVE;
                /* in order to determine playlist reload timer,
                    check playback position against playlist duration.
                    if we are near the edge of a live playlist, reload playlist quickly
                    to discover quicker new fragments and avoid buffer starvation.
                */
                var _reloadInterval : Number = 1000*Math.min((_levels[level].duration - _hls.position)/2,_levels[level].averageduration);
                // avoid spamming the server if we are at the edge ... wait 500ms between 2 reload at least
                var timeout : Number = Math.max(500, _reloadPlaylistTimer + _reloadInterval - getTimer());
                CONFIG::LOGGING {
                    Log.debug("Level " + level + " Live Playlist parsing finished: reload in " + timeout.toFixed(0) + " ms");
                }
                _timeoutID = setTimeout(_loadActiveLevelPlaylist, timeout);
            }
            if (!_canStart) {
                _canStart = (_levels[level].fragments.length > 0);
                if (_canStart) {
                    CONFIG::LOGGING {
                        Log.debug("first level filled with at least 1 fragment, notify event");
                    }
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_LOADED, _levels, _metrics));
                }
            }
            metrics.id  = _levels[level].start_seqnum;
            metrics.id2 = _levels[level].end_seqnum;
            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADED, metrics));
            _manifestLoading = null;
        };

        /** Parse First Level Playlist **/
        private function _parseManifest(string : String) : void {
            // Check for M3U8 playlist or manifest.
            if (string.indexOf(Manifest.HEADER) == 0) {
                // 1 level playlist, create unique level and parse playlist
                if (string.indexOf(Manifest.FRAGMENT) > 0) {
                    var level : Level = new Level();
                    level.url = _url;
                    _levels.push(level);
                    _metrics.parsing_end_time = getTimer();
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_PARSED, _levels));
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADING, 0));
                    CONFIG::LOGGING {
                        Log.debug("1 Level Playlist, load it");
                    }
                    _loadLevel = 0;
                    _metrics.type = HLSLoaderTypes.LEVEL_MAIN;
                    _parseLevelPlaylist(string, _url, 0,_metrics);
                } else if (string.indexOf(Manifest.LEVEL) > 0) {
                    CONFIG::LOGGING {
                        Log.debug("adaptive playlist:\n" + string);
                    }
                    // adaptative playlist, extract levels from playlist, get them and parse them
                    _levels = Manifest.extractLevels(string, _url);

                    var levelsLength : int = _levels.length;
                    if (levelsLength == 0) {
                        hlsError = new HLSError(HLSError.MANIFEST_PARSING_ERROR, _url, "No level found in Manifest");
                        _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
                    }
                    _metrics.parsing_end_time = getTimer();
                    _loadLevel = -1;
                    _hls.dispatchEvent(new HLSEvent(HLSEvent.MANIFEST_PARSED, _levels));
                    if (string.indexOf(Manifest.ALTERNATE_AUDIO) > 0) {
                        CONFIG::LOGGING {
                            Log.debug("alternate audio level found");
                        }
                        // parse alternate audio tracks
                        _altAudioTracks = Manifest.extractAltAudioTracks(string, _url);
                        CONFIG::LOGGING {
                            if (_altAudioTracks.length > 0) {
                                Log.debug(_altAudioTracks.length + " alternate audio tracks found");
                            }
                        }
                    }
                }
            } else {
                var hlsError : HLSError = new HLSError(HLSError.MANIFEST_PARSING_ERROR, _url, "Manifest is not a valid M3U8 file");
                _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
            }
        };

        /** load/reload active M3U8 playlist **/
        private function _loadActiveLevelPlaylist() : void {
            if (_closed) {
                return;
            }
            _reloadPlaylistTimer = getTimer();
            // load active M3U8 playlist only
            _manifestLoading = new Manifest();
            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_LOADING, _loadLevel));
            _manifestLoading.loadPlaylist(_hls,_levels[_loadLevel].url, _parseLevelPlaylist, _errorHandler, _loadLevel, _type, HLSSettings.flushLiveURLCache);
        };

        /** When level switch occurs, assess the need of (re)loading new level playlist **/
        private function _levelSwitchHandler(event : HLSEvent) : void {
            if (_loadLevel != event.level) {
                _loadLevel = event.level;
                CONFIG::LOGGING {
                    Log.debug("switch to level " + _loadLevel);
                }
                if (_type == HLSTypes.LIVE || _levels[_loadLevel].fragments.length == 0) {
                    _closed = false;
                    CONFIG::LOGGING {
                        Log.debug("(re)load Playlist");
                    }
                    if(_manifestLoading) {
                       _manifestLoading.close();
                       _manifestLoading = null;
                    }
                    clearTimeout(_timeoutID);
                    _timeoutID = setTimeout(_loadActiveLevelPlaylist, 0);
                }
            }
        };

        private function _close() : void {
            CONFIG::LOGGING {
                Log.debug("cancel any manifest load in progress");
            }
            _closed = true;
            clearTimeout(_timeoutID);
            try {
                _urlloader.close();
                if (_manifestLoading) {
                    _manifestLoading.close();
                }
            } catch(e : Error) {
            }
        }

        /** When the framework idles out, stop reloading manifest **/
        private function _stateHandler(event : HLSEvent) : void {
            if (event.state == HLSPlayStates.IDLE) {
                _close();
            }
        };
    }
}

fix manifest load retry counter not being reset properly after successfull loading related to #308


package com.kaltura.hls.manifest
{
	import com.hurlant.util.Hex;
	import com.kaltura.hls.crypto.FastAESKey;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	CONFIG::LOGGING
	{
		import org.osmf.logging.Logger;
        import org.osmf.logging.Log;
	}

	/**
	 * HLSManifestEncryptionKey is used to parse AES decryption key data from a m3u8
	 * manifest, load the specified key file into memory, and use the key data to decrypt
	 * audio and video streams. It supports AES-128 encryption with #PKCS7 padding, and uses
	 * explicitly passed in Initialization Vectors.
	 */

	public class HLSManifestEncryptionKey extends EventDispatcher
	{
        CONFIG::LOGGING
        {
            private static const logger:Logger = Log.getLogger("com.kaltura.hls.manifest.HLSManifestEncryptionKey");
        }

		private static const LOADER_CACHE:Dictionary = new Dictionary();
		private var _key:FastAESKey;
		public var usePadding:Boolean = false;
		public var iv:String = "";
		public var url:String = "";
		private var iv0 : uint;
		private var iv1 : uint;
		private var iv2 : uint;
		private var iv3 : uint;

		// Keep track of the segments this key applies to
		public var startSegmentId:uint = 0;
		public var endSegmentId:uint = uint.MAX_VALUE;

		private var _keyData:ByteArray;

		public var isLoading:Boolean = false;

		public function HLSManifestEncryptionKey()
		{

		}

		public function get isLoaded():Boolean { return _keyData != null; }

		public override function toString():String
		{
			return "[HLSManifestEncryptionKey start=" + startSegmentId +", end=" + endSegmentId + ", iv=" + iv + ", url=" + url + ", loaded=" + isLoaded + "]";
		}

		public static function fromParams( params:String ):HLSManifestEncryptionKey
		{
			var result:HLSManifestEncryptionKey = new HLSManifestEncryptionKey();

			var tokens:Array = KeyParamParser.parseParams( params );
			var tokenCount:int = tokens.length;

			for ( var i:int = 0; i < tokenCount; i += 2 )
			{
				var name:String = tokens[ i ];
				var value:String = tokens[ i + 1 ];

				switch ( name )
				{
					case "URI" :
						result.url = value;
						break;

					case "IV" :
						result.iv = value;
						break;
				}

			}

			return result;
		}

		/**
		 * Creates an initialization vector from the passed in uint ID, usually
		 * a segment ID.
		 */
		public static function createIVFromID( id:uint ):ByteArray
		{
			var result:ByteArray = new ByteArray();
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( id );
			return result;
		}

		public static function clearLoaderCache():void
		{
			for ( var key:String in LOADER_CACHE ) delete LOADER_CACHE[ key ];
		}

		/**
		 * Decrypts a video or audio stream using AES-128 with the provided initialization vector.
		 */

		public function decrypt( data:ByteArray, iv:ByteArray ):ByteArray
		{
			//logger.debug("got " + data.length + " bytes");
			if(data.length == 0)
				return data;

			var startTime:uint = getTimer();
			_key = new FastAESKey(_keyData);
			iv.position = 0;
			iv0 = iv.readUnsignedInt();
			iv1 = iv.readUnsignedInt();
			iv2 = iv.readUnsignedInt();
			iv3 = iv.readUnsignedInt();
			data.position = 0;
			data = _decryptCBC(data,data.length);
			if ( usePadding ){
				data = unpad( data );
			}
		//	logger.debug( "DECRYPTION OF " + data.length + " BYTES TOOK " + ( getTimer() - startTime ) + " MS" );
			return data;
		}

		/* Cypher Block Chaining Decryption, refer to
		* http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_
		* for algorithm description
		*/
		private function _decryptCBC(crypt : ByteArray, len : uint) : ByteArray {
			var src : Vector.<uint> = new Vector.<uint>(4);
			var dst : Vector.<uint> = new Vector.<uint>(4);
			var decrypt : ByteArray = new ByteArray();
			decrypt.length = len;

			for (var i : uint = 0; i < len / 16; i++) {
				// read src byte array
				src[0] = crypt.readUnsignedInt();
				src[1] = crypt.readUnsignedInt();
				src[2] = crypt.readUnsignedInt();
				src[3] = crypt.readUnsignedInt();

				// AES decrypt src vector into dst vector
				_key.decrypt128(src, dst);

				// CBC : write output = XOR(decrypted,IV)
				decrypt.writeUnsignedInt(dst[0] ^ iv0);
				decrypt.writeUnsignedInt(dst[1] ^ iv1);
				decrypt.writeUnsignedInt(dst[2] ^ iv2);
				decrypt.writeUnsignedInt(dst[3] ^ iv3);

				// CBC : next IV = (input)
				iv0 = src[0];
				iv1 = src[1];
				iv2 = src[2];
				iv3 = src[3];
			}
			return decrypt;
		}
		public function unpad(bytesToUnpad : ByteArray) : ByteArray {
			if ((bytesToUnpad.length % 16) != 0)
			{
				throw new Error("PKCS#5::unpad: ByteArray.length isn't a multiple of the blockSize");
				return a;
			}

			const paddingValue:int = bytesToUnpad[bytesToUnpad.length - 1];
			trace("paddingValue: " + paddingValue);
			var doUnpad:Boolean = true;
			for (var i:int = 0; i<paddingValue; i++) {
				var readValue:int = bytesToUnpad[bytesToUnpad.length - (1 + i)];
				trace("readvalue " + i + ": " + readValue);
				if (paddingValue != readValue)
				{
					//throw new Error("PKCS#5:unpad: Invalid padding value. expected [" + paddingValue + "], found [" + readValue + "]");
					//break;
					doUnpad = false;
					break;
				}
			}

			if(doUnpad)
			{
				trace("Unpadding decrypted byteArray");
				bytesToUnpad.length -= paddingValue + 1;
			}

			return bytesToUnpad;
		}

		public function retrieveStoredIV():ByteArray
		{
			CONFIG::LOGGING
			{
				logger.debug("IV of " + iv + " for " + url + ", key=" + Hex.fromArray(_keyData));
			}
			return Hex.toArray( iv );
		}

		private static function getLoader( url:String ):URLLoader
		{
			if ( LOADER_CACHE[ url ] != null ) return LOADER_CACHE[ url ] as URLLoader;
			var newLoader:URLLoader = new URLLoader();
			newLoader.dataFormat = URLLoaderDataFormat.BINARY;
			newLoader.load( new URLRequest( url ) );
			LOADER_CACHE[ url ] = newLoader;
			return newLoader;
		}

		public function load():void
		{
			isLoading = true;

			if ( isLoaded ) throw new Error( "Already loaded!" );
			var loader:URLLoader = getLoader( url );
			if ( loader.bytesTotal > 0 && loader.bytesLoaded == loader.bytesTotal ) onLoad();
			else loader.addEventListener( Event.COMPLETE, onLoad );
		}

		private function onLoad( e:Event = null ):void
		{
			isLoading = false;

			CONFIG::LOGGING
			{
				logger.debug("KEY LOADED! " + url);
			}

			var loader:URLLoader = getLoader( url );
			_keyData = loader.data as ByteArray;
			loader.removeEventListener( Event.COMPLETE, onLoad );
			dispatchEvent( new Event( Event.COMPLETE ) );
		}
	}
}

class KeyParamParser
{
	private static const STATE_PARSE_NAME:String = "ParseName";
	private static const STATE_BEGIN_PARSE_VALUE:String = "BeginParseValue";
	private static const STATE_PARSE_VALUE:String = "ParseValue";

	public static function parseParams( paramString:String ):Array
	{
		var result:Array = [];
		var cursor:int = 0;
		var state:String = STATE_PARSE_NAME;
		var accum:String = "";
		var usingQuotes:Boolean = false;

		while ( cursor < paramString.length )
		{
			var char:String = paramString.charAt( cursor );
			switch ( state )
			{
				case STATE_PARSE_NAME:

					if ( char == '=' )
					{
						result.push( accum );
						accum = "";
						state = STATE_BEGIN_PARSE_VALUE;
					}
					else accum += char;
					break;

				case STATE_BEGIN_PARSE_VALUE:

					if ( char == '"' ) usingQuotes = true;
					else accum += char;
					state = STATE_PARSE_VALUE;
					break;

				case STATE_PARSE_VALUE:

					if ( !usingQuotes && char == ',' )
					{
						result.push( accum );
						accum = "";
						state = STATE_PARSE_NAME;
						break;
					}

					if ( usingQuotes && char == '"' )
					{
						usingQuotes = false;
						break;
					}

					accum += char;
					break;
			}

			cursor++;

			if ( cursor == paramString.length ) result.push( accum );
		}

		return result;
	}
}

package com.kaltura.hls.manifest
{
	import com.hurlant.util.Hex;
	import com.kaltura.hls.crypto.FastAESKey;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	CONFIG::LOGGING
	{
		import org.osmf.logging.Logger;
        import org.osmf.logging.Log;
	}

	/**
	 * HLSManifestEncryptionKey is used to parse AES decryption key data from a m3u8
	 * manifest, load the specified key file into memory, and use the key data to decrypt
	 * audio and video streams. It supports AES-128 encryption with #PKCS7 padding, and uses
	 * explicitly passed in Initialization Vectors.
	 */

	public class HLSManifestEncryptionKey extends EventDispatcher
	{
        CONFIG::LOGGING
        {
            private static const logger:Logger = Log.getLogger("com.kaltura.hls.manifest.HLSManifestEncryptionKey");
        }

		private static const LOADER_CACHE:Dictionary = new Dictionary();
		private var _key:FastAESKey;
		public var usePadding:Boolean = false;
		public var iv:String = "";
		public var url:String = "";
		private var iv0 : uint;
		private var iv1 : uint;
		private var iv2 : uint;
		private var iv3 : uint;

		// Keep track of the segments this key applies to
		public var startSegmentId:uint = 0;
		public var endSegmentId:uint = uint.MAX_VALUE;

		private var _keyData:ByteArray;

		public var isLoading:Boolean = false;

		public function HLSManifestEncryptionKey()
		{

		}

		public function get isLoaded():Boolean { return _keyData != null; }

		public override function toString():String
		{
			return "[HLSManifestEncryptionKey start=" + startSegmentId +", end=" + endSegmentId + ", iv=" + iv + ", url=" + url + ", loaded=" + isLoaded + "]";
		}

		public static function fromParams( params:String ):HLSManifestEncryptionKey
		{
			var result:HLSManifestEncryptionKey = new HLSManifestEncryptionKey();

			var tokens:Array = KeyParamParser.parseParams( params );
			var tokenCount:int = tokens.length;

			for ( var i:int = 0; i < tokenCount; i += 2 )
			{
				var name:String = tokens[ i ];
				var value:String = tokens[ i + 1 ];

				switch ( name )
				{
					case "URI" :
						result.url = value;
						break;

					case "IV" :
						result.iv = value;
						break;
				}

			}

			return result;
		}

		/**
		 * Creates an initialization vector from the passed in uint ID, usually
		 * a segment ID.
		 */
		public static function createIVFromID( id:uint ):ByteArray
		{
			var result:ByteArray = new ByteArray();
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( 0 );
			result.writeUnsignedInt( id );
			return result;
		}

		public static function clearLoaderCache():void
		{
			for ( var key:String in LOADER_CACHE ) delete LOADER_CACHE[ key ];
		}

		/**
		 * Decrypts a video or audio stream using AES-128 with the provided initialization vector.
		 */

		public function decrypt( data:ByteArray, iv:ByteArray ):ByteArray
		{
			//logger.debug("got " + data.length + " bytes");
			if(data.length == 0)
				return data;

			var startTime:uint = getTimer();
			_key = new FastAESKey(_keyData);
			iv.position = 0;
			iv0 = iv.readUnsignedInt();
			iv1 = iv.readUnsignedInt();
			iv2 = iv.readUnsignedInt();
			iv3 = iv.readUnsignedInt();
			data.position = 0;
			data = _decryptCBC(data,data.length);
			if ( usePadding ){
				data = unpad( data );
			}
		//	logger.debug( "DECRYPTION OF " + data.length + " BYTES TOOK " + ( getTimer() - startTime ) + " MS" );
			return data;
		}

		/* Cypher Block Chaining Decryption, refer to
		* http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_
		* for algorithm description
		*/
		private function _decryptCBC(crypt : ByteArray, len : uint) : ByteArray {
			var src : Vector.<uint> = new Vector.<uint>(4);
			var dst : Vector.<uint> = new Vector.<uint>(4);
			var decrypt : ByteArray = new ByteArray();
			decrypt.length = len;

			for (var i : uint = 0; i < len / 16; i++) {
				// read src byte array
				src[0] = crypt.readUnsignedInt();
				src[1] = crypt.readUnsignedInt();
				src[2] = crypt.readUnsignedInt();
				src[3] = crypt.readUnsignedInt();

				// AES decrypt src vector into dst vector
				_key.decrypt128(src, dst);

				// CBC : write output = XOR(decrypted,IV)
				decrypt.writeUnsignedInt(dst[0] ^ iv0);
				decrypt.writeUnsignedInt(dst[1] ^ iv1);
				decrypt.writeUnsignedInt(dst[2] ^ iv2);
				decrypt.writeUnsignedInt(dst[3] ^ iv3);

				// CBC : next IV = (input)
				iv0 = src[0];
				iv1 = src[1];
				iv2 = src[2];
				iv3 = src[3];
			}
			return decrypt;
		}
		public function unpad(bytesToUnpad : ByteArray) : ByteArray {
			if ((bytesToUnpad.length % 16) != 0)
			{
				throw new Error("PKCS#5::unpad: ByteArray.length isn't a multiple of the blockSize");
				return a;
			}

			const paddingValue:int = bytesToUnpad[bytesToUnpad.length - 1];
			var doUnpad:Boolean = true;
			for (var i:int = 0; i<paddingValue; i++) {
				var readValue:int = bytesToUnpad[bytesToUnpad.length - (1 + i)];
				if (paddingValue != readValue)
				{
					//throw new Error("PKCS#5:unpad: Invalid padding value. expected [" + paddingValue + "], found [" + readValue + "]");
					//break;
					doUnpad = false;
					//Break to make sure we don't underrun the byte array with a byte value bigger than 16
					break;
				}
			}

			if(doUnpad)
			{
				//subtract paddingValue + 1 since the value is one less than the number of padded bytes
				bytesToUnpad.length -= paddingValue + 1;
			}

			return bytesToUnpad;
		}

		public function retrieveStoredIV():ByteArray
		{
			CONFIG::LOGGING
			{
				logger.debug("IV of " + iv + " for " + url + ", key=" + Hex.fromArray(_keyData));
			}
			return Hex.toArray( iv );
		}

		private static function getLoader( url:String ):URLLoader
		{
			if ( LOADER_CACHE[ url ] != null ) return LOADER_CACHE[ url ] as URLLoader;
			var newLoader:URLLoader = new URLLoader();
			newLoader.dataFormat = URLLoaderDataFormat.BINARY;
			newLoader.load( new URLRequest( url ) );
			LOADER_CACHE[ url ] = newLoader;
			return newLoader;
		}

		public function load():void
		{
			isLoading = true;

			if ( isLoaded ) throw new Error( "Already loaded!" );
			var loader:URLLoader = getLoader( url );
			if ( loader.bytesTotal > 0 && loader.bytesLoaded == loader.bytesTotal ) onLoad();
			else loader.addEventListener( Event.COMPLETE, onLoad );
		}

		private function onLoad( e:Event = null ):void
		{
			isLoading = false;

			CONFIG::LOGGING
			{
				logger.debug("KEY LOADED! " + url);
			}

			var loader:URLLoader = getLoader( url );
			_keyData = loader.data as ByteArray;
			loader.removeEventListener( Event.COMPLETE, onLoad );
			dispatchEvent( new Event( Event.COMPLETE ) );
		}
	}
}

class KeyParamParser
{
	private static const STATE_PARSE_NAME:String = "ParseName";
	private static const STATE_BEGIN_PARSE_VALUE:String = "BeginParseValue";
	private static const STATE_PARSE_VALUE:String = "ParseValue";

	public static function parseParams( paramString:String ):Array
	{
		var result:Array = [];
		var cursor:int = 0;
		var state:String = STATE_PARSE_NAME;
		var accum:String = "";
		var usingQuotes:Boolean = false;

		while ( cursor < paramString.length )
		{
			var char:String = paramString.charAt( cursor );
			switch ( state )
			{
				case STATE_PARSE_NAME:

					if ( char == '=' )
					{
						result.push( accum );
						accum = "";
						state = STATE_BEGIN_PARSE_VALUE;
					}
					else accum += char;
					break;

				case STATE_BEGIN_PARSE_VALUE:

					if ( char == '"' ) usingQuotes = true;
					else accum += char;
					state = STATE_PARSE_VALUE;
					break;

				case STATE_PARSE_VALUE:

					if ( !usingQuotes && char == ',' )
					{
						result.push( accum );
						accum = "";
						state = STATE_PARSE_NAME;
						break;
					}

					if ( usingQuotes && char == '"' )
					{
						usingQuotes = false;
						break;
					}

					accum += char;
					break;
			}

			cursor++;

			if ( cursor == paramString.length ) result.push( accum );
		}

		return result;
	}
}

Update HLSManifestEncryptionKey.as

Update HLSManifestEncryptionKey.as


/*
 * Copyright (C) 2003-2007
 * Nick Velloff <nick.velloff@gmail.com>
 * Derrick Grigg <dgrigg@rogers.com>
 * Sean Voisen <sean@voisen.org>
 * Sean Treadway <seant@oncotype.dk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.jivesoftware.xiff.data
{

	import org.jivesoftware.xiff.data.ISerializable;
	import org.jivesoftware.xiff.data.XMPPStanza;
	import org.jivesoftware.xiff.data.ExtensionClassRegistry;
	import org.jivesoftware.xiff.data.xhtml.XHTMLExtension;
	import flash.xml.XMLNode;
	import org.jivesoftware.xiff.events.XIFFErrorEvent;

	/**
	 * A class for abstraction and encapsulation of message data.
	 *
	 * @param recipient The JID of the message recipient
	 * @param sender The JID of the message sender - the server should report an error if this is falsified
	 * @param msgID The message ID
	 * @param msgBody The message body in plain-text format
	 * @param msgHTMLBody The message body in XHTML format
	 * @param msgType The message type
	 * @param msgSubject (Optional) The message subject
	 */
	public class Message extends XMPPStanza implements ISerializable
	{

		// Static variables for specific type strings
		public static var NORMAL_TYPE:String = "normal";
		public static var CHAT_TYPE:String = "chat";
		public static var GROUPCHAT_TYPE:String = "groupchat";
		public static var HEADLINE_TYPE:String = "headline";
		public static var ERROR_TYPE:String = "error";

		// Private references to nodes within our XML
		private var myBodyNode:XMLNode;
		private var mySubjectNode:XMLNode;
		private var myThreadNode:XMLNode;
		private var myTimeStampNode:XMLNode;

		private static var isMessageStaticCalled:Boolean = MessageStaticConstructor();
		private static var staticConstructorDependency:Array = [ XMPPStanza, XHTMLExtension, ExtensionClassRegistry ];

		public function Message( recipient:String=null, msgID:String=null, msgBody:String=null, msgHTMLBody:String=null, msgType:String=null, msgSubject:String=null )
		{
			// Flash gives a warning if superconstructor is not first, hence the inline id check
			var msgId:String = exists( msgID ) ? msgID : generateID("m_");
			super( recipient, null, msgType, msgId, "message" );
			body = msgBody;
			htmlBody = msgHTMLBody;
			subject = msgSubject;
		}

		public static function MessageStaticConstructor():Boolean
		{
			XHTMLExtension.enable();
			return true;
		}

		/**
		 * Serializes the Message into XML form for sending to a server.
		 *
		 * @return An indication as to whether serialization was successful
		 */
		override public function serialize( parentNode:XMLNode ):Boolean
		{
			return super.serialize( parentNode );
		}

		/**
		 * Deserializes an XML object and populates the Message instance with its data.
		 *
		 * @param xmlNode The XML to deserialize
		 * @return An indication as to whether deserialization was sucessful
		 */
		override public function deserialize( xmlNode:XMLNode ):Boolean
		{
			var isSerialized:Boolean = super.deserialize( xmlNode );
			if (isSerialized) {
				var children:Array = xmlNode.childNodes;
				for( var i:String in children )
				{
					trace(children[i].nodeName);
					switch( children[i].nodeName )
					{
						// Adding error handler for 404 sent back by server
						case "error":

							break;
						case "body":
							myBodyNode = children[i];
							break;

						case "subject":
							mySubjectNode = children[i];
							break;

						case "thread":
							myThreadNode = children[i];
							break;

						case "x":
							if(children[i].attributes.xmlns == "jabber:x:delay")
								myTimeStampNode = children[i];

							break;
					}
				}
			}
			return isSerialized;
		}

		/**
		 * The message body in plain-text format. If a client cannot render HTML-formatted
		 * text, this text is typically used instead.
		 */
		public function get body():String
		{
			if (!exists(myBodyNode)){
				return null;
			}
			var value: String = '';

			try
			{
				value =  myBodyNode.firstChild.nodeValue;
			}

			catch (error:Error)
			{
				trace (error.getStackTrace());
			}
			return value;
		}

		/**
		 * @private
		 */
		public function set body( bodyText:String ):void
		{
			myBodyNode = replaceTextNode(getNode(), myBodyNode, "body", bodyText);
		}

		/**
		 * The message body in XHTML format. Internally, this uses the XHTML data extension.
		 *
		 * @see org.jivesoftware.xiff.data.xhtml.XHTMLExtension
		 */
		public function get htmlBody():String
		{
			try
			{
				var ext:XHTMLExtension = getAllExtensionsByNS(XHTMLExtension.NS)[0];
				return ext.body;
			}
			catch (e:Error)
			{
				trace("Error : null trapped. Resuming.");
			}
			return null;
		}

		/**
		 * @private
		 */
		public function set htmlBody( bodyHTML:String ):void
		{
			// Removes any existing HTML body text first
	        removeAllExtensions(XHTMLExtension.NS);

	        if (exists(bodyHTML) && bodyHTML.length > 0) {
	            var ext:XHTMLExtension = new XHTMLExtension(getNode());
	            ext.body = bodyHTML;
	            addExtension(ext);
	        }
		}

		/**
		 * The message subject. Typically chat and groupchat-type messages do not use
		 * subjects. Rather, this is reserved for normal and headline-type messages.
		 */
		public function get subject():String
		{
			if (mySubjectNode == null) return null;
			return mySubjectNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set subject( aSubject:String ):void
		{
			mySubjectNode = replaceTextNode(getNode(), mySubjectNode, "subject", aSubject);
		}

		/**
		 * The message thread ID. Threading is used to group messages of the same discussion together.
		 * The library does not perform message grouping, rather it is up to any client authors to
		 * properly perform this task.
		 */
		public function get thread():String
		{
			if (myThreadNode == null) return null;
			return myThreadNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set thread( theThread:String ):void
		{
			myThreadNode = replaceTextNode(getNode(), myThreadNode, "thread", theThread);
		}

		public function set time( theTime:Date ): void
		{

		}

		public function get time():Date
		{
			if(myTimeStampNode == null) return null;
			var stamp:String = myTimeStampNode.attributes.stamp;

			var t:Date = new Date();
			//CCYYMMDDThh:mm:ss
			//20020910T23:41:07
			t.setUTCFullYear(stamp.slice(0, 4)); //2002
			t.setUTCMonth(stamp.slice(4, 6)); //09
			t.setUTCDate(stamp.slice(6, 8)); //10
											 //T
			t.setUTCHours(stamp.slice(9, 11)); //23
												//:
			t.setUTCMinutes(stamp.slice(12, 14)); //41
												//:
			t.setUTCSeconds(stamp.slice(15, 17)); //07
			return t;
		}
	}
}

/*
 * Copyright (C) 2003-2007
 * Nick Velloff <nick.velloff@gmail.com>
 * Derrick Grigg <dgrigg@rogers.com>
 * Sean Voisen <sean@voisen.org>
 * Sean Treadway <seant@oncotype.dk>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package org.jivesoftware.xiff.data
{

	import org.jivesoftware.xiff.data.ISerializable;
	import org.jivesoftware.xiff.data.XMPPStanza;
	import org.jivesoftware.xiff.data.ExtensionClassRegistry;
	import org.jivesoftware.xiff.data.xhtml.XHTMLExtension;
	import flash.xml.XMLNode;
	import org.jivesoftware.xiff.events.XIFFErrorEvent;

	/**
	 * A class for abstraction and encapsulation of message data.
	 *
	 * @param recipient The JID of the message recipient
	 * @param sender The JID of the message sender - the server should report an error if this is falsified
	 * @param msgID The message ID
	 * @param msgBody The message body in plain-text format
	 * @param msgHTMLBody The message body in XHTML format
	 * @param msgType The message type
	 * @param msgSubject (Optional) The message subject
	 */
	public class Message extends XMPPStanza implements ISerializable
	{

		// Static variables for specific type strings
		public static var NORMAL_TYPE:String = "normal";
		public static var CHAT_TYPE:String = "chat";
		public static var GROUPCHAT_TYPE:String = "groupchat";
		public static var HEADLINE_TYPE:String = "headline";
		public static var ERROR_TYPE:String = "error";

		// Private references to nodes within our XML
		private var myBodyNode:XMLNode;
		private var mySubjectNode:XMLNode;
		private var myThreadNode:XMLNode;
		private var myTimeStampNode:XMLNode;

		private static var isMessageStaticCalled:Boolean = MessageStaticConstructor();
		private static var staticConstructorDependency:Array = [ XMPPStanza, XHTMLExtension, ExtensionClassRegistry ];

		public function Message( recipient:String=null, msgID:String=null, msgBody:String=null, msgHTMLBody:String=null, msgType:String=null, msgSubject:String=null )
		{
			// Flash gives a warning if superconstructor is not first, hence the inline id check
			var msgId:String = exists( msgID ) ? msgID : generateID("m_");
			super( recipient, null, msgType, msgId, "message" );
			body = msgBody;
			htmlBody = msgHTMLBody;
			subject = msgSubject;
		}

		public static function MessageStaticConstructor():Boolean
		{
			XHTMLExtension.enable();
			return true;
		}

		/**
		 * Serializes the Message into XML form for sending to a server.
		 *
		 * @return An indication as to whether serialization was successful
		 */
		override public function serialize( parentNode:XMLNode ):Boolean
		{
			return super.serialize( parentNode );
		}

		/**
		 * Deserializes an XML object and populates the Message instance with its data.
		 *
		 * @param xmlNode The XML to deserialize
		 * @return An indication as to whether deserialization was sucessful
		 */
		override public function deserialize( xmlNode:XMLNode ):Boolean
		{
			var isSerialized:Boolean = super.deserialize( xmlNode );
			if (isSerialized) {
				var children:Array = xmlNode.childNodes;
				for( var i:String in children )
				{
					trace(children[i].nodeName);
					switch( children[i].nodeName )
					{
						// Adding error handler for 404 sent back by server
						case "error":

							break;
						case "body":
							myBodyNode = children[i];
							break;

						case "subject":
							mySubjectNode = children[i];
							break;

						case "thread":
							myThreadNode = children[i];
							break;

						case "x":
							if(children[i].attributes.xmlns == "jabber:x:delay")
								myTimeStampNode = children[i];

							break;
					}
				}
			}
			return isSerialized;
		}

		/**
		 * The message body in plain-text format. If a client cannot render HTML-formatted
		 * text, this text is typically used instead.
		 */
		public function get body():String
		{
			if (!exists(myBodyNode)){
				return null;
			}
			var value: String = '';

			try
			{
				value =  myBodyNode.firstChild.nodeValue;
			}

			catch (error:Error)
			{
				trace (error.getStackTrace());
			}
			return value;
		}

		/**
		 * @private
		 */
		public function set body( bodyText:String ):void
		{
			myBodyNode = replaceTextNode(getNode(), myBodyNode, "body", bodyText);
		}

		/**
		 * The message body in XHTML format. Internally, this uses the XHTML data extension.
		 *
		 * @see org.jivesoftware.xiff.data.xhtml.XHTMLExtension
		 */
		public function get htmlBody():String
		{
			try
			{
				var ext:XHTMLExtension = getAllExtensionsByNS(XHTMLExtension.NS)[0];
				return ext.body;
			}
			catch (e:Error)
			{
				trace("Error : null trapped. Resuming.");
			}
			return null;
		}

		/**
		 * @private
		 */
		public function set htmlBody( bodyHTML:String ):void
		{
			// Removes any existing HTML body text first
	        removeAllExtensions(XHTMLExtension.NS);

	        if (exists(bodyHTML) && bodyHTML.length > 0) {
	            var ext:XHTMLExtension = new XHTMLExtension(getNode());
	            ext.body = bodyHTML;
	            addExtension(ext);
	        }
		}

		/**
		 * The message subject. Typically chat and groupchat-type messages do not use
		 * subjects. Rather, this is reserved for normal and headline-type messages.
		 */
		public function get subject():String
		{
			if (mySubjectNode == null) return null;
			return mySubjectNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set subject( aSubject:String ):void
		{
			mySubjectNode = replaceTextNode(getNode(), mySubjectNode, "subject", aSubject);
		}

		/**
		 * The message thread ID. Threading is used to group messages of the same discussion together.
		 * The library does not perform message grouping, rather it is up to any client authors to
		 * properly perform this task.
		 */
		public function get thread():String
		{
			if (myThreadNode == null) return null;
			return myThreadNode.firstChild.nodeValue;
		}

		/**
		 * @private
		 */
		public function set thread( theThread:String ):void
		{
			myThreadNode = replaceTextNode(getNode(), myThreadNode, "thread", theThread);
		}

		public function set time( theTime:Date ): void
		{

		}

		public function get time():Date
		{
			if(myTimeStampNode == null) return null;
			var stamp:String = myTimeStampNode.attributes.stamp;

			var t:Date = new Date();
			//CCYYMMDDThh:mm:ss
			//20020910T23:41:07
			t.setUTCFullYear(stamp.slice(0, 4)); //2002
			t.setUTCMonth(Number(stamp.slice(4, 6)) - 1); //09
			t.setUTCDate(stamp.slice(6, 8)); //10
											 //T
			t.setUTCHours(stamp.slice(9, 11)); //23
												//:
			t.setUTCMinutes(stamp.slice(12, 14)); //41
												//:
			t.setUTCSeconds(stamp.slice(15, 17)); //07
			return t;
		}
	}
}

Fix an off-by-one error due to the way flex deals with dates. Thanks to Bill Bailey for pointing this out :)

git-svn-id: c197267f952b24206666de142881703007ca05d5@8984 b35dd754-fafc-0310-a699-88a17e54d16e