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

package com.freshplanet.ane.AirCapabilities {

import com.freshplanet.ane.AirCapabilities.events.AirCapabilitiesLowMemoryEvent;
import com.freshplanet.ane.AirCapabilities.events.AirCapabilitiesOpenURLEvent;
	import flash.display.BitmapData;
	import flash.events.EventDispatcher;
	import flash.events.StatusEvent;
	import flash.external.ExtensionContext;
	import flash.system.Capabilities;

	public class AirCapabilities extends EventDispatcher {

		// --------------------------------------------------------------------------------------//
		//																						 //
		// 									   PUBLIC API										 //
		// 																						 //
		// --------------------------------------------------------------------------------------//

		/**
		 * Is the ANE supported on the current platform
		 */
        static public function get isSupported():Boolean {
            return (Capabilities.manufacturer.indexOf("iOS") > -1 && Capabilities.os.indexOf("x86_64") < 0 && Capabilities.os.indexOf("i386") < 0) || Capabilities.manufacturer.indexOf("Android") > -1;
        }

		/**
		 * If true, logs will be displayed at the ActionScript and native level.
		 */
		public function setLogging(value:Boolean):void {

			_doLogging = value;
			if (isSupported)
				_extContext.call("setLogging", value);
		}

		public function get nativeLogger():ILogger {
            return _logger;
        }

        /**
         * AirCapabilities instance
         */
        static public function get instance():AirCapabilities {
			return _instance != null ? _instance : new AirCapabilities()
		}

        /**
         * Is SMS available
         * @return
         */
		public function hasSmsEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasSMS");

			return false;
		}

        /**
         * Is Twitter available
         * @return
         */
		public function hasTwitterEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasTwitter");

			return false;
		}

        /**
         * Sends an SMS message
         * @param message to send
         * @param recipient phonenumber
         */
		public function sendMsgWithSms(message:String, recipient:String = null):void {

			if (isSupported)
				_extContext.call("sendWithSms", message, recipient);
		}

        /**
         * Sends a Twitter message
         * @param message
         */
		public function sendMsgWithTwitter(message:String):void {

			if (isSupported)
				_extContext.call("sendWithTwitter", message);
		}

        /**
         * Redirect user to Rating page
         * @param appId
         * @param appName
         */
		public function redirecToRating(appId:String, appName:String):void {

			if (isSupported)
				_extContext.call("redirectToRating", appId, appName);
		}

        /**
         * Model of the device
         * @return
         */
		public function getDeviceModel():String {

			if (isSupported)
				return _extContext.call("getDeviceModel") as String;

            return "";
		}

        /**
         * Name of the machine
         * @return
         */
		public function getMachineName():String {

			if (isSupported)
				return _extContext.call("getMachineName") as String;

            return "";
		}

        /**
         * Opens the referral link URL
         * @param url
         */
		public function processReferralLink(url:String):void {

			if (isSupported)
				_extContext.call("processReferralLink", url);
		}

        /**
         * Opens Facebook page
         * @param pageId id of the facebook page
         */
		public function redirectToPageId(pageId:String):void {

			if (isSupported)
				_extContext.call("redirectToPageId", pageId);
		}

        /**
         * Opens Twitter account
         * @param twitterAccount
         */
		public function redirectToTwitterAccount(twitterAccount:String):void {

			if (isSupported)
				_extContext.call("redirectToTwitterAccount", twitterAccount);
		}

        /**
         * Is posting pictures on Twitter enabled
         * @return
         */
		public function canPostPictureOnTwitter():Boolean {

			if (isSupported)
				return _extContext.call("canPostPictureOnTwitter");

			return false;
		}

        /**
         * Post new picture on Twitter
         * @param message
         * @param bitmapData
         */
		public function postPictureOnTwitter(message:String, bitmapData:BitmapData):void {

			if (isSupported)
				_extContext.call("postPictureOnTwitter", message, bitmapData);
		}

        /**
         * Is Instagram enabled
         * @return
         */
		public function hasInstagramEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasInstagramEnabled");

			return false;
		}

        /**
         * Post new picture on Instagram
         * @param message
         * @param bitmapData
         * @param x
         * @param y
         * @param width
         * @param height
         */
		public function postPictureOnInstagram(message:String, bitmapData:BitmapData,
                                               x:int, y:int, width:int, height:int):void {

			if (isSupported)
				_extContext.call("postPictureOnInstagram", message, bitmapData, x, y, width, height);
		}

		/**
		 * Open an application (if installed on the Device) or send the player to the appstore.  iOS Only
		 * @param schemes 		List of schemes (String) that the application accepts.  Examples : @"sms://", @"twit://".  You can find schemes in http://handleopenurl.com/
		 * @param appStoreURL 	(optional) Link to the AppStore page for the Application for the player to download. URL can be generated via Apple's linkmaker (itunes.apple.com/linkmaker?)
		 */
		public function openExternalApplication(schemes:Array, appStoreURL:String = null):void {

			if (isSupported && Capabilities.manufacturer.indexOf("Android") == -1)
				_extContext.call("openExternalApplication", schemes, appStoreURL);
		}

        /**
         * Is opening URLs available
         * @param url
         * @return
         */
		public function canOpenURL(url:String):Boolean {
			return isSupported ? _extContext.call("canOpenURL", url) as Boolean : false;
		}

        /**
         * Open URL
         * @param url
         */
		public function openURL(url:String):void {

			if (canOpenURL(url))
				_extContext.call("openURL", url);
		}

		/**
		 * Opens modal app store. Available on iOS only
		 * @param appStoreId	id of the app to open a modal view to (do not include the "id" at the beginning of the number)
		 */
		public function openModalAppStoreIOS(appStoreId:String):void {

			if (Capabilities.manufacturer.indexOf("iOS") > -1)
				_extContext.call("openModalAppStore", appStoreId);
		}

        /**
         * Version of the operating system
         * @return
         */
        public function getOSVersion():String {

            if (isSupported)
                return _extContext.call("getOSVersion") as String;

            return "";
        }

        /**
         * @return  current amount of RAM being used in bytes
         */
        public function getCurrentMem():Number {

			if (!isSupported)
				return -1;

            var ret:Object = _extContext.call("getCurrentMem");
            if (ret is Error)
                throw ret;
            else
                return ret ? ret as Number : 0;
        }

        /**
         * @return  amount of RAM used by the VM
         */
        public function getCurrentVirtualMem():Number {

			if(!isSupported)
				return -1;

            if (Capabilities.manufacturer.indexOf("iOS") == -1)
                return -1;

            var ret:Object = _extContext.call("getCurrentVirtualMem");
            if (ret is Error)
                throw ret;
            else
                return ret ? ret as Number : 0;
        }

		/**
		 * @return  is requesting review available. Available on iOS only
		 */
		public function canRequestReview():Boolean {

			if (!isSupported)
				return false;

			if (Capabilities.manufacturer.indexOf("iOS") == -1)
				return false;

			return _extContext.call("canRequestReview");
		}

		/**
		 * Request AppStore review. Available on iOS only
		 */
		public function requestReview():void {

			if (!canRequestReview())
				return;

			_extContext.call("requestReview");
		}
		/**
		 * Generate haptic feedback - iOS only
		 */
		public function generateHapticFeedback(feedbackType:AirCapabilitiesHapticFeedbackType):void {

			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			if(!feedbackType)
				return

			_extContext.call("generateHapticFeedback", feedbackType.value);
		}

		public function getNativeScale():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 1;
			return _extContext.call("getNativeScale") as Number;
		}

		public function openAdSettings():void
		{
			if (Capabilities.manufacturer.indexOf("Android") < 0)
				return;

			_extContext.call("openAdSettings");
		}

		public function getTopInset():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 0;
			return _extContext.call("getTopInset") as Number;
		}

		public function getBottomInset():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 0;
			return _extContext.call("getBottomInset") as Number;
		}

		public function get iOSAppOnMac():Boolean
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return false;

			return _extContext.call("iOSAppOnMac") as Boolean;
		}

		public function switchToLandscape():void
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			_extContext.call("switchToLandscape");
		}

		public function switchToPortrait():void
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			_extContext.call("switchToPortrait");
		}

		// --------------------------------------------------------------------------------------//
		//																						 //
		// 									 	PRIVATE API										 //
		// 																						 //
		// --------------------------------------------------------------------------------------//

		static private const EXTENSION_ID:String = "com.freshplanet.ane.AirCapabilities";

		static private var _instance:AirCapabilities = null;

		private var _extContext:ExtensionContext = null;
		private var _doLogging:Boolean = false;
		private var _logger:ILogger;

		/**
		 * "private" singleton constructor
		 */
		public function AirCapabilities() {

			super();

			if (_instance)
				throw new Error("singleton class, use .instance");

			_extContext = ExtensionContext.createExtensionContext(EXTENSION_ID, null);
			_extContext.addEventListener(StatusEvent.STATUS, _handleStatusEvent);

			if (isSupported)
				_logger = new NativeLogger(_extContext);
			else
				_logger = new DefaultLogger();

			_instance = this;
		}

        private function _handleStatusEvent(event:StatusEvent):void {

            if (event.code == "log")
	            _doLogging && trace("[AirCapabilities] " + event.level);
            else if (event.code == "OPEN_URL")
                this.dispatchEvent(new AirCapabilitiesOpenURLEvent(AirCapabilitiesOpenURLEvent.OPEN_URL_SUCCESS, event.level));
            else if (event.code == AirCapabilitiesLowMemoryEvent.LOW_MEMORY_WARNING) {
	            var memory:Number = Number(event.level);
	            this.dispatchEvent(new AirCapabilitiesLowMemoryEvent(event.code, memory));
            }
            else
                this.dispatchEvent(event);
        }
	}
}

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

package com.freshplanet.ane.AirCapabilities {

import com.freshplanet.ane.AirCapabilities.events.AirCapabilitiesLowMemoryEvent;
import com.freshplanet.ane.AirCapabilities.events.AirCapabilitiesOpenURLEvent;
	import flash.display.BitmapData;
	import flash.events.EventDispatcher;
	import flash.events.StatusEvent;
	import flash.external.ExtensionContext;
	import flash.system.Capabilities;

	public class AirCapabilities extends EventDispatcher {

		// --------------------------------------------------------------------------------------//
		//																						 //
		// 									   PUBLIC API										 //
		// 																						 //
		// --------------------------------------------------------------------------------------//

		/**
		 * Is the ANE supported on the current platform
		 */
        static public function get isSupported():Boolean {
            return (Capabilities.manufacturer.indexOf("iOS") > -1 && Capabilities.os.indexOf("x86_64") < 0 && Capabilities.os.indexOf("i386") < 0) || Capabilities.manufacturer.indexOf("Android") > -1;
        }

		/**
		 * If true, logs will be displayed at the ActionScript and native level.
		 */
		public function setLogging(value:Boolean):void {

			_doLogging = value;
			if (isSupported)
				_extContext.call("setLogging", value);
		}

		public function get nativeLogger():ILogger {
            return _logger;
        }

        /**
         * AirCapabilities instance
         */
        static public function get instance():AirCapabilities {
			return _instance != null ? _instance : new AirCapabilities()
		}

        /**
         * Is SMS available
         * @return
         */
		public function hasSmsEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasSMS");

			return false;
		}

        /**
         * Is Twitter available
         * @return
         */
		public function hasTwitterEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasTwitter");

			return false;
		}

        /**
         * Sends an SMS message
         * @param message to send
         * @param recipient phonenumber
         */
		public function sendMsgWithSms(message:String, recipient:String = null):void {

			if (isSupported)
				_extContext.call("sendWithSms", message, recipient);
		}

        /**
         * Sends a Twitter message
         * @param message
         */
		public function sendMsgWithTwitter(message:String):void {

			if (isSupported)
				_extContext.call("sendWithTwitter", message);
		}

        /**
         * Redirect user to Rating page
         * @param appId
         * @param appName
         */
		public function redirecToRating(appId:String, appName:String):void {

			if (isSupported)
				_extContext.call("redirectToRating", appId, appName);
		}

        /**
         * Model of the device
         * @return
         */
		public function getDeviceModel():String {

			if (isSupported)
				return _extContext.call("getDeviceModel") as String;

            return "";
		}

        /**
         * Name of the machine
         * @return
         */
		public function getMachineName():String {

			if (isSupported)
				return _extContext.call("getMachineName") as String;

            return "";
		}

        /**
         * Opens the referral link URL
         * @param url
         */
		public function processReferralLink(url:String):void {

			if (isSupported)
				_extContext.call("processReferralLink", url);
		}

        /**
         * Opens Facebook page
         * @param pageId id of the facebook page
         */
		public function redirectToPageId(pageId:String):void {

			if (isSupported)
				_extContext.call("redirectToPageId", pageId);
		}

        /**
         * Opens Twitter account
         * @param twitterAccount
         */
		public function redirectToTwitterAccount(twitterAccount:String):void {

			if (isSupported)
				_extContext.call("redirectToTwitterAccount", twitterAccount);
		}

        /**
         * Is posting pictures on Twitter enabled
         * @return
         */
		public function canPostPictureOnTwitter():Boolean {

			if (isSupported)
				return _extContext.call("canPostPictureOnTwitter");

			return false;
		}

        /**
         * Post new picture on Twitter
         * @param message
         * @param bitmapData
         */
		public function postPictureOnTwitter(message:String, bitmapData:BitmapData):void {

			if (isSupported)
				_extContext.call("postPictureOnTwitter", message, bitmapData);
		}

        /**
         * Is Instagram enabled
         * @return
         */
		public function hasInstagramEnabled():Boolean {

			if (isSupported)
				return _extContext.call("hasInstagramEnabled");

			return false;
		}

        /**
         * Post new picture on Instagram
         * @param message
         * @param bitmapData
         * @param x
         * @param y
         * @param width
         * @param height
         */
		public function postPictureOnInstagram(message:String, bitmapData:BitmapData,
                                               x:int, y:int, width:int, height:int):void {

			if (isSupported)
				_extContext.call("postPictureOnInstagram", message, bitmapData, x, y, width, height);
		}

		/**
		 * Open an application (if installed on the Device) or send the player to the appstore.  iOS Only
		 * @param schemes 		List of schemes (String) that the application accepts.  Examples : @"sms://", @"twit://".  You can find schemes in http://handleopenurl.com/
		 * @param appStoreURL 	(optional) Link to the AppStore page for the Application for the player to download. URL can be generated via Apple's linkmaker (itunes.apple.com/linkmaker?)
		 */
		public function openExternalApplication(schemes:Array, appStoreURL:String = null):void {

			if (isSupported && Capabilities.manufacturer.indexOf("Android") == -1)
				_extContext.call("openExternalApplication", schemes, appStoreURL);
		}

        /**
         * Is opening URLs available
         * @param url
         * @return
         */
		public function canOpenURL(url:String):Boolean {
			return isSupported ? _extContext.call("canOpenURL", url) as Boolean : false;
		}

        /**
         * Open URL
         * @param url
         */
		public function openURL(url:String):void {

			if (canOpenURL(url))
				_extContext.call("openURL", url);
		}

		/**
		 * Opens modal app store. Available on iOS only
		 * @param appStoreId	id of the app to open a modal view to (do not include the "id" at the beginning of the number)
		 */
		public function openModalAppStoreIOS(appStoreId:String):void {

			if (Capabilities.manufacturer.indexOf("iOS") > -1)
				_extContext.call("openModalAppStore", appStoreId);
		}

        /**
         * Version of the operating system
         * @return
         */
        public function getOSVersion():String {

            if (isSupported)
                return _extContext.call("getOSVersion") as String;

            return "";
        }

        /**
         * @return  current amount of RAM being used in bytes
         */
        public function getCurrentMem():Number {

			if (!isSupported)
				return -1;

            var ret:Object = _extContext.call("getCurrentMem");
            if (ret is Error)
                throw ret;
            else
                return ret ? ret as Number : 0;
        }

        /**
         * @return  amount of RAM used by the VM
         */
        public function getCurrentVirtualMem():Number {

			if(!isSupported)
				return -1;

            if (Capabilities.manufacturer.indexOf("iOS") == -1)
                return -1;

            var ret:Object = _extContext.call("getCurrentVirtualMem");
            if (ret is Error)
                throw ret;
            else
                return ret ? ret as Number : 0;
        }

		/**
		 * @return  is requesting review available. Available on iOS only
		 */
		public function canRequestReview():Boolean {

			if (!isSupported)
				return false;

			if (Capabilities.manufacturer.indexOf("iOS") == -1)
				return false;

			return _extContext.call("canRequestReview");
		}

		/**
		 * Request AppStore review. Available on iOS only
		 */
		public function requestReview():void {

			if (!canRequestReview())
				return;

			_extContext.call("requestReview");
		}
		/**
		 * Generate haptic feedback - iOS only
		 */
		public function generateHapticFeedback(feedbackType:AirCapabilitiesHapticFeedbackType):void {

			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			if(!feedbackType)
				return

			_extContext.call("generateHapticFeedback", feedbackType.value);
		}

		public function getNativeScale():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 1;
			return _extContext.call("getNativeScale") as Number;
		}

		public function openAdSettings():void
		{
			if (Capabilities.manufacturer.indexOf("Android") < 0)
				return;

			_extContext.call("openAdSettings");
		}

		public function getTopInset():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 0;
			return _extContext.call("getTopInset") as Number;
		}

		public function getBottomInset():Number
		{
			if (Capabilities.manufacturer.indexOf("iOS") < 0)
				return 0;
			return _extContext.call("getBottomInset") as Number;
		}

		public function get iOSAppOnMac():Boolean
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return false;

			return _extContext.call("iOSAppOnMac") as Boolean;
		}

		public function switchToLandscape():void
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			_extContext.call("switchToLandscape");
		}

		public function switchToPortrait():void
		{
			if(Capabilities.manufacturer.indexOf("iOS") < 0)
				return;

			_extContext.call("switchToPortrait");
		}

		/**
		 * MacOS only!
		 */
		public function forceFullscreen():void
		{
			if(Capabilities.os.indexOf("Mac OS") < 0)
				return;

			_extContext.call("forceFullscreen");
		}

		/**
		 * MacOS only!
		 */
		public function exitFullscreen():void
		{
			if(Capabilities.os.indexOf("Mac OS") < 0)
				return;

			_extContext.call("exitFullscreen");
		}

		// --------------------------------------------------------------------------------------//
		//																						 //
		// 									 	PRIVATE API										 //
		// 																						 //
		// --------------------------------------------------------------------------------------//

		static private const EXTENSION_ID:String = "com.freshplanet.ane.AirCapabilities";

		static private var _instance:AirCapabilities = null;

		private var _extContext:ExtensionContext = null;
		private var _doLogging:Boolean = false;
		private var _logger:ILogger;

		/**
		 * "private" singleton constructor
		 */
		public function AirCapabilities() {

			super();

			if (_instance)
				throw new Error("singleton class, use .instance");

			_extContext = ExtensionContext.createExtensionContext(EXTENSION_ID, null);
			_extContext.addEventListener(StatusEvent.STATUS, _handleStatusEvent);

			if (isSupported)
				_logger = new NativeLogger(_extContext);
			else
				_logger = new DefaultLogger();

			_instance = this;
		}

        private function _handleStatusEvent(event:StatusEvent):void {

            if (event.code == "log")
	            _doLogging && trace("[AirCapabilities] " + event.level);
            else if (event.code == "OPEN_URL")
                this.dispatchEvent(new AirCapabilitiesOpenURLEvent(AirCapabilitiesOpenURLEvent.OPEN_URL_SUCCESS, event.level));
            else if (event.code == AirCapabilitiesLowMemoryEvent.LOW_MEMORY_WARNING) {
	            var memory:Number = Number(event.level);
	            this.dispatchEvent(new AirCapabilitiesLowMemoryEvent(event.code, memory));
            }
            else
                this.dispatchEvent(event);
        }
	}
}

add as3 side

add as3 side


package com.kaltura.kdpfl.plugin.component
{
	import flash.net.SharedObject;

	import org.puremvc.as3.interfaces.IFacade;

	public class PersistentData {

		private var _soName:String = "kaltura/vast_";

		public static const millisecondsPerDay:int = 1000 * 60 * 60 * 24;

		/**
		 * the shared object used for storing data
		 */
		private var _so:SharedObject;

		public function PersistentData(){}

		public function init(facade:IFacade) :void {
			var config:Object = facade.retrieveProxy("configProxy");
			_so = SharedObject.getLocal(_soName + config.vo.kuiConf.id);
		}

		/**
		 * check if persistence data is from the last 24 hours
		 * @return true | false
		 */
		public function isPersistenceValid():Boolean {
			var result:Boolean = true;
			var pd:Object = _so.data;
			if (pd.date == null) {
				// no saved data
				result = false;
			}
			else {
				var date:Date = new Date();
				var dif:Number = date.getTime() - pd.date.getTime();
				if (dif > millisecondsPerDay) {
					// more than 24 hours
					result = false;
				}
			}
			return result;
		}

		/**
		 * resets persistent data
		 */
		public function resetPersistenceData():void {
			var pd:Object = _so.data;
			pd.date = new Date();
			// should reset to 1 and not 0 because the value would have been increased by now.
			pd.prerollEntries = 1;
			pd.prerollFirstShow = false;
			pd.postrollEntries = 1;
			pd.postrollFirstShow = false;
			_so.flush();
		}

		/**
		 * match persistent data with plugin state
		 * */
		public function updatePersistentData(o:Object):void {
			var pd:Object = _so.data;
			pd.prerollFirstShow = o.prerollFirstShow;
			pd.prerollEntries = o.prerollEntries;
			pd.postrollFirstShow = o.postrollFirstShow;
			pd.postrollEntries = o.postrollEntries;
			_so.flush();
		}

		/**
		 * saved data
		 */
		public function get data():Object {
			return _so.data;
		}

	}
}

package com.kaltura.kdpfl.plugin.component
{
	import flash.net.SharedObject;

	import org.puremvc.as3.interfaces.IFacade;

	public class PersistentData {

		private var _soName:String = "kaltura/vast_";

		public static const millisecondsPerDay:int = 1000 * 60 * 60 * 24;

		/**
		 * the shared object used for storing data
		 */
		private var _so:SharedObject;

		private var _config:Object

		public function PersistentData(){}

		public function init(facade:IFacade) :void {
			_config = facade.retrieveProxy("configProxy");
			_so = SharedObject.getLocal(_soName + _config.vo.kuiConf.id);
		}

		/**
		 * check if persistence data is from the last 24 hours
		 * @return true | false
		 */
		public function isPersistenceValid():Boolean {
			var result:Boolean = true;
			var pd:Object = _so.data;
			if (pd.date == null) {
				// no saved data
				result = false;
			}
			else {
				var date:Date = new Date();
				var dif:Number = date.getTime() - pd.date.getTime();
				if (dif > millisecondsPerDay) {
					// more than 24 hours
					result = false;
				}
			}
			return result;
		}

		/**
		 * resets persistent data
		 */
		public function resetPersistenceData():void {
			var pd:Object = _so.data;
			pd.date = new Date();
			// should reset to 1 and not 0 because the value would have been increased by now.
			pd.prerollEntries = 1;
			pd.prerollFirstShow = false;
			pd.postrollEntries = 1;
			pd.postrollFirstShow = false;
			if (_config.vo.flashvars.allowCookies=="true")
				_so.flush();
		}

		/**
		 * match persistent data with plugin state
		 * */
		public function updatePersistentData(o:Object):void {
			var pd:Object = _so.data;
			pd.prerollFirstShow = o.prerollFirstShow;
			pd.prerollEntries = o.prerollEntries;
			pd.postrollFirstShow = o.postrollFirstShow;
			pd.postrollEntries = o.postrollEntries;
			if (_config.vo.flashvars.allowCookies=="true")
				_so.flush();
		}

		/**
		 * saved data
		 */
		public function get data():Object {
			return _so.data;
		}

	}
}

support in "allowCookies" flashvar

qnd: support in "allowCookies" flashvar

git-svn-id: 3f608e5a9a704dd448217c0a64c508e7f145cfa1@85785 6b8eccd3-e8c5-4e7d-8186-e12b5326b719


package flare.animate
{
	import flash.display.Shape;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	/**
	 * Scheduler that oversees animation and time-based processing. Uses an
	 * internal timer to regularly invoke the current set of scheduled
	 * objects. Typically, interaction with the scheduler is automatically
	 * handled by Transition classes. However, custom implmentations of
	 * the ISchedulable interface will need to be scheduled. Use the
	 * <tt>Scheduler.instance</tt> property, and not the constructor, to get
	 * a reference to the active scheduler.
	 *
	 *
By default, the Scheduler issues updates to all scheduled items each
	 * time the Flash Player advances to the next frame, as reported by the
	 * Event.ENTER_FRAME event. To instead set the update interval
	 * manually, see the timerInterval property.

	 */
	public class Scheduler
	{
		private static const _instance:Scheduler = new Scheduler(_Lock);
		/** The default Scheduler instance. */
		public static function get instance():Scheduler { return _instance; }

		private var _scheduled:Array; // list of all currently scheduled items
		private var _ids:Object;      // map of all named items
		private var _timer:Timer;     // timer for interval-based scheduling
		private var _obj:Shape;       // shape for getting ENTER_FRAME events

		/**
		 * Sets the timer interval (in milliseconds) at which the scheduler
		 * should process events. If this value is greater than zero, a
		 * Timer instance will be used to trigger scheduler
		 * updates at the given interval. If this value is less than or equal
		 * to zero (the default), scheduler updates will be issued with each
		 * time the Flash Player advances to the next frame according to the
		 * Event.ENTER_FRAME event.
		 */
		public function get timerInterval():Number { return _timer.delay; }
		public function set timerInterval(t:Number):void {
			pause(); _timer.delay = (t>0 ? t : 0); play();
		}

		/**
		 * Creates a new Scheduler--this constructor should be not used;
		 * instead use the instance property.
		 * @param lock a lock object to emulate a private constructor
		 */
		public function Scheduler(lock:Class) {
			if (lock == _Lock) {
				_scheduled = [];
				_ids = {};
				_obj = new Shape();
				_timer = new Timer(0);
				_timer.addEventListener(TimerEvent.TIMER, tick);
			} else {
				throw new Error("Invalid constructor. Use Scheduler.instance.");
			}
		}

		/**
		 * Plays the scheduler, allowing it to process events.
		 */
		private function play():void
		{
			if (timerInterval <= 0) {
				if (!_obj.hasEventListener(Event.ENTER_FRAME))
					_obj.addEventListener(Event.ENTER_FRAME, tick);
			} else if (!_timer.running) {
				_timer.start();
			}
		}

		/**
		 * Pauses the scheduler, so that events are not processed.
		 */
		private function pause():void
		{
			if (timerInterval <= 0) {
				_obj.removeEventListener(Event.ENTER_FRAME, tick);
			} else {
				_timer.stop();
			}
		}

		/**
		 * Adds an object to the scheduling list.
		 * @param item a schedulable object to add
		 */
		public function add(item:ISchedulable) : void
		{
			if (item.id && _ids[item.id] != item) {
				cancel(item.id);
				_ids[item.id] = item;
			}
			_scheduled.push(item);
			play();
		}

		/**
		 * Removes an object from the scheduling list.
		 * @param item the object to remove
		 * @return true if the object was found and removed, false otherwise
		 */
		public function remove(item:ISchedulable) : Boolean
		{
			var idx:int = _scheduled.indexOf(item);
			if (idx >= 0) {
				_scheduled.splice(idx,1);
				if (item.id && _ids[item.id] == item) {
					if (_scheduled.indexOf(item) < 0)
						delete _ids[item.id];
				}
			}
			return (idx >= 0);
		}

		/**
		 * Indicates if an object with the given id is currently in the
		 * scheduler queue.
		 * @param id the id to check for
		 * @return true if an object with that id is currently scheduled,
		 *  false otherwise
		 */
		public function isScheduled(id:String) : Boolean
		{
			return _ids[id] != undefined;
		}

		/**
		 * Looks up the scheduled object indicated by the given id, if any.
		 * @param id the id to lookup
		 * @return the scheduled object with matching id, of null if none
		 */
		public function lookup(id:String) : ISchedulable
		{
			return id==null ? null : _ids[id];
		}

		/**
		 * Cancels any scheduled object with a matching id.
		 * @param id the id to cancel
		 * @return true if an object was found and cancelled, false otherwise
		 */
		public function cancel(id:String) : Boolean
		{
			var s:ISchedulable = _ids[id];
			if (s != null) {
				remove(s);
				s.cancelled();
				return true;
			} else {
				return false;
			}
		}

		/**
		 * Frame/timer callback that invokes each scheduled object.
		 * @param event the event that triggered the callback
		 */
		public function tick(event:Event) : void
        {
        	// all events will see the same timestamp
        	var time:Number = new Date().time;

        	for each (var s:ISchedulable in _scheduled) {
        		if (s.evaluate(time))
        			remove(s);
        	}
        	if (_scheduled.length == 0) {
        		pause();
        	}
        }

	} // end of class Scheduler
}

// scheduler lock class to enforce singleton pattern
class _Lock { }

package flare.animate
{
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	/**
	 * Scheduler that oversees animation and time-based processing. Uses an
	 * internal timer to regularly invoke the current set of scheduled
	 * objects. Typically, interaction with the scheduler is automatically
	 * handled by Transition classes. However, custom implmentations of
	 * the ISchedulable interface will need to be scheduled. Use the
	 * <tt>Scheduler.instance</tt> property, and not the constructor, to get
	 * a reference to the active scheduler.
	 *
	 *
By default, the Scheduler issues updates to all scheduled items each
	 * time the Flash Player advances to the next frame, as reported by the
	 * Event.ENTER_FRAME event. To instead set the update interval
	 * manually, see the timerInterval property.

	 */
	public class Scheduler
	{
		private static const _instance:Scheduler = new Scheduler(_Lock);
		/** The default Scheduler instance. */
		public static function get instance():Scheduler { return _instance; }

		private var _scheduled:Array; // list of all currently scheduled items
		private var _ids:Object;      // map of all named items
		private var _timer:Timer;     // timer for interval-based scheduling

		/**
		 * Sets the timer interval (in milliseconds) at which the scheduler
		 * should process events.
		 */
		public function get timerInterval():Number { return _timer.delay; }
		public function set timerInterval(t:Number):void {
			pause(); _timer.delay = (t>0 ? t : 0); play();
		}

		/**
		 * Creates a new Scheduler--this constructor should be not used;
		 * instead use the instance property.
		 * @param lock a lock object to emulate a private constructor
		 */
		public function Scheduler(lock:Class) {
			if (lock == _Lock) {
				_scheduled = [];
				_ids = {};
				_timer = new Timer(0);
				_timer.addEventListener(TimerEvent.TIMER, tick, false, 0, true);
			} else {
				throw new Error("Invalid constructor. Use Scheduler.instance.");
			}
		}

		/**
		 * Plays the scheduler, allowing it to process events.
		 */
		private function play():void
		{
			if (!_timer.running) {
				_timer.start();
			}
		}

		/**
		 * Pauses the scheduler, so that events are not processed.
		 */
		private function pause():void
		{
			_timer.stop();
		}

		/**
		 * Adds an object to the scheduling list.
		 * @param item a schedulable object to add
		 */
		public function add(item:ISchedulable) : void
		{
			if (item.id && _ids[item.id] != item) {
				cancel(item.id);
				_ids[item.id] = item;
			}
			_scheduled.push(item);
			play();
		}

		/**
		 * Removes an object from the scheduling list.
		 * @param item the object to remove
		 * @return true if the object was found and removed, false otherwise
		 */
		public function remove(item:ISchedulable) : Boolean
		{
			var idx:int = _scheduled.indexOf(item);
			if (idx >= 0) {
				_scheduled.splice(idx,1);
				if (item.id && _ids[item.id] == item) {
					if (_scheduled.indexOf(item) < 0)
						delete _ids[item.id];
				}
			}
			return (idx >= 0);
		}

		/**
		 * Indicates if an object with the given id is currently in the
		 * scheduler queue.
		 * @param id the id to check for
		 * @return true if an object with that id is currently scheduled,
		 *  false otherwise
		 */
		public function isScheduled(id:String) : Boolean
		{
			return _ids[id] != undefined;
		}

		/**
		 * Looks up the scheduled object indicated by the given id, if any.
		 * @param id the id to lookup
		 * @return the scheduled object with matching id, of null if none
		 */
		public function lookup(id:String) : ISchedulable
		{
			return id==null ? null : _ids[id];
		}

		/**
		 * Cancels any scheduled object with a matching id.
		 * @param id the id to cancel
		 * @return true if an object was found and cancelled, false otherwise
		 */
		public function cancel(id:String) : Boolean
		{
			var s:ISchedulable = _ids[id];
			if (s != null) {
				remove(s);
				s.cancelled();
				return true;
			} else {
				return false;
			}
		}

		/**
		 * Frame/timer callback that invokes each scheduled object.
		 * @param event the event that triggered the callback
		 */
		public function tick(event:TimerEvent) : void
		{
			// all events will see the same timestamp
			var time:Number = new Date().time;

			for each (var s:ISchedulable in _scheduled) {
				if (s.evaluate(time))
					remove(s);
			}
			if (_scheduled.length == 0) {
				pause();
			}

			// Defer rendering to the screen update (vertical blanking) period
			event.updateAfterEvent();
		}

	} // end of class Scheduler
}

// scheduler lock class to enforce singleton pattern
class _Lock { }

Fix animation rendering issues by converting the Scheduler class to only use a Timer instance and deferring redraws to the screen update (vertical blanking) period.


////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package org.apache.flex.events
{

    COMPILE::AS3 {
        import flash.events.Event;
    }

	COMPILE::JS {
		import goog.events.Event;
	}

	/**
	 * This class simply wraps flash.events.Event so that
	 * no flash packages are needed on the JS side.
	 * At runtime, this class is not always the event object
	 * that is dispatched.  In most cases we are dispatching
	 * DOMEvents instead, so as long as you don't actually
	 * check the typeof(event) it will work
	 *
	 * @langversion 3.0
	 * @playerversion Flash 10.2
	 * @playerversion AIR 2.6
	 * @productversion FlexJS 0.0
	 */
	COMPILE::AS3
	public class Event extends flash.events.Event
	{

		//--------------------------------------
		//   Static Property
		//--------------------------------------

		static public const CHANGE:String = "change";

		//--------------------------------------
		//   Constructor
		//--------------------------------------

		/**
		 * Constructor.
		 *
		 * @param type The name of the event.
		 * @param bubbles Whether the event bubbles.
		 * @param cancelable Whether the event can be canceled.
		 *
		 * @langversion 3.0
		 * @playerversion Flash 10.2
		 * @playerversion AIR 2.6
		 * @productversion FlexJS 0.0
		 */
		public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
		{
			super(type, bubbles, cancelable);
		}

		//--------------------------------------
		//   Property
		//--------------------------------------

		//--------------------------------------
		//   Function
		//--------------------------------------

		/**
		 * @private
		 */
		public override function clone():flash.events.Event
		{
			return cloneEvent();
		}

		/**
		 * Create a copy/clone of the Event object.
		 *
		 * @langversion 3.0
		 * @playerversion Flash 10.2
		 * @playerversion AIR 2.6
		 * @productversion FlexJS 0.0
		 */
		public function cloneEvent():org.apache.flex.events.Event
		{
			return new org.apache.flex.events.Event(type, bubbles, cancelable);
		}
	}

    COMPILE::JS
    public class Event extends goog.events.Event {

		public static const CHANGE:String = "change";

        public function Event(type:String, target:Object = null) {
            super(type, target);
        }

		public function init(type:String):void {
			this.type = type;
		}
    }
}

fix up imports per platform

fix up imports per platform


/* 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.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : Number;
        /** Level Name. **/
        public var name : String;
        /** level index **/
        public var index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). **/
        public var url : String;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

        /** Create the quality level. **/
        public function Level() : void {
            this.fragments = new Vector.<Fragment>();
        };

        /** Return the Fragment before a given time position. **/
        public function getFragmentBeforePosition(position : Number) : Fragment {
            if (fragments[0].data.valid && position < fragments[0].start_time)
                return fragments[0];

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].start_time <= position && fragments[i].start_time + fragments[i].duration > position) {
                    return fragments[i];
                }
            }
            return fragments[len - 1];
        };

        /** Return the sequence number from a given program date **/
        public function getSeqNumFromProgramDate(program_date : Number) : int {
            if (program_date < fragments[0].program_date)
                return -1;

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].program_date <= program_date && fragments[i].program_date + 1000 * fragments[i].duration > program_date) {
                    return (start_seqnum + i);
                }
            }
            return -1;
        };

        /** Return the sequence number nearest a PTS **/
        public function getSeqNumNearestPTS(pts : Number, continuity : int) : Number {
            if (fragments.length == 0)
                return -1;
            var firstIndex : Number = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1 || isNaN(fragments[firstIndex].data.pts_start_computed))
                return -1;
            var lastIndex : Number = getLastIndexfromContinuity(continuity);

            for (var i : int = firstIndex; i <= lastIndex; i++) {
                var frag : Fragment = fragments[i];
                /* check nearest fragment */
                if ( frag.data.valid && (frag.duration >= 0) && (Math.abs(frag.data.pts_start_computed - pts) < Math.abs(frag.data.pts_start_computed + 1000 * frag.duration - pts))) {
                    return frag.seqnum;
                }
            }
            // requested PTS above max PTS of this level
            return Number.POSITIVE_INFINITY;
        };

        public function getLevelstartPTS() : Number {
            if (fragments.length)
                return fragments[0].data.pts_start_computed;
            else
                return NaN;
        }

        /** Return the fragment index from fragment sequence number **/
        public function getFragmentfromSeqNum(seqnum : Number) : Fragment {
            var index : int = getIndexfromSeqNum(seqnum);
            if (index != -1) {
                return fragments[index];
            } else {
                return null;
            }
        }

        /** Return the fragment index from fragment sequence number **/
        private function getIndexfromSeqNum(seqnum : int) : int {
            if (seqnum >= start_seqnum && seqnum <= end_seqnum) {
                return (fragments.length - 1 - (end_seqnum - seqnum));
            } else {
                return -1;
            }
        }

        /** Return the first index matching with given continuity counter **/
        private function getFirstIndexfromContinuity(continuity : int) : int {
            // look for first fragment matching with given continuity index
            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                if (fragments[i].continuity == continuity)
                    return i;
            }
            return -1;
        }

        /** Return the first seqnum matching with given continuity counter **/
        public function getFirstSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getFirstIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last seqnum matching with given continuity counter **/
        public function getLastSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getLastIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last index matching with given continuity counter **/
        private function getLastIndexfromContinuity(continuity : Number) : int {
            var firstIndex : int = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1)
                return -1;

            var lastIndex : int = firstIndex;
            // look for first fragment matching with given continuity index
            for (var i : int = firstIndex; i < fragments.length; i++) {
                if (fragments[i].continuity == continuity)
                    lastIndex = i;
                else
                    break;
            }
            return lastIndex;
        }

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var continuity_offset : int;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null) {
                    continuity_offset = frag.continuity - _fragments[i].continuity;
                    _fragments[i].continuity
                    if(!isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                    }
                }
            }
            if(continuity_offset) {
                CONFIG::LOGGING {
                    Log.debug("updateFragments: discontinuity sliding from live playlist,take into account discontinuity drift:" + continuity_offset);
                }
                for (i = 0; i < len; i++) {
                     _fragments[i].continuity+= continuity_offset;
                }
            }
            updateFragmentsProgramDate(_fragments);
            fragments = _fragments;

            if (len > 0) {
                start_seqnum = _fragments[0].seqnum;
                end_seqnum = _fragments[len - 1].seqnum;

                if (idx_with_metrics != -1) {
                    frag = fragments[idx_with_metrics];
                    // if at least one fragment contains PTS info, recompute PTS information for all fragments
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: found PTS info from previous playlist,seqnum/PTS:" + frag.seqnum + "/" + frag.data.pts_start);
                    }
                    updateFragment(frag.seqnum, true, frag.data.pts_start, frag.data.pts_start + 1000 * frag.duration);
                } else {
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: unknown PTS info for this level");
                    }
                    duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
                }
                averageduration = duration / len;
            } else {
                duration = 0;
                averageduration = 0;
            }
        }

        private function updateFragmentsProgramDate(_fragments : Vector.<Fragment>) : void {
            var len : int = _fragments.length;
            var continuity : int;
            var program_date : Number;
            var frag : Fragment;
            for (var i : int = 0; i < len; i++) {
                frag = _fragments[i];
                if (frag.continuity != continuity) {
                    continuity = frag.continuity;
                    program_date = 0;
                }
                if (frag.program_date) {
                    program_date = frag.program_date + 1000 * frag.duration;
                } else if (program_date) {
                    frag.program_date = program_date;
                }
            }
        }

        private function _updatePTS(from_index : int, to_index : int) : void {
            // CONFIG::LOGGING {
            // Log.info("updateFragmentPTS from/to:" + from_index + "/" + to_index);
            // }
            var frag_from : Fragment = fragments[from_index];
            var frag_to : Fragment = fragments[to_index];

            if (frag_from.data.valid && frag_to.data.valid) {
                if (!isNaN(frag_to.data.pts_start)) {
                    // we know PTS[to_index]
                    frag_to.data.pts_start_computed = frag_to.data.pts_start;
                    /* normalize computed PTS value based on known PTS value.
                     * this is to avoid computing wrong fragment duration in case of PTS looping */
                    var from_pts : Number = PTS.normalize(frag_to.data.pts_start, frag_from.data.pts_start_computed);
                    /* update fragment duration.
                    it helps to fix drifts between playlist reported duration and fragment real duration */
                    if (to_index > from_index) {
                        frag_from.duration = (frag_to.data.pts_start - from_pts) / 1000;
                        CONFIG::LOGGING {
                            if (frag_from.duration < 0) {
                                Log.error("negative duration computed for " + frag_from + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    } else {
                        frag_to.duration = ( from_pts - frag_to.data.pts_start) / 1000;
                        CONFIG::LOGGING {
                            if (frag_to.duration < 0) {
                                Log.error("negative duration computed for " + frag_to + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    }
                } else {
                    // we dont know PTS[to_index]
                    if (to_index > from_index)
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed + 1000 * frag_from.duration;
                    else
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed - 1000 * frag_to.duration;
                }
            }
        }

        public function updateFragment(seqnum : Number, valid : Boolean, min_pts : Number = 0, max_pts : Number = 0) : void {
            // CONFIG::LOGGING {
            // Log.info("updatePTS : seqnum/min/max:" + seqnum + '/' + min_pts + '/' + max_pts);
            // }
            // get fragment from seqnum
            var fragIdx : int = getIndexfromSeqNum(seqnum);
            if (fragIdx != -1) {
                var frag : Fragment = fragments[fragIdx];
                // update fragment start PTS + duration
                if (valid) {
                    frag.data.pts_start = min_pts;
                    frag.data.pts_start_computed = min_pts;
                    frag.duration = (max_pts - min_pts) / 1000;
                } else {
                    frag.duration = 0;
                }
                frag.data.valid = valid;
                // CONFIG::LOGGING {
                // Log.info("SN["+fragments[fragIdx].seqnum+"]:pts/duration:" + fragments[fragIdx].start_pts_computed + "/" + fragments[fragIdx].duration);
                // }

                // adjust fragment PTS/duration from seqnum-1 to frag 0
                for (var i : int = fragIdx; i > 0 && fragments[i - 1].continuity == frag.continuity; i--) {
                    _updatePTS(i, i - 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i-1].seqnum+"]:pts/duration:" + fragments[i-1].start_pts_computed + "/" + fragments[i-1].duration);
                    // }
                }

                // adjust fragment PTS/duration from seqnum to last frag
                for (i = fragIdx; i < fragments.length - 1 && fragments[i + 1].continuity == frag.continuity; i++) {
                    _updatePTS(i, i + 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i+1].seqnum+"]:pts/duration:" + fragments[i+1].start_pts_computed + "/" + fragments[i+1].duration);
                    // }
                }

                // second, adjust fragment offset
                var start_time_offset : Number = fragments[0].start_time;
                var len : int = fragments.length;
                for (i = 0; i < len; i++) {
                    fragments[i].start_time = start_time_offset;
                    start_time_offset += fragments[i].duration;
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i].seqnum+"]:start_time/continuity/pts/duration:" + fragments[i].start_time + "/" + fragments[i].continuity + "/"+ fragments[i].start_pts_computed + "/" + fragments[i].duration);
                    // }
                }
                duration = start_time_offset;
            } else {
                CONFIG::LOGGING {
                    Log.error("updateFragment:seqnum " + seqnum + " not found!");
                }
            }
        }
    }
}

/* 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.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : Number;
        /** Level Name. **/
        public var name : String;
        /** level index **/
        public var index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). **/
        public var url : String;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

        /** Create the quality level. **/
        public function Level() : void {
            this.fragments = new Vector.<Fragment>();
        };

        /** Return the Fragment before a given time position. **/
        public function getFragmentBeforePosition(position : Number) : Fragment {
            if (fragments[0].data.valid && position < fragments[0].start_time)
                return fragments[0];

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].start_time <= position && fragments[i].start_time + fragments[i].duration > position) {
                    return fragments[i];
                }
            }
            return fragments[len - 1];
        };

        /** Return the sequence number from a given program date **/
        public function getSeqNumFromProgramDate(program_date : Number) : int {
            if (program_date < fragments[0].program_date)
                return -1;

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].program_date <= program_date && fragments[i].program_date + 1000 * fragments[i].duration > program_date) {
                    return (start_seqnum + i);
                }
            }
            return -1;
        };

        /** Return the sequence number nearest a PTS **/
        public function getSeqNumNearestPTS(pts : Number, continuity : int) : Number {
            if (fragments.length == 0)
                return -1;
            var firstIndex : Number = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1 || isNaN(fragments[firstIndex].data.pts_start_computed))
                return -1;
            var lastIndex : Number = getLastIndexfromContinuity(continuity);

            for (var i : int = firstIndex; i <= lastIndex; i++) {
                var frag : Fragment = fragments[i];
                /* check nearest fragment */
                if ( frag.data.valid && (frag.duration >= 0) && (Math.abs(frag.data.pts_start_computed - pts) < Math.abs(frag.data.pts_start_computed + 1000 * frag.duration - pts))) {
                    return frag.seqnum;
                }
            }
            // requested PTS above max PTS of this level
            return Number.POSITIVE_INFINITY;
        };

        public function getLevelstartPTS() : Number {
            if (fragments.length)
                return fragments[0].data.pts_start_computed;
            else
                return NaN;
        }

        /** Return the fragment index from fragment sequence number **/
        public function getFragmentfromSeqNum(seqnum : Number) : Fragment {
            var index : int = getIndexfromSeqNum(seqnum);
            if (index != -1) {
                return fragments[index];
            } else {
                return null;
            }
        }

        /** Return the fragment index from fragment sequence number **/
        private function getIndexfromSeqNum(seqnum : int) : int {
            if (seqnum >= start_seqnum && seqnum <= end_seqnum) {
                return (fragments.length - 1 - (end_seqnum - seqnum));
            } else {
                return -1;
            }
        }

        /** Return the first index matching with given continuity counter **/
        private function getFirstIndexfromContinuity(continuity : int) : int {
            // look for first fragment matching with given continuity index
            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                if (fragments[i].continuity == continuity)
                    return i;
            }
            return -1;
        }

        /** Return the first seqnum matching with given continuity counter **/
        public function getFirstSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getFirstIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last seqnum matching with given continuity counter **/
        public function getLastSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getLastIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last index matching with given continuity counter **/
        private function getLastIndexfromContinuity(continuity : Number) : int {
            var firstIndex : int = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1)
                return -1;

            var lastIndex : int = firstIndex;
            // look for first fragment matching with given continuity index
            for (var i : int = firstIndex; i < fragments.length; i++) {
                if (fragments[i].continuity == continuity)
                    lastIndex = i;
                else
                    break;
            }
            return lastIndex;
        }

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var continuity_offset : int;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null) {
                    continuity_offset = frag.continuity - _fragments[i].continuity;
                    if(!isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                    }
                }
            }
            if(continuity_offset) {
                CONFIG::LOGGING {
                    Log.debug("updateFragments: discontinuity sliding from live playlist,take into account discontinuity drift:" + continuity_offset);
                }
                for (i = 0; i < len; i++) {
                     _fragments[i].continuity+= continuity_offset;
                }
            }
            updateFragmentsProgramDate(_fragments);
            fragments = _fragments;

            if (len > 0) {
                start_seqnum = _fragments[0].seqnum;
                end_seqnum = _fragments[len - 1].seqnum;

                if (idx_with_metrics != -1) {
                    frag = fragments[idx_with_metrics];
                    // if at least one fragment contains PTS info, recompute PTS information for all fragments
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: found PTS info from previous playlist,seqnum/PTS:" + frag.seqnum + "/" + frag.data.pts_start);
                    }
                    updateFragment(frag.seqnum, true, frag.data.pts_start, frag.data.pts_start + 1000 * frag.duration);
                } else {
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: unknown PTS info for this level");
                    }
                    duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
                }
                averageduration = duration / len;
            } else {
                duration = 0;
                averageduration = 0;
            }
        }

        private function updateFragmentsProgramDate(_fragments : Vector.<Fragment>) : void {
            var len : int = _fragments.length;
            var continuity : int;
            var program_date : Number;
            var frag : Fragment;
            for (var i : int = 0; i < len; i++) {
                frag = _fragments[i];
                if (frag.continuity != continuity) {
                    continuity = frag.continuity;
                    program_date = 0;
                }
                if (frag.program_date) {
                    program_date = frag.program_date + 1000 * frag.duration;
                } else if (program_date) {
                    frag.program_date = program_date;
                }
            }
        }

        private function _updatePTS(from_index : int, to_index : int) : void {
            // CONFIG::LOGGING {
            // Log.info("updateFragmentPTS from/to:" + from_index + "/" + to_index);
            // }
            var frag_from : Fragment = fragments[from_index];
            var frag_to : Fragment = fragments[to_index];

            if (frag_from.data.valid && frag_to.data.valid) {
                if (!isNaN(frag_to.data.pts_start)) {
                    // we know PTS[to_index]
                    frag_to.data.pts_start_computed = frag_to.data.pts_start;
                    /* normalize computed PTS value based on known PTS value.
                     * this is to avoid computing wrong fragment duration in case of PTS looping */
                    var from_pts : Number = PTS.normalize(frag_to.data.pts_start, frag_from.data.pts_start_computed);
                    /* update fragment duration.
                    it helps to fix drifts between playlist reported duration and fragment real duration */
                    if (to_index > from_index) {
                        frag_from.duration = (frag_to.data.pts_start - from_pts) / 1000;
                        CONFIG::LOGGING {
                            if (frag_from.duration < 0) {
                                Log.error("negative duration computed for " + frag_from + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    } else {
                        frag_to.duration = ( from_pts - frag_to.data.pts_start) / 1000;
                        CONFIG::LOGGING {
                            if (frag_to.duration < 0) {
                                Log.error("negative duration computed for " + frag_to + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    }
                } else {
                    // we dont know PTS[to_index]
                    if (to_index > from_index)
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed + 1000 * frag_from.duration;
                    else
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed - 1000 * frag_to.duration;
                }
            }
        }

        public function updateFragment(seqnum : Number, valid : Boolean, min_pts : Number = 0, max_pts : Number = 0) : void {
            // CONFIG::LOGGING {
            // Log.info("updatePTS : seqnum/min/max:" + seqnum + '/' + min_pts + '/' + max_pts);
            // }
            // get fragment from seqnum
            var fragIdx : int = getIndexfromSeqNum(seqnum);
            if (fragIdx != -1) {
                var frag : Fragment = fragments[fragIdx];
                // update fragment start PTS + duration
                if (valid) {
                    frag.data.pts_start = min_pts;
                    frag.data.pts_start_computed = min_pts;
                    frag.duration = (max_pts - min_pts) / 1000;
                } else {
                    frag.duration = 0;
                }
                frag.data.valid = valid;
                // CONFIG::LOGGING {
                // Log.info("SN["+fragments[fragIdx].seqnum+"]:pts/duration:" + fragments[fragIdx].start_pts_computed + "/" + fragments[fragIdx].duration);
                // }

                // adjust fragment PTS/duration from seqnum-1 to frag 0
                for (var i : int = fragIdx; i > 0 && fragments[i - 1].continuity == frag.continuity; i--) {
                    _updatePTS(i, i - 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i-1].seqnum+"]:pts/duration:" + fragments[i-1].start_pts_computed + "/" + fragments[i-1].duration);
                    // }
                }

                // adjust fragment PTS/duration from seqnum to last frag
                for (i = fragIdx; i < fragments.length - 1 && fragments[i + 1].continuity == frag.continuity; i++) {
                    _updatePTS(i, i + 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i+1].seqnum+"]:pts/duration:" + fragments[i+1].start_pts_computed + "/" + fragments[i+1].duration);
                    // }
                }

                // second, adjust fragment offset
                var start_time_offset : Number = fragments[0].start_time;
                var len : int = fragments.length;
                for (i = 0; i < len; i++) {
                    fragments[i].start_time = start_time_offset;
                    start_time_offset += fragments[i].duration;
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i].seqnum+"]:start_time/continuity/pts/duration:" + fragments[i].start_time + "/" + fragments[i].continuity + "/"+ fragments[i].start_pts_computed + "/" + fragments[i].duration);
                    // }
                }
                duration = start_time_offset;
            } else {
                CONFIG::LOGGING {
                    Log.error("updateFragment:seqnum " + seqnum + " not found!");
                }
            }
        }
    }
}

remove useless line

remove useless line


package aerys.minko
{
	import aerys.minko.type.log.ILogger;

	public final class Minko
	{
		private static const VERSION	: String	= "2.0b";

		private static var _logger		: ILogger	= null;
		private static var _debugLevel	: uint		= 0; // LogLevel.DISABLED;

		public static function set logger(value : ILogger)	: void
		{
			_logger = value;
		}

		public static function get debugLevel() : uint
		{
			return _debugLevel;
		}
		public static function set debugLevel(value : uint)	: void
		{
			_debugLevel = value;
		}

		public static function log(type : uint, message : Object, target : Object = null) : void
		{
			if (_debugLevel & type)
			{
				if (_logger != null)
					_logger.log(message);
				else
					trace(message);
			}
		}
	}
}

package aerys.minko
{
	import aerys.minko.type.log.ILogger;

	public final class Minko
	{
		private static const VERSION	: String	= '2.0';

		private static var _logger		: ILogger	= null;
		private static var _debugLevel	: uint		= 0; // LogLevel.DISABLED;

		public static function set logger(value : ILogger)	: void
		{
			_logger = value;
		}

		public static function get debugLevel() : uint
		{
			return _debugLevel;
		}
		public static function set debugLevel(value : uint)	: void
		{
			_debugLevel = value;
		}

		public static function log(type : uint, message : Object, target : Object = null) : void
		{
			if (_debugLevel & type)
			{
				if (_logger != null)
					_logger.log(message);
				else
					trace(message);
			}
		}
	}
}

set Minko.VERSION to 2.0

set Minko.VERSION to 2.0


package goplayer
{
  import flash.utils.getTimer

  import streamio.Stat

  public class Player
    implements FlashNetConnectionListener, FlashNetStreamListener
  {
    private const DEFAULT_VOLUME : Number = .8

    private const START_BUFFER : Duration = Duration.seconds(1)
    private const SMALL_BUFFER : Duration = Duration.seconds(5)
    private const LARGE_BUFFER : Duration = Duration.seconds(60)
    private const SEEK_GRACE_TIME : Duration = Duration.seconds(2)

    private const finishingListeners : Array = []

    private var connection : FlashNetConnection
    private var _movie : Movie
    private var bitratePolicy : BitratePolicy
    private var enableRTMP : Boolean

    private var _started : Boolean = false
    private var _finished : Boolean = false
    private var triedStandardRTMP : Boolean = false
    private var _usingRTMP : Boolean = false

    private var measuredBandwidth : Bitrate = null
    private var measuredLatency : Duration = null

    private var stream : FlashNetStream = null
    private var metadata : Object = null

    private var _volume : Number = DEFAULT_VOLUME
    private var savedVolume : Number = 0
    private var _paused : Boolean = false
    private var _buffering : Boolean = false
    private var seekStopwatch : Stopwatch = new Stopwatch

    public function Player
      (connection : FlashNetConnection,
       movie : Movie,
       bitratePolicy : BitratePolicy,
       enableRTMP : Boolean)
    {
      this.connection = connection
      _movie = movie
      this.bitratePolicy = bitratePolicy
      this.enableRTMP = enableRTMP

      Stat.view(movie.id)

      connection.listener = this
    }

    public function get movie() : Movie
    { return _movie }

    public function addFinishingListener
      (value : PlayerFinishingListener) : void
    { finishingListeners.push(value) }

    public function get started() : Boolean
    { return _started }

    public function get running() : Boolean
    { return started && !finished }

    public function get finished() : Boolean
    { return _finished }

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

    public function start() : void
    {
      _started = true

      if (enableRTMP && movie.rtmpURL != null)
        connectUsingRTMP()
      else
        connectUsingHTTP()

      Stat.play(movie.id)
    }

    public function handleConnectionFailed() : void
    {
      if (movie.rtmpURL.hasPort && !triedStandardRTMP)
        handleCustomRTMPUnavailable()
      else
        handleRTMPUnavailable()
    }

    private function handleCustomRTMPUnavailable() : void
    {
      debug("Trying to connect using default RTMP ports.")
      connectUsingStandardRTMP()
    }

    private function handleRTMPUnavailable() : void
    {
      debug("Could not connect using RTMP; trying plain HTTP.")
      connectUsingHTTP()
    }

    public function get usingRTMP() : Boolean
    { return _usingRTMP }

    private function connectUsingRTMP() : void
    { connection.connect(movie.rtmpURL) }

    private function connectUsingStandardRTMP() : void
    {
      triedStandardRTMP = true
      connection.connect(movie.rtmpURL.withoutPort)
    }

    private function connectUsingHTTP() : void
    {
      _usingRTMP = false
      connection.dontConnect()
      startPlaying()
    }

    public function handleConnectionClosed() : void
    {}

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

    public function handleConnectionEstablished() : void
    {
      _usingRTMP = true

      if (bandwidthDeterminationNeeded)
        connection.determineBandwidth()
      else
        startPlaying()
    }

    private function get bandwidthDeterminationNeeded() : Boolean
    { return bitratePolicy == BitratePolicy.BEST }

    public function handleBandwidthDetermined
      (bandwidth : Bitrate, latency : Duration) : void
    {
      measuredBandwidth = bandwidth
      measuredLatency = latency

      startPlaying()
    }

    private function get bandwidthDetermined() : Boolean
    { return measuredBandwidth != null }

    private function startPlaying() : void
    {
      stream = connection.getNetStream()
      stream.listener = this
      stream.volume = volume

      useStartBuffer()

      if (usingRTMP)
        playRTMPStream()
      else
        playHTTPStream()

      if (paused)
        stream.paused = true
    }

    private function playRTMPStream() : void
    { stream.playRTMP(streamPicker.first, streamPicker.all) }

    private function playHTTPStream() : void
    { stream.playHTTP(movie.httpURL) }

    private function get streamPicker() : RTMPStreamPicker
    {
      return new RTMPStreamPicker
        (movie.rtmpStreams, bitratePolicy, measuredBandwidth)
    }

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

    public function handleNetStreamMetadata(data : Object) : void
    {
      metadata = data

      if (!usingRTMP)
        debug("Video file size: " + stream.httpFileSize)
    }

    public function handleStreamingStarted() : void
    {
      useStartBuffer()
      _buffering = true
      _finished = false
    }

    public function handleBufferFilled() : void
    { handleBufferingFinished() }

    private function handleBufferingFinished() : void
    {
      _buffering = false

      // Give the backend a chance to resume playback before we go
      // ahead and raise the buffer size further.
      later($handleBufferingFinished)
    }

    private function $handleBufferingFinished() : void
    {
      // Make sure playback was not interrupted.
      if (!buffering && !finished)
        useLargeBuffer()
    }

    public function handleBufferEmptied() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
      else
        handleBufferingStarted()
    }

    private function handleBufferingStarted() : void
    {
      useSmallBuffer()
      _buffering = true
      _finished = false
    }

    private function useStartBuffer() : void
    { stream.bufferTime = START_BUFFER }

    private function useSmallBuffer() : void
    { stream.bufferTime = SMALL_BUFFER }

    private function useLargeBuffer() : void
    { stream.bufferTime = LARGE_BUFFER }

    public function get buffering() : Boolean
    { return _buffering }

    public function get bufferingUnexpectedly() : Boolean
    { return buffering && !justSeeked }

    private function get justSeeked() : Boolean
    { return seekStopwatch.within(SEEK_GRACE_TIME) }

    public function handleStreamingStopped() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
    }

    private function handleFinishedPlaying() : void
    {
      debug("Finished playing.")
      _finished = true

      for each (var listener : PlayerFinishingListener in finishingListeners)
        listener.handleMovieFinishedPlaying()
    }

    private function get finishedPlaying() : Boolean
    { return timeRemaining.seconds < 1 }

    private function get timeRemaining() : Duration
    { return streamLength.minus(playheadPosition) }

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

    public function get paused() : Boolean
    { return _paused }

    public function set paused(value : Boolean) : void
    {
      // Allow pausing before the stream has been created.
      _paused = value

      if (stream)
        stream.paused = value
    }

    public function togglePaused() : void
    {
      // Forbid pausing before the stream has been created.
      if (stream)
        paused = !paused
    }

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

    public function get playheadPosition() : Duration
    {
      return stream != null && stream.playheadPosition != null
        ? stream.playheadPosition : Duration.ZERO
    }

    public function get playheadRatio() : Number
    { return getRatio(playheadPosition.seconds, streamLength.seconds) }

    public function get bufferRatio() : Number
    { return getRatio(bufferPosition.seconds, streamLength.seconds) }

    private function getRatio
      (numerator : Number, denominator : Number) : Number
    { return Math.min(1, numerator / denominator) }

    private function get bufferPosition() : Duration
    { return playheadPosition.plus(bufferLength) }

    public function set playheadPosition(value : Duration) : void
    {
      if (stream)
        $playheadPosition = value
    }

    private function set $playheadPosition(value : Duration) : void
    {
      _finished = false
      seekStopwatch.start()
      useStartBuffer()
      stream.playheadPosition = value
    }

    public function set playheadRatio(value : Number) : void
    { playheadPosition = streamLength.scaledBy(value) }

    public function seekBy(delta : Duration) : void
    { playheadPosition = playheadPosition.plus(delta) }

    public function rewind() : void
    { playheadPosition = Duration.ZERO }

    public function get playing() : Boolean
    { return stream != null && !paused && !finished }

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

    public function get volume() : Number
    { return _volume }

    public function set volume(value : Number) : void
    {
      _volume = Math.round(clamp(value, 0, 1) * 100) / 100

      if (stream)
        stream.volume = value
    }

    public function changeVolumeBy(delta : Number) : void
    { volume = volume + delta }

    public function mute() : void
    {
      savedVolume = volume
      volume = 0
    }

    public function unmute() : void
    { volume = savedVolume == 0 ? DEFAULT_VOLUME : savedVolume }

    public function get muted() : Boolean
    { return volume == 0 }

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

    public function get aspectRatio() : Number
    { return movie.aspectRatio }

    public function get bufferTime() : Duration
    {
      return stream != null && stream.bufferTime != null
        ? stream.bufferTime : START_BUFFER
    }

    public function get bufferLength() : Duration
    {
      return stream != null && stream.bufferLength != null
        ? stream.bufferLength : Duration.ZERO
    }

    public function get bufferFillRatio() : Number
    { return getRatio(bufferLength.seconds, bufferTime.seconds) }

    public function get streamLength() : Duration
    {
      return metadata != null
        ? Duration.seconds(metadata.duration)
        : movie.duration
    }

    public function get currentBitrate() : Bitrate
    {
      return stream != null && stream.bitrate != null
        ? stream.bitrate : Bitrate.ZERO
    }

    public function get currentBandwidth() : Bitrate
    {
      return stream != null && stream.bandwidth != null
        ? stream.bandwidth : Bitrate.ZERO
    }

    public function get highQualityDimensions() : Dimensions
    {
      var result : Dimensions = Dimensions.ZERO

      for each (var stream : RTMPStream in movie.rtmpStreams)
        if (stream.dimensions.isGreaterThan(result))
          result = stream.dimensions

      return result
    }
  }
}

package goplayer
{
  import flash.utils.getTimer

  import streamio.Stat

  public class Player
    implements FlashNetConnectionListener, FlashNetStreamListener
  {
    private const DEFAULT_VOLUME : Number = .8

    private const START_BUFFER : Duration = Duration.seconds(3)
    private const SMALL_BUFFER : Duration = Duration.seconds(5)
    private const LARGE_BUFFER : Duration = Duration.seconds(60)
    private const SEEK_GRACE_TIME : Duration = Duration.seconds(2)

    private const finishingListeners : Array = []

    private var connection : FlashNetConnection
    private var _movie : Movie
    private var bitratePolicy : BitratePolicy
    private var enableRTMP : Boolean

    private var _started : Boolean = false
    private var _finished : Boolean = false
    private var triedStandardRTMP : Boolean = false
    private var _usingRTMP : Boolean = false

    private var measuredBandwidth : Bitrate = null
    private var measuredLatency : Duration = null

    private var stream : FlashNetStream = null
    private var metadata : Object = null

    private var _volume : Number = DEFAULT_VOLUME
    private var savedVolume : Number = 0
    private var _paused : Boolean = false
    private var _buffering : Boolean = false
    private var seekStopwatch : Stopwatch = new Stopwatch

    public function Player
      (connection : FlashNetConnection,
       movie : Movie,
       bitratePolicy : BitratePolicy,
       enableRTMP : Boolean)
    {
      this.connection = connection
      _movie = movie
      this.bitratePolicy = bitratePolicy
      this.enableRTMP = enableRTMP

      Stat.view(movie.id)

      connection.listener = this
    }

    public function get movie() : Movie
    { return _movie }

    public function addFinishingListener
      (value : PlayerFinishingListener) : void
    { finishingListeners.push(value) }

    public function get started() : Boolean
    { return _started }

    public function get running() : Boolean
    { return started && !finished }

    public function get finished() : Boolean
    { return _finished }

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

    public function start() : void
    {
      _started = true

      if (enableRTMP && movie.rtmpURL != null)
        connectUsingRTMP()
      else
        connectUsingHTTP()

      Stat.play(movie.id)
    }

    public function handleConnectionFailed() : void
    {
      if (movie.rtmpURL.hasPort && !triedStandardRTMP)
        handleCustomRTMPUnavailable()
      else
        handleRTMPUnavailable()
    }

    private function handleCustomRTMPUnavailable() : void
    {
      debug("Trying to connect using default RTMP ports.")
      connectUsingStandardRTMP()
    }

    private function handleRTMPUnavailable() : void
    {
      debug("Could not connect using RTMP; trying plain HTTP.")
      connectUsingHTTP()
    }

    public function get usingRTMP() : Boolean
    { return _usingRTMP }

    private function connectUsingRTMP() : void
    { connection.connect(movie.rtmpURL) }

    private function connectUsingStandardRTMP() : void
    {
      triedStandardRTMP = true
      connection.connect(movie.rtmpURL.withoutPort)
    }

    private function connectUsingHTTP() : void
    {
      _usingRTMP = false
      connection.dontConnect()
      startPlaying()
    }

    public function handleConnectionClosed() : void
    {}

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

    public function handleConnectionEstablished() : void
    {
      _usingRTMP = true

      if (bandwidthDeterminationNeeded)
        connection.determineBandwidth()
      else
        startPlaying()
    }

    private function get bandwidthDeterminationNeeded() : Boolean
    { return bitratePolicy == BitratePolicy.BEST }

    public function handleBandwidthDetermined
      (bandwidth : Bitrate, latency : Duration) : void
    {
      measuredBandwidth = bandwidth
      measuredLatency = latency

      startPlaying()
    }

    private function get bandwidthDetermined() : Boolean
    { return measuredBandwidth != null }

    private function startPlaying() : void
    {
      stream = connection.getNetStream()
      stream.listener = this
      stream.volume = volume

      useStartBuffer()

      if (usingRTMP)
        playRTMPStream()
      else
        playHTTPStream()

      if (paused)
        stream.paused = true
    }

    private function playRTMPStream() : void
    { stream.playRTMP(streamPicker.first, streamPicker.all) }

    private function playHTTPStream() : void
    { stream.playHTTP(movie.httpURL) }

    private function get streamPicker() : RTMPStreamPicker
    {
      return new RTMPStreamPicker
        (movie.rtmpStreams, bitratePolicy, measuredBandwidth)
    }

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

    public function handleNetStreamMetadata(data : Object) : void
    {
      metadata = data

      if (!usingRTMP)
        debug("Video file size: " + stream.httpFileSize)
    }

    public function handleStreamingStarted() : void
    {
      useStartBuffer()
      _buffering = true
      _finished = false
    }

    public function handleBufferFilled() : void
    { handleBufferingFinished() }

    private function handleBufferingFinished() : void
    {
      _buffering = false

      // Give the backend a chance to resume playback before we go
      // ahead and raise the buffer size further.
      later($handleBufferingFinished)
    }

    private function $handleBufferingFinished() : void
    {
      // Make sure playback was not interrupted.
      if (!buffering && !finished)
        useLargeBuffer()
    }

    public function handleBufferEmptied() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
      else
        handleBufferingStarted()
    }

    private function handleBufferingStarted() : void
    {
      useSmallBuffer()
      _buffering = true
      _finished = false
    }

    private function useStartBuffer() : void
    { stream.bufferTime = START_BUFFER }

    private function useSmallBuffer() : void
    { stream.bufferTime = SMALL_BUFFER }

    private function useLargeBuffer() : void
    { stream.bufferTime = LARGE_BUFFER }

    public function get buffering() : Boolean
    { return _buffering }

    public function get bufferingUnexpectedly() : Boolean
    { return buffering && !justSeeked }

    private function get justSeeked() : Boolean
    { return seekStopwatch.within(SEEK_GRACE_TIME) }

    public function handleStreamingStopped() : void
    {
      if (finishedPlaying)
        handleFinishedPlaying()
    }

    private function handleFinishedPlaying() : void
    {
      debug("Finished playing.")
      _finished = true

      for each (var listener : PlayerFinishingListener in finishingListeners)
        listener.handleMovieFinishedPlaying()
    }

    private function get finishedPlaying() : Boolean
    { return timeRemaining.seconds < 1 }

    private function get timeRemaining() : Duration
    { return streamLength.minus(playheadPosition) }

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

    public function get paused() : Boolean
    { return _paused }

    public function set paused(value : Boolean) : void
    {
      // Allow pausing before the stream has been created.
      _paused = value

      if (stream)
        stream.paused = value
    }

    public function togglePaused() : void
    {
      // Forbid pausing before the stream has been created.
      if (stream)
        paused = !paused
    }

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

    public function get playheadPosition() : Duration
    {
      return stream != null && stream.playheadPosition != null
        ? stream.playheadPosition : Duration.ZERO
    }

    public function get playheadRatio() : Number
    { return getRatio(playheadPosition.seconds, streamLength.seconds) }

    public function get bufferRatio() : Number
    { return getRatio(bufferPosition.seconds, streamLength.seconds) }

    private function getRatio
      (numerator : Number, denominator : Number) : Number
    { return Math.min(1, numerator / denominator) }

    private function get bufferPosition() : Duration
    { return playheadPosition.plus(bufferLength) }

    public function set playheadPosition(value : Duration) : void
    {
      if (stream)
        $playheadPosition = value
    }

    private function set $playheadPosition(value : Duration) : void
    {
      _finished = false
      seekStopwatch.start()
      useStartBuffer()
      stream.playheadPosition = value
    }

    public function set playheadRatio(value : Number) : void
    { playheadPosition = streamLength.scaledBy(value) }

    public function seekBy(delta : Duration) : void
    { playheadPosition = playheadPosition.plus(delta) }

    public function rewind() : void
    { playheadPosition = Duration.ZERO }

    public function get playing() : Boolean
    { return stream != null && !paused && !finished }

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

    public function get volume() : Number
    { return _volume }

    public function set volume(value : Number) : void
    {
      _volume = Math.round(clamp(value, 0, 1) * 100) / 100

      if (stream)
        stream.volume = value
    }

    public function changeVolumeBy(delta : Number) : void
    { volume = volume + delta }

    public function mute() : void
    {
      savedVolume = volume
      volume = 0
    }

    public function unmute() : void
    { volume = savedVolume == 0 ? DEFAULT_VOLUME : savedVolume }

    public function get muted() : Boolean
    { return volume == 0 }

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

    public function get aspectRatio() : Number
    { return movie.aspectRatio }

    public function get bufferTime() : Duration
    {
      return stream != null && stream.bufferTime != null
        ? stream.bufferTime : START_BUFFER
    }

    public function get bufferLength() : Duration
    {
      return stream != null && stream.bufferLength != null
        ? stream.bufferLength : Duration.ZERO
    }

    public function get bufferFillRatio() : Number
    { return getRatio(bufferLength.seconds, bufferTime.seconds) }

    public function get streamLength() : Duration
    {
      return metadata != null
        ? Duration.seconds(metadata.duration)
        : movie.duration
    }

    public function get currentBitrate() : Bitrate
    {
      return stream != null && stream.bitrate != null
        ? stream.bitrate : Bitrate.ZERO
    }

    public function get currentBandwidth() : Bitrate
    {
      return stream != null && stream.bandwidth != null
        ? stream.bandwidth : Bitrate.ZERO
    }

    public function get highQualityDimensions() : Dimensions
    {
      var result : Dimensions = Dimensions.ZERO

      for each (var stream : RTMPStream in movie.rtmpStreams)
        if (stream.dimensions.isGreaterThan(result))
          result = stream.dimensions

      return result
    }
  }
}

Use a start buffer of 3s instead of 1s.

Use a start buffer of 3s instead of 1s.