[FragmentLoader] Add com.pivotshare.hls.loader.FragmentLoader

error: pathspec 'src/com/pivotshare/hls/loader/FragmentLoader.as' did not match any file(s) known to git

--- src/com/pivotshare/hls/loader/FragmentLoader.as
+++ src/com/pivotshare/hls/loader/FragmentLoader.as
@@ -0,0 +1,1354 @@
+/* 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 com.pivotshare.hls.loader {
+
+    import flash.events.*;
+    import flash.net.*;
+    import flash.utils.ByteArray;
+    import flash.utils.getTimer;
+    import flash.utils.Timer;
+
+    import com.pivotshare.hls.loader.FragmentStream;
+    import com.pivotshare.hls.loader.KeyLoader;
+
+    import org.mangui.hls.constant.HLSLoaderTypes;
+    import org.mangui.hls.constant.HLSTypes;
+    import org.mangui.hls.controller.AudioTrackController;
+    import org.mangui.hls.controller.LevelController;
+    import org.mangui.hls.demux.Demuxer;
+    import org.mangui.hls.demux.DemuxHelper;
+    import org.mangui.hls.demux.ID3Tag;
+    import org.mangui.hls.event.HLSError;
+    import org.mangui.hls.event.HLSEvent;
+    import org.mangui.hls.event.HLSLoadMetrics;
+    import org.mangui.hls.flv.FLVTag;
+    import org.mangui.hls.HLS;
+    import org.mangui.hls.HLSSettings;
+    import org.mangui.hls.model.AudioTrack;
+    import org.mangui.hls.model.Fragment;
+    import org.mangui.hls.model.FragmentData;
+    import org.mangui.hls.model.Level;
+    import org.mangui.hls.stream.StreamBuffer;
+
+    CONFIG::LOGGING {
+        import org.mangui.hls.utils.Log;
+        import org.mangui.hls.utils.Hex;
+    }
+
+    /**
+     * FlasHLS-compatible Fragment loader.
+     *
+     * Utilizes FragmentStream and KeyLoader from com.pivotshare.hls.
+     *
+     * @class   FragmentLoader
+     * @author  HGPA
+     */
+    public class FragmentLoader {
+
+        //
+        //
+        // FragmentLoader loop states
+        // See _checkBuffer method
+        //
+        //
+
+        public static const LOADING_STOPPED              : int = -1;
+        public static const LOADING_IDLE                 : int = 0;
+        public static const LOADING_IN_PROGRESS          : int = 1;
+        public static const LOADING_WAITING_LEVEL_UPDATE : int = 2;
+        public static const LOADING_STALLED              : int = 3;
+        public static const LOADING_FRAGMENT_IO_ERROR    : int = 4;
+        public static const LOADING_KEY_IO_ERROR         : int = 5;
+        public static const LOADING_COMPLETED            : int = 6;
+
+        //
+        //
+        // References to external instantiations
+        //
+        //
+
+        private var _hls : HLS;
+        private var _levelController : LevelController;
+        private var _audioTrackController : AudioTrackController;
+
+        /** Timer used control FragmentLoader event loop. */
+        private var _timer : Timer;
+
+        /** Encryption Key Loader */
+        private var _keyLoader : KeyLoader;
+
+        /**
+         * Reference to the manifest levels.
+         * This is set via _manifestLoadedHandler.
+         */
+        private var _levels : Vector.<Level>;
+
+        /* demux instance */
+        private var _demux : Demuxer;
+
+        /** stream buffer instance */
+        private var _streamBuffer : StreamBuffer;
+
+        /** Util for loading the fragment. */
+        private var _fragstreamloader : URLStream;
+
+        private var _fragmentStream : FragmentStream;
+
+        /** reference to previous/current fragment */
+        private var _fragPrevious : Fragment;
+        private var _fragCurrent : Fragment;
+
+        /** loading metrics */
+        private var _metrics : HLSLoadMetrics;
+
+        //
+        //
+        // Secondary state variables
+        //
+        //
+
+        /* loading state variable */
+        private var _loadingState : int;
+
+        /** has manifest just being reloaded **/
+        private var _manifestJustLoaded : Boolean;
+
+        /** last loaded level. **/
+        private var _levelLastLoaded : int;
+
+        /** next level (-1 if not defined yet) **/
+        private var _levelNext : int = -1;
+
+        /** Did the stream switch quality levels. **/
+        private var _switchLevel : Boolean;
+
+        /** Did a discontinuity occurs in the stream **/
+        private var _hasDiscontinuity : Boolean;
+
+        /** boolean to track whether PTS analysis is ongoing or not */
+        private var _ptsAnalyzing : Boolean = false;
+
+        /** requested seek position **/
+        private var _seekPosition : Number;
+
+        /** first fragment loaded ? **/
+        private var _fragmentFirstLoaded : Boolean;
+
+        /* key error/reload */
+        private var _keyLoadErrorDate : Number;
+        private var _keyRetryTimeout : Number;
+        private var _keyRetryCount : int;
+        private var _keyLoadStatus : int;
+
+        /* fragment error/reload */
+        private var _fragLoadErrorDate : Number;
+        private var _fragRetryTimeout : Number;
+        private var _fragRetryCount : int;
+        private var _fragLoadStatus : int;
+        private var _fragSkipping : Boolean;
+
+        //
+        //
+        //
+        // PUBLIC METHODS
+        //
+        //
+        //
+
+        /**
+         * Create the FragmentLoader.
+         *
+         * @constructor
+         * @param  {HLS}                   hls
+         * @param  {AudioTrackController}  audioTrackController
+         * @param  {LevelController}       levelController
+         * @param  {StreamBuffer}          streamBuffer
+         */
+        public function FragmentLoader(
+            hls : HLS,
+            audioTrackController : AudioTrackController,
+            levelController : LevelController,
+            streamBuffer : StreamBuffer) : void {
+
+            _hls = hls;
+            _levelController = levelController;
+            _audioTrackController = audioTrackController;
+            _streamBuffer = streamBuffer;
+            _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
+            _hls.addEventListener(HLSEvent.LEVEL_LOADED, _levelLoadedHandler);
+            _timer = new Timer(20, 0);
+            _timer.addEventListener(TimerEvent.TIMER, _checkLoading);
+            _loadingState = LOADING_STOPPED;
+            _manifestJustLoaded = false;
+            _keyLoader = new KeyLoader();
+        };
+
+        /**
+         * Dispose this FragmentLoader.
+         *
+         * @method  dispose
+         */
+        public function dispose() : void {
+            stop();
+            _hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
+            _hls.removeEventListener(HLSEvent.LEVEL_LOADED, _levelLoadedHandler);
+            _loadingState = LOADING_STOPPED;
+            _keyLoader = new KeyLoader();
+        }
+
+        /**
+         * Load necessary Fragments for requested seeking.
+         *
+         * @method  seek
+         * @param   {Number}  position
+         */
+        public function seek(position : Number) : void {
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#seek(" + position.toFixed(2) + ")");
+            }
+            // reset IO Error when seeking
+            _fragRetryCount = 0;
+            _keyRetryCount = 0;
+            _fragRetryTimeout = 1000;
+            _keyRetryTimeout = 1000;
+            _loadingState = LOADING_IDLE;
+            _seekPosition = position;
+            _fragmentFirstLoaded = false;
+            _fragPrevious = null;
+            _fragSkipping = false;
+            _timer.start();
+        }
+
+        /**
+         * Stop this FragmentLoader.
+         *
+         * @method  stop
+         */
+        public function stop() : void {
+            _stop_load();
+            _timer.stop();
+            _loadingState = LOADING_STOPPED;
+        }
+
+        public function get audioExpected() : Boolean {
+            if (_demux) {
+                return _demux.audioExpected;
+            } else {
+                // always return true in case demux is not yet initialized
+                return true;
+            }
+        }
+
+        public function get videoExpected() : Boolean {
+            if (_demux) {
+                return _demux.videoExpected;
+            } else {
+                // always return true in case demux is not yet initialized
+                return true;
+            }
+        }
+
+        //
+        //
+        //
+        // PRIVATE METHODS
+        //
+        //
+        //
+
+        /**
+         * Called when Adaptive Manifest has been loaded.
+         *
+         * @private
+         * @method   _manifestLoadedHandler
+         * @param    {HLSEvent}  event  -  HLSEvent.MANIFEST_LOADED
+         */
+        private function _manifestLoadedHandler(event : HLSEvent) : void {
+            _levels = event.levels;
+            _manifestJustLoaded = true;
+        };
+
+        /**
+         * Called when Level Manifest has been loaded.
+         *
+         * @private
+         * @method   _levelLoadedHandler
+         * @param    {HLSEvent}  event  -  HLSEvent.LEVEL_LOADED
+         */
+        private function _levelLoadedHandler(event : HLSEvent) : void {
+            _levelLastLoaded = event.loadMetrics.level;
+            if (_loadingState == LOADING_WAITING_LEVEL_UPDATE && _levelLastLoaded == _hls.loadLevel) {
+                _loadingState = LOADING_IDLE;
+            }
+            // speed up loading of new fragment
+            _timer.start();
+        };
+
+        /**
+         * Stop the pipeline of the current Fragment.
+         *
+         * @private
+         * @method   _stop_load
+         */
+        private function _stop_load() : void {
+
+            if (_fragmentStream) {
+                _fragmentStream.close();
+            }
+
+            // FIXME: Potential bug now that we do not expliclty close key stream load
+            // KeyLoader could be changed to allow close of stream, but we'll need some ID/state
+            /*
+            if (_keystreamloader && _keystreamloader.connected) {
+                _keystreamloader.close();
+            }
+            */
+
+            if (_demux) {
+                _demux.cancel();
+                _demux = null;
+            }
+        }
+
+        /**
+         * FragmentLoader event loop handler.
+         *
+         * States:
+         *
+         * - LOADING_STOPPED
+         * - LOADING_WAITING_LEVEL_UPDATE
+         * - LOADING_IN_PROGRESS
+         * - LOADING_IDLE
+         * - LOADING_STALLED
+         * - LOADING_KEY_IO_ERROR
+         * - LOADING_COMPLETED
+         *
+         * @private
+         * @method   _checkLoading
+         * @param    {Event}  e  -  TimerEvent
+         */
+        private function _checkLoading(e : Event) : void {
+            switch(_loadingState) {
+
+                /*
+                 * Loading has explicitly been stopped. Stop this loop.
+                 */
+                case LOADING_STOPPED:
+                    stop();
+                    break;
+
+                /*
+                 * Waiting for Level manifest. Do nothing.
+                 */
+                case LOADING_WAITING_LEVEL_UPDATE:
+                    break;
+
+                /*
+                 * Loading already in progress.
+                 *
+                 * If we are using adaptive bitrate then monitor Fragment
+                 * loading to determine if we should stay at current bitrate.
+                 */
+                case LOADING_IN_PROGRESS:
+                     if(_hls.autoLevel && _fragCurrent.level) {
+
+                        // monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
+                        var requestDelay : int = getTimer() - _metrics.loading_request_time;
+                        var fragDuration : Number = _fragCurrent.duration;
+
+                        if(requestDelay > 500 * fragDuration) {
+                            var loaded : int = _fragCurrent.data.bytesLoaded;
+                            var expected : int = fragDuration * _levels[_fragCurrent.level].bitrate / 8;
+                            if(expected < loaded) {
+                                expected = loaded;
+                            }
+                            var loadRate : int = loaded*1000/requestDelay; // byte/s
+                            var fragLoadedDelay : Number =(expected-loaded)/loadRate;
+                            var fragLevel0LoadedDelay : Number = fragDuration*_levels[0].bitrate/(8*loadRate); //bps/Bps
+                            var bufferLen : Number = _hls.stream.bufferLength;
+
+                            // CONFIG::LOGGING {
+                            //     Log.info("bufferLen/fragDuration/fragLoadedDelay/fragLevel0LoadedDelay:" + bufferLen.toFixed(1) + "/" + fragDuration.toFixed(1) + "/" + fragLoadedDelay.toFixed(1) + "/" + fragLevel0LoadedDelay.toFixed(1));
+                            // }
+                            /* if we have less than 2 frag duration in buffer and if frag loaded delay is greater than buffer len
+                              ... and also bigger than duration needed to load fragment at next level ...*/
+                            if(bufferLen < 2*fragDuration && fragLoadedDelay > bufferLen && fragLoadedDelay > fragLevel0LoadedDelay) {
+                                // abort fragment loading ...
+                                CONFIG::LOGGING {
+                                    Log.warn("FragmentLoader#_checkLoading: loading too slow, abort fragment loading");
+                                    Log.warn("fragLoadedDelay/bufferLen/fragLevel0LoadedDelay:" + fragLoadedDelay.toFixed(1) + "/" + bufferLen.toFixed(1) + "/" + fragLevel0LoadedDelay.toFixed(1));
+                                }
+                                //abort fragment loading
+                                _stop_load();
+                                // fill loadMetrics so please LevelController that will adjust bw for next fragment
+                                // fill theoritical value, assuming bw will remain as it is
+                                _metrics.size = expected;
+                                _metrics.duration = 1000*fragDuration;
+                                _metrics.loading_end_time = _metrics.parsing_end_time = _metrics.loading_request_time + 1000*expected/loadRate;
+                                _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _metrics));
+
+                              // switch back to IDLE state to request new fragment at lowest level
+                              _loadingState = LOADING_IDLE;
+                            }
+                        }
+                    }
+                    break;
+
+                /*
+                 * No loading in progress. Start loading.
+                 */
+                case LOADING_IDLE:
+                    var level : int;
+                    // check if first fragment after seek has been already loaded
+                    if (_fragmentFirstLoaded == false) {
+
+                        CONFIG::LOGGING {
+                            Log.debug("FragmentLoader#_checkLoading not _fragmentFirstLoaded");
+                        }
+
+                        // select level for first fragment load
+                        if(_levelNext != -1) {
+                            level = _levelNext;
+                        } else if (_hls.autoLevel) {
+                            if (_manifestJustLoaded) {
+                                level = _hls.startLevel;
+                            } else {
+                                level = _hls.seekLevel;
+                            }
+                        } else {
+                            level = _hls.manualLevel;
+                        }
+
+                        if (level != _hls.loadLevel) {
+                            _demux = null;
+                            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, level));
+                        }
+
+                        _switchLevel = true;
+
+                        // check if we received playlist for choosen level. if live playlist, ensure that new playlist has been refreshed
+                        if ((_levels[level].fragments.length == 0) || (_hls.type == HLSTypes.LIVE && _levelLastLoaded != level)) {
+                            // playlist not yet received
+                            CONFIG::LOGGING {
+                                Log.debug("FragmentLoader#_checkLoading: playlist not received for levels[" + level + "]");
+                            }
+                            _loadingState = LOADING_WAITING_LEVEL_UPDATE;
+                            _levelNext = level;
+                        } else {
+                            // just after seek, load first fragment
+                            CONFIG::LOGGING {
+                                Log.debug("FragmentLoader#_checkLoading Will _loadfirstfragment");
+                            }
+                            _loadingState = _loadfirstfragment(_seekPosition, level);
+                        }
+
+                        /* first fragment already loaded
+                         * check if we need to load next fragment, do it only if buffer is NOT full
+                         */
+                    } else if (HLSSettings.maxBufferLength == 0 || _hls.stream.bufferLength < HLSSettings.maxBufferLength) {
+
+                        // select level for next fragment load
+                        if(_levelNext != -1) {
+                            level = _levelNext;
+                        } else if (_hls.autoLevel && _levels.length > 1 ) {
+                            // select level from heuristics (current level / last fragment duration / buffer length)
+                            level = _levelController.getnextlevel(_hls.loadLevel, _hls.stream.bufferLength);
+                        } else if (_hls.autoLevel && _levels.length == 1 ) {
+                            level = 0;
+                        } else {
+                            level = _hls.manualLevel;
+                        }
+
+                        // notify in case level switch occurs
+                        if (level != _hls.loadLevel) {
+                            _switchLevel = true;
+                            _demux = null;
+                            _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, level));
+                        }
+
+                        // check if we received playlist for choosen level. if live playlist, ensure that new playlist has been refreshed
+                        if ((_levels[level].fragments.length == 0) || (_hls.type == HLSTypes.LIVE && _levelLastLoaded != level)) {
+                            // playlist not yet received
+                            CONFIG::LOGGING {
+                                Log.debug("FragmentLoader#_checkLoading: playlist not received for levels[" + level + "]");
+                            }
+                            _loadingState = LOADING_WAITING_LEVEL_UPDATE;
+                            _levelNext = level;
+                        } else {
+                            _loadingState = _loadnextfragment(level, _fragPrevious);
+                        }
+                    }
+                    break;
+
+                /*
+                 * Loading has stalled.
+                 *
+                 * Next consecutive fragment not found, which is possible due
+                 * with live playlists:
+                 * - if bandwidth available is lower than lowest quality needed bandwidth
+                 * - after long pause
+                 */
+                case LOADING_STALLED:
+                    CONFIG::LOGGING {
+                        Log.warn("FragmentLoader#_checkLoading: loading stalled so stop fragment loading");
+                    }
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.LIVE_LOADING_STALLED));
+                    stop();
+                    break;
+
+                /*
+                 * Encryption key failed to load.
+                 *
+                 * Try reloading it after timeout.
+                 * See `_loadFragment`
+                 */
+                case  LOADING_KEY_IO_ERROR:
+                    if (getTimer() >= _keyLoadErrorDate) {
+                        _loadfragment(_fragCurrent);
+                        _loadingState = LOADING_IN_PROGRESS;
+                    }
+                    break;
+
+                /*
+                 * Fragment failed to load.
+                 *
+                 * Try reloading it after timeout.
+                 */
+                case LOADING_FRAGMENT_IO_ERROR:
+                    if (getTimer() >= _fragLoadErrorDate) {
+                        _loadfragment(_fragCurrent);
+                        _loadingState = LOADING_IN_PROGRESS;
+                    }
+                    break;
+
+                /*
+                 * Final Fragment has completely loaded.
+                 *
+                 * We have finished downloading Video. Stop loader.
+                 */
+                case LOADING_COMPLETED:
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.LAST_VOD_FRAGMENT_LOADED));
+                    stop();
+                    break;
+
+                /*
+                 * Unknown state. Throw error.
+                 */
+                default:
+                    CONFIG::LOGGING {
+                        Log.error("FragmentLoader#_checkLoading: invalid loading state: " + _loadingState);
+                    }
+                    break;
+            }
+        }
+
+        //
+        //
+        //
+        // PRIVATE FRAGMENT METHODS
+        //
+        //
+        //
+
+        //
+        // TODO: REVIEW!
+        //
+        private function _onFragmentIOError(message : String) : void {
+            /* usually, errors happen in two situations :
+            - bad networks  : in that case, the second or third reload of URL should fix the issue
+                               if loading retry still fails after HLSSettings.fragmentLoadMaxRetry, and
+                               if (a) redundant stream(s) is/are available for that level, then try to switch
+                               to that redundant stream instead.
+            - live playlist : when we are trying to load an out of bound fragments : for example,
+            the playlist on webserver is from SN [51-61]
+            the one in memory is from SN [50-60], and we are trying to load SN50.
+             */
+            CONFIG::LOGGING {
+                Log.error("I/O Error while loading fragment:" + message);
+            }
+            if (HLSSettings.fragmentLoadMaxRetry == -1 || _fragRetryCount < HLSSettings.fragmentLoadMaxRetry) {
+                _loadingState = LOADING_FRAGMENT_IO_ERROR;
+                _fragLoadErrorDate = getTimer() + _fragRetryTimeout;
+                CONFIG::LOGGING {
+                    Log.warn("retry fragment load in " + _fragRetryTimeout + " ms, count=" + _fragRetryCount);
+                }
+                /* exponential increase of retry timeout, capped to fragmentLoadMaxRetryTimeout */
+                _fragRetryCount++;
+                _fragRetryTimeout = Math.min(HLSSettings.fragmentLoadMaxRetryTimeout, 2 * _fragRetryTimeout);
+            } else {
+                var level : Level = _levels[_fragCurrent.level];
+                // if we have redundant streams left for that level, switch to it
+                if(level.redundantStreamId < level.redundantStreamsNb) {
+                    CONFIG::LOGGING {
+                        Log.warn("max load retry reached, switch to redundant stream");
+                    }
+                    level.redundantStreamId++;
+                    _fragRetryCount = 0;
+                    _fragRetryTimeout = 1000;
+                    _loadingState = LOADING_IDLE;
+                } else if(HLSSettings.fragmentLoadSkipAfterMaxRetry == true) {
+                    /* check if loaded fragment is not the last one of a live playlist.
+                        if it is the case, don't skip to next, as there is no next fragment :-)
+                    */
+                    if(_hls.type == HLSTypes.LIVE && _fragCurrent.seqnum == level.end_seqnum) {
+                        _loadingState = LOADING_FRAGMENT_IO_ERROR;
+                        _fragLoadErrorDate = getTimer() + _fragRetryTimeout;
+                        CONFIG::LOGGING {
+                            Log.warn("max load retry reached on last fragment of live playlist, retrying loading this one...");
+                        }
+                        /* exponential increase of retry timeout, capped to fragmentLoadMaxRetryTimeout */
+                        _fragRetryCount++;
+                        _fragRetryTimeout = Math.min(HLSSettings.fragmentLoadMaxRetryTimeout, 2 * _fragRetryTimeout);
+                    } else {
+                        CONFIG::LOGGING {
+                            Log.warn("max fragment load retry reached, skip fragment and load next one");
+                        }
+                        var tags : Vector.<FLVTag> = tags = new Vector.<FLVTag>();
+                        tags.push(_fragCurrent.getSkippedTag());
+                        // send skipped FLV tag to StreamBuffer
+                        _streamBuffer.appendTags(HLSLoaderTypes.FRAGMENT_MAIN,_fragCurrent.level,_fragCurrent.seqnum ,tags,_fragCurrent.data.pts_start_computed, _fragCurrent.data.pts_start_computed + 1000*_fragCurrent.duration, _fragCurrent.continuity, _fragCurrent.start_time);
+                        _fragRetryCount = 0;
+                        _fragRetryTimeout = 1000;
+                        _fragPrevious = _fragCurrent;
+                        _fragSkipping = true;
+                        // set fragment first loaded to be true to ensure that we can skip first fragment as well
+                        _fragmentFirstLoaded = true;
+                        _loadingState = LOADING_IDLE;
+                    }
+                } else {
+                    var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, _fragCurrent.url, "I/O Error :" + message);
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
+                }
+            }
+        }
+
+        //
+        //
+        //
+        //
+        //
+        //
+
+        private function _loadfirstfragment(position : Number, level : int) : int {
+
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#_loadfirstfragment(" + position + ", " + level + ")");
+            }
+
+            var frag : Fragment = _levels[level].getFragmentBeforePosition(position);
+            _hasDiscontinuity = true;
+
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#_loadfirstfragment: Loading " + frag.seqnum + " of [" + (_levels[level].start_seqnum) + "," + (_levels[level].end_seqnum) + "],level " + level);
+            }
+
+            _loadfragment(frag);
+
+            return LOADING_IN_PROGRESS;
+        }
+
+        /** Load a fragment **/
+        private function _loadnextfragment(level : int, frag_previous : Fragment) : int {
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#_loadnextfragment");
+            }
+            var new_seqnum : Number;
+            var last_seqnum : Number = -1;
+            var log_prefix : String;
+            var frag : Fragment;
+
+            if (_switchLevel == false || frag_previous.continuity == -1) {
+                last_seqnum = frag_previous.seqnum;
+            } else {
+                // level switch
+                // trust program-time : if program-time defined in previous loaded fragment, try to find seqnum matching program-time in new level.
+                if (frag_previous.program_date) {
+                    last_seqnum = _levels[level].getSeqNumFromProgramDate(frag_previous.program_date);
+                    CONFIG::LOGGING {
+                        Log.debug("FragmentLoader#_loadnextfragment: getSeqNumFromProgramDate(level,date,cc:" + level + "," + frag_previous.program_date + ")=" + last_seqnum);
+                    }
+                }
+                if (last_seqnum == -1) {
+                    // if we are here, it means that no program date info is available in the playlist. try to get last seqnum position from PTS + continuity counter
+                    last_seqnum = _levels[level].getSeqNumNearestPTS(frag_previous.data.pts_start, frag_previous.continuity);
+                    CONFIG::LOGGING {
+                        Log.debug("FragmentLoader#_loadnextfragment: getSeqNumNearestPTS(level,pts,cc:" + level + "," + frag_previous.data.pts_start + "," + frag_previous.continuity + ")=" + last_seqnum);
+                    }
+                    if (last_seqnum == Number.POSITIVE_INFINITY) {
+                        /* requested PTS above max PTS of this level:
+                         * this case could happen when switching level at the edge of live playlist,
+                         * in case playlist of new level is outdated
+                         * return 1 to retry loading later.
+                         */
+                        return LOADING_WAITING_LEVEL_UPDATE;
+                    } else if (last_seqnum == -1) {
+                        // if we are here, it means that we have no PTS info for this continuity index, we need to do some PTS probing to find the right seqnum
+                        /* we need to perform PTS analysis on fragments from same continuity range
+                        get first fragment from playlist matching with criteria and load pts */
+                        last_seqnum = _levels[level].getFirstSeqNumfromContinuity(frag_previous.continuity);
+                        CONFIG::LOGGING {
+                            Log.debug("FragmentLoader#_loadnextfragment: getFirstSeqNumfromContinuity(level,cc:" + level + "," + frag_previous.continuity + ")=" + last_seqnum);
+                        }
+                        if (last_seqnum == Number.NEGATIVE_INFINITY) {
+                            // playlist not yet received
+                            return LOADING_WAITING_LEVEL_UPDATE;
+                        }
+                        /* when probing PTS, take previous sequence number as reference if possible */
+                        new_seqnum = Math.min(frag_previous.seqnum + 1, _levels[level].getLastSeqNumfromContinuity(frag_previous.continuity));
+                        new_seqnum = Math.max(new_seqnum, _levels[level].getFirstSeqNumfromContinuity(frag_previous.continuity));
+                        _ptsAnalyzing = true;
+                        log_prefix = "analyzing PTS ";
+                    } else {
+                        // last seqnum found on new level, reset PTS analysis flag
+                        _ptsAnalyzing = false;
+                    }
+                }
+            }
+
+            if (_ptsAnalyzing == false) {
+                if (last_seqnum == _levels[level].end_seqnum) {
+                    // if last segment of level already loaded, return
+                    if (_hls.type == HLSTypes.VOD) {
+                        // if VOD playlist, loading is completed
+                        return LOADING_COMPLETED;
+                    } else {
+                        // if live playlist, loading is pending on manifest update
+                        return LOADING_WAITING_LEVEL_UPDATE;
+                    }
+                } else {
+                    // if previous segment is not the last one, increment it to get new seqnum
+                    new_seqnum = last_seqnum + 1;
+                    if (new_seqnum < _levels[level].start_seqnum) {
+                        // loading stalled ! report to caller
+                        return LOADING_STALLED;
+                    }
+                    frag = _levels[level].getFragmentfromSeqNum(new_seqnum);
+                    if (frag == null) {
+                        CONFIG::LOGGING {
+                            Log.warn("FragmentLoader#_loadnextfragment: error trying to load " + new_seqnum + " of [" + (_levels[level].start_seqnum) + "," + (_levels[level].end_seqnum) + "],level " + level);
+                        }
+                        return LOADING_WAITING_LEVEL_UPDATE;
+                    }
+                    // check whether there is a discontinuity between last segment and new segment
+                    _hasDiscontinuity = ((frag.continuity != frag_previous.continuity) || _fragSkipping);
+                    ;
+                    log_prefix = "Loading ";
+                }
+            }
+            frag = _levels[level].getFragmentfromSeqNum(new_seqnum);
+            _loadfragment(frag);
+            return LOADING_IN_PROGRESS;
+        };
+
+        /**
+         * Load a Fragment.
+         *
+         * Loads encryption key synchronously if necessary.
+         *
+         * @param  {Fragment}
+         */
+        private function _loadfragment(frag : Fragment) : void {
+
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#_loadfragment: Will load Fragment levels[" + frag.level + "][" + frag.seqnum + "] from " + frag.url);
+            }
+
+            if (_fragmentStream == null) {
+                _fragmentStream = new FragmentStream(_hls.stage);
+                _fragmentStream.addEventListener(IOErrorEvent.IO_ERROR, _onFragmentStreamError);
+                _fragmentStream.addEventListener(SecurityErrorEvent.SECURITY_ERROR, _onFragmentStreamError);
+                _fragmentStream.addEventListener(ProgressEvent.PROGRESS, _onFragmentStreamProgress);
+                _fragmentStream.addEventListener(Event.COMPLETE, _onFragmentStreamComplete);
+            }
+
+            // If there is a DISCONTINUITY or level switch, then force new Demuxer
+            if (_hasDiscontinuity || _switchLevel) {
+                _demux = null;
+            }
+
+            _fragCurrent = frag; // BEWARE STATE
+
+            frag.data.auto_level = _hls.autoLevel;
+
+            /**
+             * Called after encryption key was loaded, if necessary.
+             *
+             * NOTE: This is a JS style callback.
+             *
+             * @param  {HLSError}
+             * @param  {ByteArray}  keyData  -  Encryption key
+             */
+            var onKeyLoaded : Function = function onKeyLoaded(err : HLSError, keyData : ByteArray) : void {
+
+                // If there was an error loading key then retry
+                // Accomplished by bailing - event loop will recall _loadfragment
+                if (err) {
+                    // Try reloading the key if feature is enabled
+                    if (HLSSettings.keyLoadMaxRetry == -1 || _keyRetryCount < HLSSettings.keyLoadMaxRetry) {
+                        _loadingState = LOADING_KEY_IO_ERROR;
+                        _keyLoadErrorDate = getTimer() + _keyRetryTimeout;
+                        CONFIG::LOGGING {
+                            Log.warn("FragmentLoader#_loadfragment#onKeyLoaded: Will retry key load in " + _keyRetryTimeout + " ms, count=" + _keyRetryCount);
+                        }
+                        /* exponential increase of retry timeout, capped to keyLoadMaxRetryTimeout */
+                        _keyRetryCount++;
+                        _keyRetryTimeout = Math.min(HLSSettings.keyLoadMaxRetryTimeout, 2 * _keyRetryTimeout);
+                    } else {
+                        var hlsError : HLSError = new HLSError(HLSError.KEY_LOADING_ERROR, _fragCurrent.decrypt_url, "I/O Error");
+                        _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, err));
+                    }
+                    return;
+                }
+
+                try {
+                    // load complete, reset retry counter
+                    _keyRetryCount = 0;
+                    _keyRetryTimeout = 1000;
+
+                    frag.data.bytes = null;
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADING, frag.url));
+
+                    _metrics = _fragmentStream.load(frag, keyData);
+
+                    CONFIG::LOGGING {
+                        Log.debug("FragmentLoader#_loadfragment#onKeyLoaded: Loading Fragment levels[" + frag.level + "][" + frag.seqnum + "] from " + frag.url);
+                    }
+                } catch (error : Error) {
+                    var hlsError : HLSError = new HLSError(HLSError.FRAGMENT_LOADING_ERROR, frag.url, error.message);
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
+                }
+            }
+
+            _keyLoader.load(frag.decrypt_url, onKeyLoaded);
+        }
+
+        //
+        //
+        //
+        // FragmentStream Event Listeners
+        //
+        //
+        //
+
+        /**
+         * When there awas an error downloading Fragment.
+         *
+         * This is usually:
+         * - I/O Error
+         * - CORS Error
+         *
+         * @private
+         * @method   _onFragmentStreamError
+         * @param    {ErrorEvent}  evt
+         */
+        private function _onFragmentStreamError(event : ErrorEvent) : void {
+            CONFIG::LOGGING {
+                Log.error("FragmentLoader#_onFragmentStreamError!");
+            }
+
+            // SecurityErrorEvent is a fatal error
+            if (event is SecurityErrorEvent) {
+                var hlsError : HLSError = new HLSError(
+                    HLSError.FRAGMENT_LOADING_CROSSDOMAIN_ERROR,
+                    _fragCurrent.url,
+                    "Cannot load fragment: crossdomain access denied:" + event.text
+                    );
+                _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, hlsError));
+            } else {
+                _onFragmentIOError("HTTP status:" + _fragLoadStatus + ",msg:" + event.text);
+            }
+        };
+
+        /**
+         * When a portion of Fragment has been downloaded by FragmentStream.
+         *
+         * When possible progressed stream is immediately forwarded to Demuxer
+         * for parsing, except in the following cases:
+         *
+         *  - Only a partial Fragment was requested via the
+         *    Fragment.byterange_start_offset field.
+         *
+         * The end-case listener, `onFragmentStreamComplete` will parse either
+         * the final portion or the entirety of the Fragment.
+         *
+         * @private
+         * @method   onFragmentStreamProgress
+         * @param    {ProgressEvent}  evt
+         */
+        private function _onFragmentStreamProgress(evt : ProgressEvent) : void {
+
+            _fragCurrent = _fragmentStream.getFragment();
+
+            // If we are loading a partial Fragment then only parse when it has
+            // completed loading to desired portion (See onFragmentStreamComplete)
+            if (_fragCurrent.byterange_start_offset != -1) {
+                return;
+            }
+
+            // START PARSING METRICS
+            if (_metrics.parsing_begin_time == 0) {
+                _metrics.parsing_begin_time = getTimer();
+            }
+
+            // Demuxer has not yet been initialized as this is probably first
+            // call to onFragmentStreamProgress, but demuxer may also be/remain
+            // null due to unknown Fragment type. Probe is synchronous.
+            if (_demux == null) {
+
+                _demux = DemuxHelper.probe(
+                    _fragCurrent.data.bytes,
+                    _levels[_hls.loadLevel],
+                    _onDemuxAudioTrackRequested,
+                    _onDemuxProgress,
+                    _onDemuxComplete,
+                    _onDemuxVideoMetadata,
+                    _onDemuxID3TagFound,
+                    false
+                    );
+            }
+
+            if (_demux) {
+
+                // Demuxer expects the ByteArray delta
+                var byteArray : ByteArray = new ByteArray();
+                _fragCurrent.data.bytes.readBytes(byteArray, 0, _fragCurrent.data.bytes.length - _fragCurrent.data.bytes.position);
+                byteArray.position = 0;
+
+                _demux.append(byteArray);
+            }
+        }
+
+        /**
+         * When the Fragment has been completely downloaded by FragmentStream.
+         *
+         * If Fragment had a non-start offset then this method sends data to
+         * demuxer.
+         *
+         * @private
+         * @method   onFragmentStreamComplete
+         * @param    {Event}  evt
+         */
+        private function _onFragmentStreamComplete(evt : Event) : void {
+
+            _fragCurrent = _fragmentStream.getFragment();
+
+            CONFIG::LOGGING {
+                // String used to identify fragment in debug messages
+                var fragmentString : String = "Fragment[" + _fragCurrent.level + "][" + _fragCurrent.seqnum + "]";
+                Log.debug("FragmentLoader#_onFragmentStreamComplete: " +
+                    fragmentString + " Processing Complete");
+            }
+
+            // ???: Is this because the Player has been stopped?
+            if (_loadingState == LOADING_IDLE) {
+                return;
+            }
+
+            // Handle partial Fragment loading if necessary
+            if (_fragCurrent.byterange_start_offset != -1) {
+
+                // START PARSING METRICS (which were skipped during loading progress)
+                if (_metrics.parsing_begin_time == 0) {
+                    _metrics.parsing_begin_time = getTimer();
+                }
+
+                CONFIG::LOGGING {
+                    Log.debug("FragmentLoader#_onFragmentStreamComplete: trim byte range, start/end offset:" +
+                        _fragCurrent.byterange_start_offset + "/" +
+                        _fragCurrent.byterange_end_offset);
+                }
+
+                // Copy only the part of Fragment we care about
+                var partialByteArray : ByteArray = new ByteArray();
+                _fragCurrent.data.bytes.position = _fragCurrent.byterange_start_offset;
+                _fragCurrent.data.bytes.readBytes(partialByteArray, 0, _fragCurrent.byterange_end_offset - _fragCurrent.byterange_start_offset);
+
+                _demux = DemuxHelper.probe(
+                    partialByteArray,
+                    _levels[_hls.loadLevel],
+                    _onDemuxAudioTrackRequested,
+                    _onDemuxProgress,
+                    _onDemuxComplete,
+                    _onDemuxVideoMetadata,
+                    _onDemuxID3TagFound,
+                    false
+                    );
+
+                if (_demux) {
+                    partialByteArray.position = 0;
+                    _demux.append(partialByteArray);
+                }
+            }
+
+            // If demuxer is still null, then the Fragment type was invalid
+            if (_demux == null) {
+                CONFIG::LOGGING {
+                    Log.error("FragmentLoader#_onFragmentStreamComplete: unknown fragment type");
+                    if (HLSSettings.logDebug2) {
+                        _fragCurrent.data.bytes.position = 0;
+                        var bytes2 : ByteArray = new ByteArray();
+                        _fragCurrent.data.bytes.readBytes(bytes2, 0, 512);
+                        Log.debug2("FragmentLoader#_onFragmentStreamComplete: frag dump(512 bytes)");
+                        Log.debug2(Hex.fromArray(bytes2));
+                    }
+                }
+                _onFragmentIOError("invalid content received");
+                _fragCurrent.data.bytes = null;
+                return;
+            }
+
+            _demux.notifycomplete();
+        }
+
+        //
+        //
+        //
+        // Demuxer Callbacks
+        //
+        //
+        //
+
+        /**
+         * Called when Demuxer parsed portion of Fragment.
+         *
+         * @method  _onDemuxProgress
+         * @param   {Vector<FLVTag}  tags
+         */
+        private function _onDemuxProgress(tags : Vector.<FLVTag>) : void {
+
+            _fragCurrent.data.appendTags(tags);
+
+            CONFIG::LOGGING {
+                Log.debug2("FragmentLoader#_onDemuxProgress: " + tags.length + " FLVTags extracted and appended to Fragment[" + _fragCurrent.level + "][" + _fragCurrent.seqnum + "]");
+            }
+
+            var isFirstFragmentLevelDefined : Boolean =
+                HLSSettings.startFromLevel !== -1 ||
+                HLSSettings.startFromBitrate !== -1 ||
+                _levels.length == 1;
+
+            // Determine if we can do progressively append to StreamBuffer
+            if (_fragmentFirstLoaded || (_manifestJustLoaded && isFirstFragmentLevelDefined)) {
+
+                //
+                // if audio expected, PTS analysis is done on audio
+                // if audio not expected, PTS analysis is done on video
+                // the check below ensures that we can compute min/max PTS
+                //
+                if ((_demux.audioExpected && _fragCurrent.data.audio_found) || (!_demux.audioExpected && _fragCurrent.data.video_found)) {
+
+                    //
+                    // TODO: REVIEW THIS CODE
+                    //
+                    if (_ptsAnalyzing == true) {
+
+                        // in case we are probing PTS, retrieve PTS info and synchronize playlist PTS / sequence number
+                        CONFIG::LOGGING {
+                            Log.debug("FragmentLoader#_onDemuxProgress: we were analyzing PTS");
+                        }
+
+                        _ptsAnalyzing = false;
+                        _levels[_hls.loadLevel].updateFragment(
+                            _fragCurrent.seqnum,
+                            true,
+                            _fragCurrent.data.pts_min,
+                            _fragCurrent.data.pts_min + _fragCurrent.duration * 1000);
+
+                        // in case we are probing PTS, retrieve PTS info and synchronize playlist PTS / sequence number
+                        CONFIG::LOGGING {
+                            Log.debug("FragmentLoader#_onDemuxProgress: analyzed PTS " +
+                                _fragCurrent.seqnum + " of [" +
+                                (_levels[_hls.loadLevel].start_seqnum) + "," +
+                                (_levels[_hls.loadLevel].end_seqnum) + "],level " +
+                                _hls.loadLevel + " m PTS:" + _fragCurrent.data.pts_min);
+                        }
+
+                        //
+                        // check if fragment loaded for PTS analysis is the next one
+                        // if this is the expected one, then continue
+                        // if not, then cancel current fragment loading, next call to loadnextfragment() will load the right seqnum
+                        //
+                        var next_seqnum : Number = _levels[_hls.loadLevel].getSeqNumNearestPTS(
+                            _fragPrevious.data.pts_start,
+                            _fragCurrent.continuity
+                            ) + 1;
+
+                        CONFIG::LOGGING {
+                            Log.debug("FragmentLoader#_onDemuxProgress: analyzed PTS : getSeqNumNearestPTS(level,pts,cc:" +
+                                _hls.loadLevel + "," + _fragPrevious.data.pts_start +
+                                "," + _fragCurrent.continuity + ")=" + next_seqnum);
+                        }
+
+                        if (next_seqnum != _fragCurrent.seqnum) {
+                            // stick to same level after PTS analysis
+                            _levelNext = _hls.loadLevel;
+                            CONFIG::LOGGING {
+                                Log.debug("FragmentLoader#_onDemuxProgress: PTS analysis done on " +
+                                    _fragCurrent.seqnum + ", matching seqnum is " +
+                                    next_seqnum + " of [" +
+                                    (_levels[_hls.loadLevel].start_seqnum) + "," +
+                                    (_levels[_hls.loadLevel].end_seqnum) +
+                                    "], cancel loading and get new one");
+                            }
+                            _stop_load();
+                            _fragCurrent.data.flushTags();
+                            _loadingState = LOADING_IDLE;
+                            return;
+                        }
+                    }
+
+                    if (_fragCurrent.data.metadata_tag_injected == false) {
+                        _fragCurrent.data.tags.unshift(_fragCurrent.getMetadataTag());
+                        if (_hasDiscontinuity) {
+                            _fragCurrent.data.tags.unshift(new FLVTag(FLVTag.DISCONTINUITY, _fragCurrent.data.dts_min, _fragCurrent.data.dts_min, false));
+                        }
+                        _fragCurrent.data.metadata_tag_injected = true;
+                    }
+
+                    // provide tags to StreamBuffer
+                    _streamBuffer.appendTags(
+                        HLSLoaderTypes.FRAGMENT_MAIN,
+                        _fragCurrent.level,
+                        _fragCurrent.seqnum,
+                        _fragCurrent.data.tags,
+                        _fragCurrent.data.tag_pts_min,
+                        _fragCurrent.data.tag_pts_max + _fragCurrent.data.tag_duration,
+                        _fragCurrent.continuity,
+                        _fragCurrent.start_time + _fragCurrent.data.tag_pts_start_offset / 1000
+                        );
+
+                    _fragCurrent.data.shiftTags();
+
+                    _metrics.parsing_end_time = getTimer();
+                    _metrics.size = _fragCurrent.data.bytesLoaded;
+                    _metrics.duration = _fragCurrent.data.tag_pts_end_offset;
+                    _metrics.id2 = _fragCurrent.data.tags.length;
+
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.TAGS_LOADED, _metrics));
+                    _hasDiscontinuity = false;
+                }
+            }
+
+        }
+
+        /**
+         * Called when Demuxer has finished parsing Fragment.
+         *
+         * This method is responsible for appending to StreamBuffer if
+         * progressive buffering was not possbile.
+         *
+         * @method  _onDemuxComplete
+         */
+        private function _onDemuxComplete() : void {
+
+            if (_loadingState == LOADING_IDLE) {
+                return;
+            }
+
+            //
+            // Throw error if Demuxer had problems, but do not bail.
+            //
+            if ((_demux.audioExpected && !_fragCurrent.data.audio_found) && (_demux.videoExpected && !_fragCurrent.data.video_found)) {
+                var parsingError : HLSError = new HLSError(
+                    HLSError.FRAGMENT_PARSING_ERROR,
+                    _fragCurrent.url,
+                    "error parsing fragment, no tag found"
+                    );
+                _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, parsingError));
+            }
+            CONFIG::LOGGING {
+                if (_fragCurrent.data.audio_found) {
+                    Log.debug("FragmentLoader#_onDemuxComplete: m/M audio PTS:" +
+                        _fragCurrent.data.pts_min_audio + "/" + _fragCurrent.data.pts_max_audio);
+                }
+                if (_fragCurrent.data.video_found) {
+                    Log.debug("FragmentLoader#_onDemuxComplete: m/M video PTS:" +
+                        _fragCurrent.data.pts_min_video + "/" + _fragCurrent.data.pts_max_video);
+
+                    if (!_fragCurrent.data.audio_found) {
+                    } else {
+                        Log.debug("FragmentLoader#_onDemuxComplete: Delta audio/video m/M PTS:" +
+                            (_fragCurrent.data.pts_min_video - _fragCurrent.data.pts_min_audio) +
+                            "/" + (_fragCurrent.data.pts_max_video - _fragCurrent.data.pts_max_audio));
+                    }
+                }
+            }
+
+            //
+            // Finish calculating processing metrics
+            // TODO: What else should this include, e.g. fragment fixing?
+            //
+            _metrics.parsing_end_time = getTimer();
+            CONFIG::LOGGING {
+                Log.debug("FragmentLoader#_onDemuxComplete: Total Process duration/length/bw: " +
+                    _metrics.processing_duration + " / " + _metrics.size + " / " +
+                    Math.round(_metrics.bandwidth / 1024) + " kbps");
+            }
+
+            //
+            // Check if we should immediately adjust level.
+            // If this was the first Fragment loaded after the adaptive manifest
+            // itself was loaded then we may be using this Fragment simply to be
+            // testing bandwidth. Toss it and start Fragment loop again.
+            //
+            if (_manifestJustLoaded) {
+                _manifestJustLoaded = false;
+                if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1 && _levels.length > 1) {
+                    // check if we can directly switch to a better bitrate, in case download bandwidth is enough
+                    var bestlevel : int = _levelController.getbestlevel(_metrics.bandwidth);
+                    if (bestlevel > _hls.loadLevel) {
+                        CONFIG::LOGGING {
+                            Log.info("FragmentLoader#_onDemuxComplete: enough download bandwidth, adjust start level from " + _hls.loadLevel + " to " + bestlevel);
+                        }
+                        // dispatch event for tracking purpose
+                        _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADED, _metrics));
+                        // let's directly jump to the accurate level to improve quality at player start
+                        _levelNext = bestlevel;
+                        _loadingState = LOADING_IDLE;
+                        _switchLevel = true;
+                        _demux = null;
+                        _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _hls.loadLevel));
+                        // speed up loading of new playlist
+                        _timer.start();
+                        return;
+                    }
+                }
+            }
+
+            try {
+
+                _switchLevel = false;
+                _levelNext = -1;
+
+                CONFIG::LOGGING {
+                    Log.debug("FragmentLoader#_onDemuxComplete: Completed parsing of " +
+                        _fragCurrent.seqnum + " of [" + (_levels[_hls.loadLevel].start_seqnum) +
+                        "," + (_levels[_hls.loadLevel].end_seqnum) + "],level " +
+                        _hls.loadLevel + " m/M PTS:" + _fragCurrent.data.pts_min + "/" + _fragCurrent.data.pts_max);
+                }
+
+                // TODO: What type of Fragment do we have if this is false?
+                if (_fragCurrent.data.audio_found || _fragCurrent.data.video_found) {
+
+                    // Update Fragment (???)
+                    _levels[_hls.loadLevel].updateFragment(_fragCurrent.seqnum, true, _fragCurrent.data.pts_min, _fragCurrent.data.pts_max + _fragCurrent.data.tag_duration);
+
+                    // set pts_start here, it might not be updated directly in updateFragment() if this loaded fragment has been removed from a live playlist
+                    _fragCurrent.data.pts_start = _fragCurrent.data.pts_min;
+
+                    _hls.dispatchEvent(new HLSEvent(HLSEvent.PLAYLIST_DURATION_UPDATED, _levels[_hls.loadLevel].duration));
+
+                    // We did not progressively add to StreamBuffer
+                    if (_fragCurrent.data.tags.length) {
+
+                        //
+                        // If tag vector does not start with a METADATA FLVTag then
+                        // prepend one, and if there is DISCONTINUITY then prepend
+                        // to that
+                        //
+                        // NOTE: `getMetadataTag` should really be `createMetadataTag`
+                        //
+                        if (_fragCurrent.data.metadata_tag_injected == false) {
+                            _fragCurrent.data.tags.unshift(_fragCurrent.getMetadataTag());
+                            if (_hasDiscontinuity) {
+                                _fragCurrent.data.tags.unshift(
+                                    new FLVTag(
+                                        FLVTag.DISCONTINUITY,
+                                        _fragCurrent.data.dts_min,
+                                        _fragCurrent.data.dts_min,
+                                        false
+                                        )
+                                    );
+                            }
+                            _fragCurrent.data.metadata_tag_injected = true;
+                        }
+
+                        _streamBuffer.appendTags(
+                            HLSLoaderTypes.FRAGMENT_MAIN,
+                            _fragCurrent.level,
+                            _fragCurrent.seqnum,
+                            _fragCurrent.data.tags,
+                            _fragCurrent.data.tag_pts_min,
+                            _fragCurrent.data.tag_pts_max + _fragCurrent.data.tag_duration,
+                            _fragCurrent.continuity,
+                            _fragCurrent.start_time + _fragCurrent.data.tag_pts_start_offset / 1000
+                            );
+
+                        // Metrics
+                        _metrics.duration = _fragCurrent.data.pts_max + _fragCurrent.data.tag_duration - _fragCurrent.data.pts_min;
+                        _metrics.id2 = _fragCurrent.data.tags.length;
+
+                        _hls.dispatchEvent(new HLSEvent(HLSEvent.TAGS_LOADED, _metrics));
+                        _fragCurrent.data.shiftTags();
+                        _hasDiscontinuity = false;
+                    }
+
+                } else {
+                    _metrics.duration = _fragCurrent.duration * 1000;
+                }
+
+                //
+                // Resets states for next fragment and to start load
+                //
+
+                _loadingState = LOADING_IDLE;
+                _ptsAnalyzing = false;
+                _hls.dispatchEvent(new HLSEvent(HLSEvent.FRAGMENT_LOADED, _metrics));
+                _fragmentFirstLoaded = true;
+                _fragPrevious = _fragCurrent;
+
+            } catch (error : Error) {
+                var otherError : HLSError = new HLSError(
+                    HLSError.OTHER_ERROR,
+                    _fragCurrent.url,
+                    error.message
+                    );
+                _hls.dispatchEvent(new HLSEvent(HLSEvent.ERROR, otherError));
+            }
+
+            _timer.start();
+        }
+
+        /**
+         * Called when Demuxer needs to know which AudioTrack to parse for.
+         *
+         * @method  _onDemuxAudioTrackRequested
+         * @param   {Vector<AudioTrack}  audioTrackList  -  List of AudioTracks
+         * @return  {AudioTrack}  -  AudioTrack to parse
+         */
+        private function _onDemuxAudioTrackRequested(audioTrackList : Vector.<AudioTrack>) : AudioTrack {
+            return _audioTrackController.audioTrackSelectionHandler(audioTrackList);
+        }
+
+        /**
+         * Called when ID3 tags are found.
+         *
+         * @method  _onDemuxID3TagFound
+         * @param   {Vector.<ID3Tag>}  id3_tags  -  ID3 Tags
+         */
+        private function _onDemuxVideoMetadata(width : uint, height : uint) : void {
+            var fragData : FragmentData = _fragCurrent.data;
+            if (fragData.video_width == 0) {
+                CONFIG::LOGGING {
+                    Log.debug("FragmentLoader#_onDemuxVideoMetadata: AVC SPS = " + width + "x" + height);
+                }
+                fragData.video_width = width;
+                fragData.video_height = height;
+            }
+        }
+
+        /**
+         * Called when Video metadata is parsed.
+         *
+         * Specifically, when Sequence Parameter Set (SPS) is found.
+         *
+         * @method  _onDemuxVideoMetadata
+         * @param   {uint}  width
+         * @param   {uint}  height
+         */
+        private function _onDemuxID3TagFound(id3_tags : Vector.<ID3Tag>) : void {
+            _fragCurrent.data.id3_tags = id3_tags;
+        }
+    }
+}


Add KrewScenarioPlayer

Add KrewScenarioPlayer

error: pathspec 'krew-framework/krewfw/builtin_actor/system/KrewScenarioPlayer.as' did not match any file(s) known to git

--- krew-framework/krewfw/builtin_actor/system/KrewScenarioPlayer.as
+++ krew-framework/krewfw/builtin_actor/system/KrewScenarioPlayer.as
@@ -0,0 +1,187 @@
+package krewfw.builtin_actor.system {
+
+    import krewfw.core.KrewActor;
+
+    /**
+     * Base class for event-driven command player.
+     */
+    //------------------------------------------------------------
+    public class KrewScenarioPlayer extends KrewActor {
+
+        private var _eventList:Array;
+        private var _eventsByTrigger:Object;
+
+        private var _triggers:Object = {};
+        private var _methods:Object  = {};
+
+        //------------------------------------------------------------
+        public function KrewScenarioPlayer() {
+            displayable = false;
+        }
+
+        protected override function onDispose():void {
+            _eventList       = null;
+            _eventsByTrigger = null;
+            _triggers        = null;
+            _methods         = null;
+        }
+
+        //------------------------------------------------------------
+        // public
+        //------------------------------------------------------------
+
+        /**
+         * @params eventList Example:
+         *

+         * [
+         *     {
+         *         "trigger_key" : "start_turn",
+         *         "trigger_params": {"turn": 1},
+         *         "method": "dialog",
+         *         "params": {
+         *             "messages": [ ... ]
+         *         },
+         *         "next": [
+         *             {
+         *                 "method": "overlay",
+         *                 "params": { ... }
+         *             }
+         *         ]
+         *     },
+         *     ...
+         * ]
+         *

+         */
+        public function setData(eventList:Array):void {
+            _eventList = eventList;
+
+            _eventsByTrigger = {};
+            for each (var eventData:Object in _eventList) {
+                var triggerKey:String = eventData.trigger_key;
+                if (!triggerKey) { continue; }
+
+                if (!_eventsByTrigger[triggerKey]) {
+                    _eventsByTrigger[triggerKey] = [];
+                }
+                _eventsByTrigger[triggerKey].push(eventData);
+            }
+        }
+
+        /**
+         * @param checker Schema: function(eventArgs;Object, triggerParams:Object):Boolean
+         */
+        public function addTrigger(triggerKey:String, eventName:String, checker:Function):void {
+            _triggers[triggerKey] = new TriggerInfo(eventName, checker);
+        }
+
+        /**
+         * @param triggerInfoList Example:
+         *

+         * [
+         *     ["triggerKey", "eventName", checkerFunc],
+         *     ...
+         * ]
+         *

+         */
+        public function addTriggers(triggerInfoList:Array):void {
+            for each (var info:Array in triggerInfoList) {
+                addTrigger(info[0], info[1], info[2]);
+            }
+        }
+
+        /**
+         * @param handler Schema: function(params:Object, onComplete:Function):void
+         */
+        public function addMethod(methodName:String, handler:Function):void {
+            _methods[methodName] = handler;
+        }
+
+        /**
+         * @param triggerInfoList Example:
+         *

+         * [
+         *     ["methodName", methodFunc],
+         *     ...
+         * ]
+         *

+         */
+        public function addMethods(methodList:Array):void {
+            for each (var info:Array in methodList) {
+                addMethod(info[0], info[1]);
+            }
+        }
+
+        /**
+         * Activate player.
+         * Please call this after setData(), addTriggers(), addMethods(), and init().
+         */
+        public function activate():void {
+            for (var triggerKey:String in _eventsByTrigger) {
+                _listenEvent(triggerKey);
+            }
+        }
+
+        //------------------------------------------------------------
+        // private
+        //------------------------------------------------------------
+
+        private function _listenEvent(triggerKey:String):void {
+            var info:TriggerInfo = _triggers[triggerKey];
+            if (!info) {
+                throw new Error('[KrewEventPlayer] trigger not registered: ' + triggerKey);
+                return;
+            }
+
+            listen(info.eventName, function(eventArgs:Object):void {
+                for each (var eventData:Object in _eventsByTrigger[triggerKey]) {
+
+                    var triggerParams:Object = eventData.trigger_params;
+                    if (!info.checker(eventArgs, triggerParams)) { continue; }
+
+                    _callMethod(eventData);
+                }
+            });
+        }
+
+        private function _callMethod(eventData:Object):void {
+            var methodName:String = eventData.method;
+            if (!methodName) {
+                throw new Error('[KrewEventPlayer] method name not found. (trigger: '
+                                + eventData.trigger_key + ')');
+                return;
+            }
+
+            var method:Function = _methods[methodName];
+            if (method == null) {
+                throw new Error('[KrewEventPlayer] method not registered: ' + methodName);
+                return;
+            }
+
+            method(eventData.params, function():void {
+                _callNextMethod(eventData);
+            });
+        }
+
+        private function _callNextMethod(parentEventData:Object):void {
+            if (!parentEventData.next) { return; }
+
+            for each (var nextEventData:Object in parentEventData.next) {
+                _callMethod(nextEventData);
+            }
+        }
+
+    }
+}
+
+
+class TriggerInfo {
+
+    public var eventName:String;
+    public var checker:Function;
+
+    public function TriggerInfo(eventName:String, checker:Function) {
+        this.eventName = eventName;
+        this.checker   = checker;
+    }
+
+}


updating some things, now the Config is loading the 'config.xml' file manually, this must to be updated as soon as the new loading approach is implemented

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/rafaelcordoba/cocktail-demo.git/'


A container that holds a slider (that you've set up) and a label showing
the slider value.

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

error: pathspec 'src/as/com/threerings/flex/LabeledSlider.as' did not match any file(s) known to git

--- src/as/com/threerings/flex/LabeledSlider.as
+++ src/as/com/threerings/flex/LabeledSlider.as
@@ -0,0 +1,42 @@
+package com.threerings.flex {
+
+import mx.containers.HBox;
+
+import mx.controls.Label;
+
+import mx.controls.sliderClasses.Slider;
+
+import mx.events.SliderEvent;
+
+/**
+ * A simple component that displays a label to the left of a slider.
+ */
+public class LabeledSlider extends HBox
+{
+    /** The slider, all public and accessable. Don't fuck it up! */
+    public var slider :Slider;
+
+    /**
+     * Create a LabeledSlider holding the specified slider.
+     */
+    public function LabeledSlider (slider :Slider)
+    {
+        _label = new Label();
+        _label.text = String(slider.value);
+
+        addChild(_label);
+        this.slider = slider;
+        slider.showDataTip = false; // because we do it...
+        addChild(slider);
+
+        slider.addEventListener(SliderEvent.CHANGE, handleSliderChange, false, 0, true);
+    }
+
+    protected function handleSliderChange (event :SliderEvent) :void
+    {
+        _label.text = String(event.value);
+    }
+
+    protected var _label :Label;
+}
+}


no longer returning redundant points in region()

--- src/com/nodename/Delaunay/Site.as
+++ src/com/nodename/Delaunay/Site.as
@@ -173,8 +173,10 @@

 		private function reorderEdges():void
 		{
+			//trace("_edges:", _edges);
 			var reorderer:EdgeReorderer = new EdgeReorderer(_edges, Vertex);
 			_edges = reorderer.edges;
+			//trace("reordered:", _edges);
 			_edgeOrientations = reorderer.edgeOrientations;
 			reorderer.dispose();
 		}
@@ -209,14 +211,14 @@
 				}
 				connect(points, j, bounds);
 			}
-			// close up the polygon:
+			// close up the polygon by adding another corner point of the bounds if needed:
 			connect(points, i, bounds, true);
 			return points;
 		}

 		private function connect(points:Vector.<Point>, j:int, bounds:Rectangle, closingUp:Boolean = false):void
 		{
-			var rightPoint:Point = points[points.length - 1] as Point;
+			var rightPoint:Point = points[points.length - 1];
 			var newEdge:Edge = _edges[j] as Edge;
 			var newOrientation:LR = _edgeOrientations[j];
 			// the point that  must be connected to rightPoint:
@@ -233,6 +235,7 @@
 					// (NOTE this will not be correct if the region should take up more than
 					// half of the bounds rect, for then we will have gone the wrong way
 					// around the bounds and included the smaller part rather than the larger)
+					// TODO check the clockwise/counterclockwise orientation of the polygon
 					var rightCheck:int = BoundsCheck.check(rightPoint, bounds);
 					var newCheck:int = BoundsCheck.check(newPoint, bounds);
 					var px:Number, py:Number;
@@ -352,7 +355,11 @@
 				}
 				points.push(newPoint);
 			}
-			points.push(newEdge.clippedEnds[LR.other(newOrientation)]);
+			var newRightPoint:Point = newEdge.clippedEnds[LR.other(newOrientation)];
+			if (!closeEnough(points[0], newRightPoint))
+			{
+				points.push(newRightPoint);
+			}
 		}

 		internal function get x():Number


updating Layout bullet

updating Layout bullet

fatal: unable to access 'https://github.com/rafaelcordoba/cocktail-demo.git/': The requested URL returned error: 403


RSTPClient: OPTIONS

RSTPClient: OPTIONS

Start an RTSP session with fetching its options
and verify if the option is supported before
issuing it.

--- src/com/axis/rtspclient/RTSPClient.as
+++ src/com/axis/rtspclient/RTSPClient.as
@@ -17,15 +17,17 @@
     private static var userAgent:String = "Slush 0.1";

     private static var STATE_INITIAL:uint       = 1<<0;
-    private static var STATE_DESCRIBE_SENT:uint = 1<<1;
-    private static var STATE_DESCRIBE_RCVD:uint = 1<<2;
-    private static var STATE_SETUP:uint         = 1<<3;
-    private static var STATE_PLAY_SENT:uint     = 1<<4;
-    private static var STATE_PLAYING:uint       = 1<<5;
-    private static var STATE_PAUSE_SENT:uint    = 1<<6;
-    private static var STATE_PAUSED:uint        = 1<<7;
-    private static var STATE_TEARDOWN_SENT:uint = 1<<8;
-    private static var STATE_TEARDOWN_RCVD:uint = 1<<9;
+    private static var STATE_OPTIONS_SENT:uint  = 1<<1;
+    private static var STATE_OPTIONS_RCVD:uint  = 1<<2;
+    private static var STATE_DESCRIBE_SENT:uint = 1<<3;
+    private static var STATE_DESCRIBE_RCVD:uint = 1<<4;
+    private static var STATE_SETUP:uint         = 1<<5;
+    private static var STATE_PLAY_SENT:uint     = 1<<6;
+    private static var STATE_PLAYING:uint       = 1<<7;
+    private static var STATE_PAUSE_SENT:uint    = 1<<8;
+    private static var STATE_PAUSED:uint        = 1<<9;
+    private static var STATE_TEARDOWN_SENT:uint = 1<<10;
+    private static var STATE_TEARDOWN_RCVD:uint = 1<<11;

     private var state:int = STATE_INITIAL;

@@ -43,6 +45,7 @@
     private var interleaveChannelIndex:uint = 0;

     private var headers:ByteArray = new ByteArray();
+    private var methods:Array = [];
     private var data:ByteArray = new ByteArray();
     private var rtpLength:int = -1;
     private var rtpChannel:int = -1;
@@ -67,19 +70,34 @@
         trace('Cannot start unless in initial state.');
         return false;
       }
-      sendDescribeReq();
-      state = STATE_DESCRIBE_SENT;
+
+      if (0 === this.methods.length) {
+        /* We don't know the options yet. Start with that. */
+        sendOptionsReq();
+        state = STATE_OPTIONS_SENT;
+      } else {
+        /* Already queried the options (and perhaps got unauthorized on describe) */
+        sendDescribeReq();
+        state = STATE_DESCRIBE_SENT;
+      }
       return true;
     }

     public function pause():Boolean
     {
       if (state !== STATE_PLAYING) {
-        trace('Unable to pause a stream if not playing');
-        return false;
-      }
-      sendPauseReq();
-      state = STATE_PAUSE_SENT;
+        trace('Unable to pause a stream if not playing.');
+        return false;
+      }
+
+      try {
+        sendPauseReq();
+        state = STATE_PAUSE_SENT;
+      } catch (err:Error) {
+        trace(err.message);
+        return false;
+      }
+
       return true;
     }

@@ -89,6 +107,7 @@
         trace('Unable to resume a stream if not paused.');
         return false;
       }
+
       sendPlayReq();
       state = STATE_PLAY_SENT;
       return true;
@@ -195,10 +214,17 @@
       case STATE_INITIAL:
         trace("RTSPClient: STATE_INITIAL");

+      case STATE_OPTIONS_SENT:
+        trace("RTSPClient: STATE_DESCRIBE_SENT");
+        state = STATE_OPTIONS_RCVD;
+        this.methods = parsed.headers.public.split(/[ ]*,[ ]*/);
+
+        sendDescribeReq();
+        state = STATE_DESCRIBE_SENT;
+
         break;
       case STATE_DESCRIBE_SENT:
         trace("RTSPClient: STATE_DESCRIBE_SENT");
-
         state = STATE_DESCRIBE_RCVD;

         if (!sdp.parse(body)) {
@@ -303,6 +329,20 @@
       }
     }

+    private function supportCommand(command:String):Boolean
+    {
+      return (-1 !== this.methods.indexOf(command));
+    }
+
+    private function sendOptionsReq():void {
+      var req:String =
+        "OPTIONS * RTSP/1.0\r\n" +
+        "CSeq: " + (++cSeq) + "\r\n" +
+        "User-Agent: " + userAgent + "\r\n" +
+        "\r\n";
+      handle.writeUTFBytes(req);
+    }
+
     private function sendDescribeReq():void {
       var u:String = 'rtsp://' + urlParsed.host + ":" + urlParsed.port + urlParsed.urlpath;
       var req:String =
@@ -349,6 +389,10 @@
     }

     private function sendPauseReq():void {
+      if (-1 === this.supportCommand("PAUSE")) {
+        throw new Error('Pause is not supported by server.');
+      }
+
       /* NetStream must be closed here, otherwise it will think of this rtsp pause
          as a very bad connection and buffer a lot before playing again. Not
          excellent for live data. */


pieces now move quicker if they have to move further.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/rlowke/lowkemedia.git/'


Now there is a 'ComponentView' which manages the node selection/combobox for several Component Managers.

error: pathspec 'protogeni/demo/src/ComponentView.as' did not match any file(s) known to git

--- protogeni/demo/src/ComponentView.as
+++ protogeni/demo/src/ComponentView.as
@@ -0,0 +1,221 @@
+/* GENIPUBLIC-COPYRIGHT
+ * Copyright (c) 2008, 2009 University of Utah and the Flux Group.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software is hereby
+ * granted provided that (1) source code retains these copyright, permission,
+ * and disclaimer notices, and (2) redistributions including binaries
+ * reproduce the notices in supporting documentation.
+ *
+ * THE UNIVERSITY OF UTAH ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
+ * CONDITION.  THE UNIVERSITY OF UTAH DISCLAIMS ANY LIABILITY OF ANY KIND
+ * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ */
+
+package
+{
+  // This object populates and takes events from both 'select', a
+  // combo-box used to specify the component manager and 'list', a
+  // listbox with all of the nodes managed by the component manager.
+  import fl.controls.ComboBox;
+  import fl.controls.List;
+  import fl.events.ListEvent;
+  import flash.events.Event;
+
+  class ComponentView
+  {
+    public function ComponentView(newSelect : ComboBox,
+                                  newList : List,
+                                  newNodes : ActiveNodes) : void
+    {
+      select = newSelect;
+      list = newList;
+      list.allowMultipleSelection = true;
+      listStatus = new ListStatusClip();
+      list.addChild(listStatus);
+      listStatus.alpha = 0.3;
+      nodes = newNodes;
+
+      managers = new Array(
+        new ComponentManager("", "", "", "", updateList),
+/*
+        new ComponentManager("e2a9e480-aa9f-11dd-9fcd-001143e43770",
+                             "ProtoGENI",
+                             ".emulab.net",
+                             "https://myboss.myelab.testbed.emulab.net:443/protogeni/xmlrpc",
+                             updateList),
+*/
+        new ComponentManager("28a10955-aa00-11dd-ad1f-001143e453fe",
+                             "Emulab",
+                             ".emulab.net",
+                             "https://boss.emulab.net:443/protogeni/xmlrpc/",
+                             updateList)/*,
+        new ComponentManager("be300821-ecb7-11dd-a0f8-001143e43ff3",
+                             "gtwelab",
+                             ".emulab.net",
+                             "https://myboss.emulab.geni.emulab.net:443/protogeni/xmlrpc",
+                             updateList),
+        new ComponentManager("b83b47be-e7f0-11dd-848b-0013468d3dc8",
+                             "Kentucky",
+                             ".uky.emulab.net",
+                             "https://www.uky.emulab.net/protogeni/xmlrpc",
+                             updateList),
+        new ComponentManager("f38e8571-f7af-11dd-ab88-00304868a4be",
+                             "Wisconsin",
+                             ".schooner.wail.wisc.edu",
+                             "https://www.schooner.wail.wisc.edu/protogeni/xmlrpc",
+                             updateList),
+        new ComponentManager("", "CMU", ".cmcl.cs.cmu.edu",
+                             "https://boss.cmcl.cs.cmu.edu/protogeni/xmlrpc",
+                             updateList)*/);
+      select.removeAll();
+      select.selectedIndex = 0;
+      select.rowCount = 4;
+
+      var i : int = 0;
+      for (; i < managers.length; ++i)
+      {
+        select.addItem(new ListItem(managers[i].getName(), managers[i]));
+      }
+
+      managers[0].setState(ComponentManager.NORMAL);
+
+      list.addEventListener(ListEvent.ITEM_CLICK, clickItem);
+      list.addEventListener(Event.CHANGE, changeItem);
+      select.addEventListener(Event.CHANGE, changeComponent);
+
+      updateList();
+    }
+
+    public function cleanup() : void
+    {
+      list.removeEventListener(ListEvent.ITEM_CLICK, clickItem);
+      list.removeEventListener(Event.CHANGE, changeItem);
+      select.removeEventListener(Event.CHANGE, changeComponent);
+      listStatus.parent.removeChild(listStatus);
+    }
+
+    public function getManagers() : Array
+    {
+      return managers;
+    }
+
+/*
+    public function getTicket(index : int) : String
+    {
+      return tickets[index];
+    }
+
+    public function setTicket(index : int, value : String) : void
+    {
+      tickets[index] = value;
+    }
+
+    public function getUrl(index : int) : String
+    {
+      return cmUrls[index];
+    }
+*/
+/*
+    public function getCmCount() : int
+    {
+      return managers.length;
+    }
+*/
+/*
+    public function removeNode(cm : ComponentManager, nodeIndex : int) : void
+    {
+      cm.removeUsed(nodeIndex);
+      if (cm == select.selectedItem.data)
+      {
+        updateList();
+      }
+    }
+*/
+    function clickItem(event : ListEvent) : void
+    {
+      var cm : ComponentManager = managers[select.selectedIndex];
+      if (! cm.isUsed(event.index))
+      {
+        var component = cm.getComponent(event.index);
+        nodes.addNode(component, cm, event.index,
+                      select.stage.mouseX, select.stage.mouseY);
+        cm.addUsed(event.index);
+      }
+      list.selectedIndices = cm.getUsed();
+    }
+
+    function changeItem(event : Event) : void
+    {
+      list.selectedIndices = managers[select.selectedIndex].getUsed();
+    }
+
+    function changeComponent(event : Event) : void
+    {
+      updateList();
+    }
+
+    function updateList() : void
+    {
+      var cm = managers[select.selectedIndex];
+      list.removeAll();
+      list.clearSelection();
+      var i : int = 0;
+      for (; i < cm.getComponentCount(); ++i)
+      {
+        list.addItem(new ListItem(cm.getComponent(i).name, "NodeNone"));
+      }
+      list.selectedIndices = cm.getUsed();
+      if (cm.getState() == ComponentManager.NORMAL)
+      {
+        listStatus.visible = false;
+      }
+      else if (cm.getState() == ComponentManager.LOADING)
+      {
+        listStatus.visible = true;
+        listStatus.text.text = "Loading";
+        listStatus.text.backgroundColor = 0x00ff00;
+      }
+      else
+      {
+        listStatus.visible = true;
+        listStatus.text.text = "Failed";
+        listStatus.text.backgroundColor = 0xff0000;
+      }
+    }
+/*
+    public function succeedResources(cm : ComponentManager, str : String)
+    {
+      cm.populateNodes(str);
+      updateList();
+    }
+
+    public function failResources(cm : ComponentManager)
+    {
+      cm.setState(FAILED);
+      updateList();
+    }
+*/
+    var select : ComboBox;
+    var list : List;
+    var listStatus : ListStatusClip;
+    var nodes : ActiveNodes;
+    // The ComponentManagers
+    var managers : Array;
+/*
+    // An array of arrays. Outer == CM, inner == Component
+    var components : Array;
+
+    var used : Array;
+    var tickets : Array;
+    var states : Array;
+*/
+/*
+    public static var hostName : Array = new Array("",
+    public static var cmNames : Array = new Array("", ,
+    static var cmUrls : Array =
+    static var cmUuids : Array =
+    static var cmResults : Array =
+*/
+  }
+}


Bug 723461: regression test (r pending=dschaffe).

error: pathspec 'test/acceptance/regress/bug_723461.as' did not match any file(s) known to git

--- test/acceptance/regress/bug_723461.as
+++ test/acceptance/regress/bug_723461.as
@@ -0,0 +1,116 @@
+/* ***** BEGIN LICENSE BLOCK *****
+* Version: MPL 1.1/GPL 2.0/LGPL 2.1
+*
+* The contents of this file are subject to the Mozilla Public License Version
+* 1.1 (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+* http://www.mozilla.org/MPL/
+*
+* Software distributed under the License is distributed on an "AS IS" basis,
+* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+* for the specific language governing rights and limitations under the
+* License.
+*
+* The Original Code is [Open Source Virtual Machine.].
+*
+* The Initial Developer of the Original Code is
+* Adobe System Incorporated.
+* Portions created by the Initial Developer are Copyright (C) 2012
+* the Initial Developer. All Rights Reserved.
+*
+* Contributor(s):
+*   Adobe AS3 Team
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU General Public License Version 2 or later (the "GPL"), or
+* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+* in which case the provisions of the GPL or the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of either the GPL or the LGPL, and not to allow others to
+* use your version of this file under the terms of the MPL, indicate your
+* decision by deleting the provisions above and replace them with the notice
+* and other provisions required by the GPL or the LGPL. If you do not delete
+* the provisions above, a recipient may use your version of this file under
+* the terms of any one of the MPL, the GPL or the LGPL.
+*
+* ***** END LICENSE BLOCK ***** */
+
+var SECTION = "regress_723461";
+var VERSION = "AS3";
+var TITLE   = "ByteArray readUTFBytes does not properly update position when it starts at a UTF8 BOM";
+var bug = "723461";
+
+startTest();
+writeHeaderToLog(SECTION + " " + TITLE);
+
+import flash.utils.ByteArray;
+
+function testCore(starting) {
+    var results = {};
+    var starting = 0;
+    var b:ByteArray = new ByteArray();
+    b.position = starting;
+    b.writeUTFBytes('\ufeff1234');
+    var posPostWrite = b.position;
+    var len = b.length;
+    b.writeUTFBytes('5678');
+    var s1;
+    var s2;
+    var posPostRead;
+
+    b.position = starting;
+    try {
+        s1 = b.readUTFBytes(len);
+    } catch (e) { }
+    posPostRead = b.position;
+
+    try {
+        s2 = b.readUTFBytes(4);
+    } catch (e) { }
+
+
+    results.s1 = s1;
+    results.s2 = s2;
+    results.posPostWrite = posPostWrite;
+    results.posPostRead = posPostRead;
+
+    return results;
+}
+
+function testStartAt0() {
+    var results = testCore(0);
+
+    AddTestCase("at 0 result string skips BOM",
+                "1234",
+                results.s1);
+
+    AddTestCase("at 0 result subsequent reads correct",
+                "5678",
+                results.s2);
+
+    AddTestCase("at 0 position updated correctly",
+                results.posPostWrite,
+                results.posPostRead);
+}
+
+function testStartAt10() {
+    var results = testCore(10);
+
+    AddTestCase("at 10 result string skips BOM",
+                "1234",
+                results.s1);
+
+    AddTestCase("at 10 result subsequent reads correct",
+                "5678",
+                results.s2);
+
+    AddTestCase("at 10 position updated correctly",
+                results.posPostWrite,
+                results.posPostRead);
+}
+
+testStartAt0();
+
+testStartAt10();
+
+test();


adding structs directory

adding structs directory

error: pathspec 'as/org/osflash/asjs/parser/structs/ParserStruct.as' did not match any file(s) known to git

--- as/org/osflash/asjs/parser/structs/ParserStruct.as
+++ as/org/osflash/asjs/parser/structs/ParserStruct.as
@@ -0,0 +1,22 @@
+package org.osflash.asjs.parser.structs {
+
+    import org.osflash.asjs.parser.ParserState;
+
+    public class ParserStruct {
+
+        public var remaining:ParserState;
+        public var match:String;
+        public var ast:String;
+
+        public function ParserStruct(r:ParserState, m:String, a:String){
+
+            remaining = r;
+            match = m;
+            ast = a;
+
+        }
+
+    }
+
+}
+


Utility method for creating a menu item that will post controller commands.

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4194 542714f4-19e9-0310-aa3c-eee0fc999fb1

error: pathspec 'src/as/com/threerings/util/MenuUtil.as' did not match any file(s) known to git

--- src/as/com/threerings/util/MenuUtil.as
+++ src/as/com/threerings/util/MenuUtil.as
@@ -0,0 +1,29 @@
+package com.threerings.util {
+
+import flash.ui.ContextMenuItem;
+import flash.events.ContextMenuEvent;
+
+import com.threerings.events.ControllerEvent;
+
+/**
+ */
+public class MenuUtil
+{
+    /**
+     * Create a menu item that will submit a controller command when selected.
+     */
+    public static function createControllerMenuItem (
+            caption :String, cmd :String, arg :Object = null,
+            separatorBefore :Boolean = false, enabled :Boolean = true,
+            visible :Boolean = true) :ContextMenuItem
+    {
+        var item :ContextMenuItem =
+            new ContextMenuItem(caption, separatorBefore, enabled, visible);
+        item.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
+            function (event :ContextMenuEvent) :void {
+                event.mouseTarget.dispatchEvent(new ControllerEvent(cmd, arg));
+            });
+        return item;
+    }
+}
+}


exists

exists

error: pathspec 'source/com/kemsky/filters/exists.as' did not match any file(s) known to git

--- source/com/kemsky/filters/exists.as
+++ source/com/kemsky/filters/exists.as
@@ -0,0 +1,18 @@
+package com.kemsky.filters
+{
+    import com.kemsky.support.toValue;
+
+    //todo test
+    public function exists(val:*):Function
+    {
+        return function (item:*):Boolean
+        {
+            var value:* = toValue(item, val);
+
+            //not undefined
+            //not null
+            //not NaN
+            return value !== undefined && value != null && (value is Number ? value == value : true);
+        };
+    }
+}


Inlined buildChannelListBasedOnOperation

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/pubnub/flash.git/'


for some reason the compiler started to care about whether this file existed or not

error: pathspec 'compiler.jx.tests/test-files/flexjs/files/StockDataJSONItemConverter.as' did not match any file(s) known to git

--- compiler.jx.tests/test-files/flexjs/files/StockDataJSONItemConverter.as
+++ compiler.jx.tests/test-files/flexjs/files/StockDataJSONItemConverter.as
@@ -0,0 +1,40 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package
+{
+    import org.apache.flex.collections.converters.JSONItemConverter;
+
+    public class StockDataJSONItemConverter extends JSONItemConverter
+    {
+        public function StockDataJSONItemConverter()
+        {
+            super();
+        }
+
+        override public function convertItem(data:String):Object
+        {
+            var obj:Object = super.convertItem(data);
+			if (obj["query"]["count"] == 0)
+				return "No Data";
+
+			obj = obj["query"]["results"]["quote"];
+			return obj;
+        }
+    }
+}


Transferred from the Adobe Open Source Subversion Repository

Revision: 5099
Author:   gauravj@adobe.com
Date:     2009-02-27T03:43:02.639229Z

Log Message:
-----------
Update action script files with asdoc version tags.

QE Notes: None.
Doc Notes: Please review and update as necessary.
tests: checkintests

Modified Paths:
--------------
147 paths were modified

git-svn-id: 7146a936b37d36e51ea0d66f8acbe0b8281bc7b7@1273933 13f79535-47bb-0310-9956-ffa450edef68

--- frameworks/projects/rpc/src/mx/messaging/messages/ErrorMessage.as
+++ frameworks/projects/rpc/src/mx/messaging/messages/ErrorMessage.as
@@ -18,6 +18,12 @@
  *  The ErrorMessage class is used to report errors within the messaging system.
  *  An error message only occurs in response to a message sent within the
  *  system.
+ *
+ *  @langversion 3.0
+ *  @playerversion Flash 9
+ *  @playerversion AIR 1.1
+ *  @productversion BlazeDS 4
+ *  @productversion LCDS 3
  */
 public class ErrorMessage extends AcknowledgeMessage
 {
@@ -30,6 +36,12 @@
     /**
      *  If a message may not have been delivered, the faultCode will
      *  contain this constant.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public static const MESSAGE_DELIVERY_IN_DOUBT:String = "Client.Error.DeliveryInDoubt";

@@ -37,6 +49,12 @@
      *  Header name for the retryable hint header.
      *  This is used to indicate that the operation that generated the error
      *  may be retryable rather than fatal.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public static const RETRYABLE_HINT_HEADER:String = "DSRetryableErrorHint";

@@ -48,6 +66,12 @@

     /**
      *  Constructs an ErrorMessage instance.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public function ErrorMessage()
     {
@@ -65,17 +89,35 @@
      *  This value typically follows the convention of
      *  "[outer_context].[inner_context].[issue]".
      *  For example: "Channel.Connect.Failed", "Server.Call.Failed", etc.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public var faultCode:String;

     /**
      *  A simple description of the error.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public var faultString:String;

     /**
      *  Detailed description of what caused the error.
      *  This is typically a stack trace from the remote destination.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public var faultDetail:String;

@@ -83,12 +125,24 @@
      *  Should a root cause exist for the error, this property contains those details.
      *  This may be an ErrorMessage, a NetStatusEvent info Object, or an underlying
      *  Flash error event: ErrorEvent, IOErrorEvent, or SecurityErrorEvent.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public var rootCause:Object;

     /**
      * Extended data that the remote destination has chosen to associate
      * with this error to facilitate custom error processing on the client.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 9
+     *  @playerversion AIR 1.1
+     *  @productversion BlazeDS 4
+     *  @productversion LCDS 3
      */
     public var extendedData:Object;


Broke out code into nicely named functions.  Identified jump bug, it doesn't need to be fixed as it exists in the fakeJump function.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/c0d3m0nk3y/Konata.git/'


Adding some more cleanup

Adding some more cleanup

error: pathspec 'flex/src/org/asmine/core/global/spyOn.as' did not match any file(s) known to git

--- flex/src/org/asmine/core/global/spyOn.as
+++ flex/src/org/asmine/core/global/spyOn.as
@@ -0,0 +1,8 @@
+package org.asmine.core.global
+{
+	import org.asmine.core.jasmine;
+
+	public const spyOn = function(obj, methodName) {
+		return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+	};
+}


Testing commiting

Testing commiting

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/nambread/curbstomp.git/'


improving complex log messages readability


update calculation for demand mode get time

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/OnCircle/vpaid-wrapper.git/'


Singleton inforcement on HTTPVideoProvider.initNetConnection and added close to initNetStream.

fatal: unable to access 'https://github.com/OnCircle/vpaid-wrapper.git/': The requested URL returned error: 403


exposes context on mxml ContextBuilderTag

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/eric-stanley/robotlegs-framework.git/'


Removed dead variable _locked from ViewProcessorMapping