/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : uint;
        /** Level Name. **/
        public var name : String;
        /** level index (sorted by bitrate) **/
        public var index : int = 0;
        /** level index (manifest order) **/
        public var manifest_index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). (it is a vector so that we can store redundant streams in same level) **/
        public var urls : Vector.<String>;
        // index of used url (non 0 if we switch to a redundant stream)
        private var _redundantStreamId : int = 0;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

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

        public function get url() : String {
            return urls[_redundantStreamId];
        }

        public function get redundantStreamsNb() : int {
            if(urls && urls.length) {
                return urls.length-1;
            } else {
                return 0;
            }
        }

        public function get redundantStreamId() : int {
            return _redundantStreamId;
        }

        // when switching to a redundant stream, reset fragments. they will be retrieved from new playlist
        public function set redundantStreamId(id) : void {
            if(id < urls.length && id != _redundantStreamId) {
                _redundantStreamId = id;
                fragments = new Vector.<Fragment>();
            }
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : uint;
        /** Level Name. **/
        public var name : String;
        /** level index (sorted by bitrate) **/
        public var index : int = 0;
        /** level index (manifest order) **/
        public var manifest_index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). (it is a vector so that we can store redundant streams in same level) **/
        public var urls : Vector.<String>;
        // index of used url (non 0 if we switch to a redundant stream)
        private var _redundantStreamId : int = 0;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

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

        public function get url() : String {
            return urls[_redundantStreamId];
        }

        public function get redundantStreamsNb() : int {
            if(urls && urls.length) {
                return urls.length-1;
            } else {
                return 0;
            }
        }

        public function get redundantStreamId() : int {
            return _redundantStreamId;
        }

        // when switching to a redundant stream, reset fragments. they will be retrieved from new playlist
        public function set redundantStreamId(id : int) : void {
            if(id < urls.length && id != _redundantStreamId) {
                _redundantStreamId = id;
                fragments = new Vector.<Fragment>();
            }
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fix redundantStreamId param type

[Mangui.Level] Fix redundantStreamId param type


package goplayer
{
  public class StreamioAPI implements MovieEventReporter
  {
    private static const VERSION : String = "/v1"

    private var baseURL : String
    private var http : HTTP
    private var trackerID : String

    public function StreamioAPI
      (baseURL : String, http : HTTP, trackerID : String)
    {
      this.baseURL = baseURL
      this.http = http
      this.trackerID = trackerID
    }

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

    public function fetchMovie
      (id : String, handler : MovieHandler) : void
    { fetch(getMoviePath(id), new MovieJSONHandler(handler)) }

    public function reportMovieViewed(movieID : String) : void
    { reportMovieEvent(movieID, "views", {}) }

    public function reportMoviePlayed(movieID : String) : void
    { reportMovieEvent(movieID, "plays", {}) }

    public function reportMovieHeatmapData
      (movieID : String, time : Number) : void
    { reportMovieEvent(movieID, "heat", { time: time }) }

    private function reportMovieEvent
      (movieID : String, event : String, parameters : Object) : void
    { post(statsPath, getStatsParameters(movieID, event, parameters)) }

    private function getStatsParameters
      (movieID : String, event : String, parameters : Object) : Object
    {
      const result : Object = new Object

      result.tracker_id = trackerID
      result.movie_id = movieID
      result.event = event

      for (var name : String in parameters)
        result[name] = parameters[name]

      return result
    }

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

    private function getMoviePath(id : String) : String
    { return "/videos/" + id + "/public_show.json" }

    private function get statsPath() : String
    { return "/stats" }

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

    private function fetch(path : String, handler : JSONHandler) : void
    { http.fetchJSON(getURL(path), handler) }

    private function post(path : String, parameters : Object) : void
    { http.post(getURL(path), parameters, new NullHTTPHandler) }

    private function getURL(path : String) : URL
    { return URL.parse(baseURL + VERSION + path) }
  }
}

import goplayer.*

class MovieJSONHandler implements JSONHandler
{
  private var handler : MovieHandler

  public function MovieJSONHandler(handler : MovieHandler)
  { this.handler = handler }

  public function handleJSON(json : Object) : void
  { handler.handleMovie(new StreamioMovie(json)) }
}

class NullHTTPHandler implements HTTPResponseHandler
{ public function handleHTTPResponse(text : String) : void {} }

package goplayer
{
  public class StreamioAPI implements MovieEventReporter
  {
    private static const VERSION : String = "/v1"

    private var baseURL : String
    private var http : HTTP
    private var trackerID : String

    public function StreamioAPI
      (baseURL : String, http : HTTP, trackerID : String)
    {
      this.baseURL = baseURL
      this.http = http
      this.trackerID = trackerID
    }

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

    public function fetchMovie
      (id : String, handler : MovieHandler) : void
    { fetch(getMoviePath(id), new MovieJSONHandler(handler)) }

    public function reportMovieViewed(movieID : String) : void
    { reportMovieEvent(movieID, "views", {}) }

    public function reportMoviePlayed(movieID : String) : void
    { reportMovieEvent(movieID, "plays", {}) }

    public function reportMovieHeatmapData
      (movieID : String, time : Number) : void
    { reportMovieEvent(movieID, "heat", { time: time }) }

    private function reportMovieEvent
      (movieID : String, event : String, parameters : Object) : void
    {
      if (trackerID != null && trackerID != "")
        post(statsPath, getStatsParameters(movieID, event, parameters))
    }

    private function getStatsParameters
      (movieID : String, event : String, parameters : Object) : Object
    {
      const result : Object = new Object

      result.tracker_id = trackerID
      result.movie_id = movieID
      result.event = event

      for (var name : String in parameters)
        result[name] = parameters[name]

      return result
    }

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

    private function getMoviePath(id : String) : String
    { return "/videos/" + id + "/public_show.json" }

    private function get statsPath() : String
    { return "/stats" }

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

    private function fetch(path : String, handler : JSONHandler) : void
    { http.fetchJSON(getURL(path), handler) }

    private function post(path : String, parameters : Object) : void
    { http.post(getURL(path), parameters, new NullHTTPHandler) }

    private function getURL(path : String) : URL
    { return URL.parse(baseURL + VERSION + path) }
  }
}

import goplayer.*

class MovieJSONHandler implements JSONHandler
{
  private var handler : MovieHandler

  public function MovieJSONHandler(handler : MovieHandler)
  { this.handler = handler }

  public function handleJSON(json : Object) : void
  { handler.handleMovie(new StreamioMovie(json)) }
}

class NullHTTPHandler implements HTTPResponseHandler
{ public function handleHTTPResponse(text : String) : void {} }

Disable tracking when `streamio:tracker' is empty.


package goog.events {

import goog.Disposable;

/**
 * An implementation of {@code goog.events.Listenable} with full W3C
 * EventTarget-like support (capture/bubble mechanism, stopping event
 * propagation, preventing default actions).
 *
 * You may subclass this class to turn your class into a Listenable.
 *
 * Unless propagation is stopped, an event dispatched by an
 * EventTarget will bubble to the parent returned by
 * {@code getParentEventTarget}. To set the parent, call
 * {@code setParentEventTarget}. Subclasses that don't support
 * changing the parent can override the setter to throw an error.
 *
 * Example usage:
 *

 * var source = new goog.events.EventTarget();
 * function handleEvent(e) {
 * alert('Type: ' + e.type + '; Target: ' + e.target);
 * }
 * source.listen('foo', handleEvent);
 * // Or: goog.events.listen(source, 'foo', handleEvent);
 * ...
 * source.dispatchEvent('foo');  // will call handleEvent
 * ...
 * source.unlisten('foo', handleEvent);
 * // Or: goog.events.unlisten(source, 'foo', handleEvent);
 *

 *
 * @see [eventtarget]
 */
public class EventTarget extends Disposable implements Listenable {

    /**
     * Marks a given class (constructor) as an implementation of
     * Listenable, do that we can query that fact at runtime. The class
     * must have already implemented the interface.
     *
     * @param cls [Function] The class constructor. The corresponding class must have already implemented the interface.
     * @see [listenable]
     */
    public static function addImplementation(cls:Class /* Function */):void {}

    /**
     * @param obj [(Object|null)] The object to check.
     * @see [listenable]
     * @returns {boolean} Whether a given instance implements Listenable. The class/superclass of the instance must call addImplementation.
     */
    public static function isImplementedBy(obj:Object):Boolean {return false}

    /**
     * An implementation of {@code goog.events.Listenable} with full W3C
     * EventTarget-like support (capture/bubble mechanism, stopping event
     * propagation, preventing default actions).
     *
     * You may subclass this class to turn your class into a Listenable.
     *
     * Unless propagation is stopped, an event dispatched by an
     * EventTarget will bubble to the parent returned by
     * {@code getParentEventTarget}. To set the parent, call
     * {@code setParentEventTarget}. Subclasses that don't support
     * changing the parent can override the setter to throw an error.
     *
     * Example usage:
     *

     * var source = new goog.events.EventTarget();
     * function handleEvent(e) {
     * alert('Type: ' + e.type + '; Target: ' + e.target);
     * }
     * source.listen('foo', handleEvent);
     * // Or: goog.events.listen(source, 'foo', handleEvent);
     * ...
     * source.dispatchEvent('foo');  // will call handleEvent
     * ...
     * source.unlisten('foo', handleEvent);
     * // Or: goog.events.unlisten(source, 'foo', handleEvent);
     *

     *
     * @see [eventtarget]
     */
    public function EventTarget() {
        super();
    }

    /**
     * @see [eventtarget]
     */
    public function removeAllListeners(type:String = null):Number { return 0 }

    /**
     * Sets the target to be used for {@code event.target} when firing
     * event. Mainly used for testing. For example, see
     * {@code goog.testing.events.mixinListenable}.
     *
     * @param target [Object] The target.
     * @see [eventtarget]
     */
    public function setTargetForTesting(target:Object):void {  }

    /**
     * Adds an event listener to the event target. The same handler can only be
     * added once per the type. Even if you add the same handler multiple times
     * using the same type then it will only be called once when the event is
     * dispatched.
     *
     * @param type [string] The type of the event to listen for.
     * @param handler [(function (?): ?|null|{handleEvent: function (?): ?})] The function to handle the event. The handler can also be an object that implements the handleEvent method which takes the event object as argument.
     * @param opt_capture [(boolean|undefined)] In DOM-compliant browsers, this determines whether the listener is fired during the capture or bubble phase of the event.
     * @param opt_handlerScope [(Object|null|undefined)] Object in whose scope to call the listener.
     * @see [eventtarget]
     */
    public function addEventListener(type:String, handler:Object, opt_capture:Boolean = false, opt_handlerScope:Object = null):void {  }

    /**
     * Returns the parent of this event target to use for bubbling.
     *
     * @see [eventtarget]
     * @returns {(goog.events.EventTarget|null)} The parent EventTarget or null if there is no parent.
     */
    public function getParentEventTarget():EventTarget {  return null; }

    /**
     * Removes an event listener from the event target. The handler must be the
     * same object as the one added. If the handler has not been added then
     * nothing is done.
     *
     * @param type [string] The type of the event to listen for.
     * @param handler [(function (?): ?|null|{handleEvent: function (?): ?})] The function to handle the event. The handler can also be an object that implements the handleEvent method which takes the event object as argument.
     * @param opt_capture [(boolean|undefined)] In DOM-compliant browsers, this determines whether the listener is fired during the capture or bubble phase of the event.
     * @param opt_handlerScope [(Object|null|undefined)] Object in whose scope to call the listener.
     * @see [eventtarget]
     */
    public function removeEventListener(type:String, handler:Object, opt_capture:Boolean = false, opt_handlerScope:Object = null):void {  }

    /**
     * Asserts that the event target instance is initialized properly.
     *
     * @see [eventtarget]
     */
    public function assertInitialized_():void {  }

    /**
     * @see [eventtarget]
     */
    public function unlisten(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):void {  }

    /**
     * @see [eventtarget]
     */
    public function fireListeners(type:Object, capture:Boolean, eventObject:Object):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function listenOnce(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):ListenableKey { return null }

    /**
     * @see [eventtarget]
     */
    public function unlistenByKey(key:ListenableKey):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function dispatchEvent(e:Object):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function hasListener(opt_type:Object = null, opt_capture:Boolean = false):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function getListener(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):goog.events.ListenableKey { return null }

    /**
     * @see [eventtarget]
     */
    public function listen(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):goog.events.ListenableKey { return null }

    /**
     * Dispatches the given event on the ancestorsTree.
     *
     * @param target [Object] The target to dispatch on.
     * @param e [(Object|goog.events.Event|null|string)] The event object.
     * @param opt_ancestorsTree [(Array<(goog.events.Listenable|null)>|null|undefined)] The ancestors tree of the target, in reverse order from the closest ancestor to the root event target. May be null if the target has no ancestor.
     * @see [eventtarget]
     * @returns {boolean} If anyone called preventDefault on the event object (or if any of the listeners returns false) this will also return false.
     */
    public static function dispatchEventInternal_(target:Object, e:Object, opt_ancestorsTree:Array = null):Boolean {  return false }

    /**
     * @see [eventtarget]
     */
    public function getListeners(type:Object, capture:Boolean):Array { return null }

    /**
     * Sets the parent of this event target to use for capture/bubble
     * mechanism.
     *
     * @param parent [(goog.events.EventTarget|null)] Parent listenable (null if none).
     * @see [eventtarget]
     */
    public function setParentEventTarget(parent:EventTarget):void {  }

}
}

package goog.events {

import goog.Disposable;

/**
 * An implementation of {@code goog.events.Listenable} with full W3C
 * EventTarget-like support (capture/bubble mechanism, stopping event
 * propagation, preventing default actions).
 *
 * You may subclass this class to turn your class into a Listenable.
 *
 * Unless propagation is stopped, an event dispatched by an
 * EventTarget will bubble to the parent returned by
 * {@code getParentEventTarget}. To set the parent, call
 * {@code setParentEventTarget}. Subclasses that don't support
 * changing the parent can override the setter to throw an error.
 *
 * Example usage:
 *

 * var source = new goog.events.EventTarget();
 * function handleEvent(e) {
 * alert('Type: ' + e.type + '; Target: ' + e.target);
 * }
 * source.listen('foo', handleEvent);
 * // Or: goog.events.listen(source, 'foo', handleEvent);
 * ...
 * source.dispatchEvent('foo');  // will call handleEvent
 * ...
 * source.unlisten('foo', handleEvent);
 * // Or: goog.events.unlisten(source, 'foo', handleEvent);
 *

 *
 * @see [eventtarget]
 */
public class EventTarget extends Disposable implements Listenable {

    /**
     * Marks a given class (constructor) as an implementation of
     * Listenable, do that we can query that fact at runtime. The class
     * must have already implemented the interface.
     *
     * @param cls [Function] The class constructor. The corresponding class must have already implemented the interface.
     * @see [listenable]
     */
    public static function addImplementation(cls:Function):void {}

    /**
     * @param obj [(Object|null)] The object to check.
     * @see [listenable]
     * @returns {boolean} Whether a given instance implements Listenable. The class/superclass of the instance must call addImplementation.
     */
    public static function isImplementedBy(obj:Object):Boolean {return false}

    /**
     * An implementation of {@code goog.events.Listenable} with full W3C
     * EventTarget-like support (capture/bubble mechanism, stopping event
     * propagation, preventing default actions).
     *
     * You may subclass this class to turn your class into a Listenable.
     *
     * Unless propagation is stopped, an event dispatched by an
     * EventTarget will bubble to the parent returned by
     * {@code getParentEventTarget}. To set the parent, call
     * {@code setParentEventTarget}. Subclasses that don't support
     * changing the parent can override the setter to throw an error.
     *
     * Example usage:
     *

     * var source = new goog.events.EventTarget();
     * function handleEvent(e) {
     * alert('Type: ' + e.type + '; Target: ' + e.target);
     * }
     * source.listen('foo', handleEvent);
     * // Or: goog.events.listen(source, 'foo', handleEvent);
     * ...
     * source.dispatchEvent('foo');  // will call handleEvent
     * ...
     * source.unlisten('foo', handleEvent);
     * // Or: goog.events.unlisten(source, 'foo', handleEvent);
     *

     *
     * @see [eventtarget]
     */
    public function EventTarget() {
        super();
    }

    /**
     * @see [eventtarget]
     */
    public function removeAllListeners(type:String = null):Number { return 0 }

    /**
     * Sets the target to be used for {@code event.target} when firing
     * event. Mainly used for testing. For example, see
     * {@code goog.testing.events.mixinListenable}.
     *
     * @param target [Object] The target.
     * @see [eventtarget]
     */
    public function setTargetForTesting(target:Object):void {  }

    /**
     * Adds an event listener to the event target. The same handler can only be
     * added once per the type. Even if you add the same handler multiple times
     * using the same type then it will only be called once when the event is
     * dispatched.
     *
     * @param type [string] The type of the event to listen for.
     * @param handler [(function (?): ?|null|{handleEvent: function (?): ?})] The function to handle the event. The handler can also be an object that implements the handleEvent method which takes the event object as argument.
     * @param opt_capture [(boolean|undefined)] In DOM-compliant browsers, this determines whether the listener is fired during the capture or bubble phase of the event.
     * @param opt_handlerScope [(Object|null|undefined)] Object in whose scope to call the listener.
     * @see [eventtarget]
     */
    public function addEventListener(type:String, handler:Object, opt_capture:Boolean = false, opt_handlerScope:Object = null):void {  }

    /**
     * Returns the parent of this event target to use for bubbling.
     *
     * @see [eventtarget]
     * @returns {(goog.events.EventTarget|null)} The parent EventTarget or null if there is no parent.
     */
    public function getParentEventTarget():EventTarget {  return null; }

    /**
     * Removes an event listener from the event target. The handler must be the
     * same object as the one added. If the handler has not been added then
     * nothing is done.
     *
     * @param type [string] The type of the event to listen for.
     * @param handler [(function (?): ?|null|{handleEvent: function (?): ?})] The function to handle the event. The handler can also be an object that implements the handleEvent method which takes the event object as argument.
     * @param opt_capture [(boolean|undefined)] In DOM-compliant browsers, this determines whether the listener is fired during the capture or bubble phase of the event.
     * @param opt_handlerScope [(Object|null|undefined)] Object in whose scope to call the listener.
     * @see [eventtarget]
     */
    public function removeEventListener(type:String, handler:Object, opt_capture:Boolean = false, opt_handlerScope:Object = null):void {  }

    /**
     * Asserts that the event target instance is initialized properly.
     *
     * @see [eventtarget]
     */
    public function assertInitialized_():void {  }

    /**
     * @see [eventtarget]
     */
    public function unlisten(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):void {  }

    /**
     * @see [eventtarget]
     */
    public function fireListeners(type:Object, capture:Boolean, eventObject:Object):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function listenOnce(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):ListenableKey { return null }

    /**
     * @see [eventtarget]
     */
    public function unlistenByKey(key:ListenableKey):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function dispatchEvent(e:Object):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function hasListener(opt_type:Object = null, opt_capture:Boolean = false):Boolean { return false }

    /**
     * @see [eventtarget]
     */
    public function getListener(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):goog.events.ListenableKey { return null }

    /**
     * @see [eventtarget]
     */
    public function listen(type:Object, listener:Object, opt_useCapture:Boolean = false, opt_listenerScope:Object = null):goog.events.ListenableKey { return null }

    /**
     * Dispatches the given event on the ancestorsTree.
     *
     * @param target [Object] The target to dispatch on.
     * @param e [(Object|goog.events.Event|null|string)] The event object.
     * @param opt_ancestorsTree [(Array<(goog.events.Listenable|null)>|null|undefined)] The ancestors tree of the target, in reverse order from the closest ancestor to the root event target. May be null if the target has no ancestor.
     * @see [eventtarget]
     * @returns {boolean} If anyone called preventDefault on the event object (or if any of the listeners returns false) this will also return false.
     */
    public static function dispatchEventInternal_(target:Object, e:Object, opt_ancestorsTree:Array = null):Boolean {  return false }

    /**
     * @see [eventtarget]
     */
    public function getListeners(type:Object, capture:Boolean):Array { return null }

    /**
     * Sets the parent of this event target to use for capture/bubble
     * mechanism.
     *
     * @param parent [(goog.events.EventTarget|null)] Parent listenable (null if none).
     * @see [eventtarget]
     */
    public function setParentEventTarget(parent:EventTarget):void {  }

}
}

switch back to Function instead of Class


package test {
  class Literal {
    function test() {
      call(1, 1, 200, 200, -1, -50, 32767, 32768, -32760, -500000);
    }
  }

  class Arithmetics {
    function b() {
      var a:int = 0;
      var b:int;
      b = (a += 1);
      return (b -= (a += 1));
    }

    function a() {
      var a:int = 0;
      var b:int = ++a;
      var c:int = a++;
      return a;
    }
  }

  class Logic {
    function P_a_or_b_p_and_P_c_and_d_p_or_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      var v:Boolean = (a || b) && (c && d) || e;
      away();
      return v;
    }

    function a_or_b(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      if(a || b) {
        yes();
      } else {
        no();
      }
    }

    function a_and_b_and_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a && (b && c);
    }

    function P_a_and_b_p_and_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return (a && b) && c;
    }

    function a_and_b(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a && b;
    }
  }

  class Ternary {
    function hardcore(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      if(((a && b) ? (c || d) && e : b) && (a || b)) {
        return pow();
      } else {
        weeee();
      }
      duh();
    }
    function q(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return ((a ? b : c) ? (b ? c : d) : (c ? d : e));
    }
    function a_I_b_E_c_I_d_E_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a ? b : (c ? d : e);
    }
    function a_I_b_I_c_E_d_E_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a ? (b ? c : d) : e;
    }
    function a_I_b_E_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return (1 > 2) ? b : c;
    }
  }

  class Conditionals {
    function elsif(param1:Boolean, param2:Boolean) : Boolean {
      if(param1) {
        return true;
      } else if(param2) {
        return false;
      } else {
        return null;
      }
    }

    function els(param1:Object, param2:String, param3:String) : Boolean {
      var _loc_4:Object = null;
      if (param1 != null)
      {
          _loc_4 = param1[param2];
          if (_loc_4 != null)
          {
              if (_loc_4[param3] != null)
              {
                  return true;
              }
              a();
          }
          b();
      }
      heys();
      return false;
    }

    function els2(param1:Object, param2:String, param3:String) : Boolean {
      var _loc_4:Object = null;
      if (param1 != null)
      {
          _loc_4 = param1[param2];
          if (_loc_4 != null)
          {
              if (_loc_4[param3] != null)
              {
                  return true;
              }
          }
      }
      heys();
      return false;
    }

    function b(a:Boolean) {
      if(a)
        return foo();
      else
        return 1;
    }

    function q(a:Boolean, b:Boolean) {
      if(b) {
        if(a) {
          foo();
        }
      }
      baz();
    }

    function a(a:Boolean) {
      baz();
      if(a) {
        foge();
      } else {
        huga();
      }
      piyo();
    }

    function nested(a:Boolean, b:Boolean) {
      if(a) {
        foo();
      } else {
        if(b) {
          bar();
        } else {
          baz();
        }
      }
    }
  }

  class Loops {
    function e(f:Boolean) {
      do { f = tttest();
      } while(f);
    }

    function d() {
      while(true) {
        pow();
        if(frak()) { a(); break; b(); }
        weee();
      }
    }

    function b() {
      weee();
      for(var q:int = 1; q > 0; q++) {
        frak();
      }
    }
  }

  class Switch {
    function improper_switch(q:int):Boolean {
      switch(q) {
      case 0x10:
        hoge();
        break;
      case "test":
        fuga();
        break;
      case 0x30:
        piyo();
        break;
      case 0x40:
        fuck();
        break;
      }
      return false;
    }

    function proper_switch(q:int):Boolean {
      switch(q) {
      case 1:
        hoge();
      break;
      case 2:
        fuga();
      break;
      case 3:
        piyo();
      break;
      case 5:
        bar();
      break;
      default:
        baz();
      break;
      }
      return false;
    }

    function fallthrough_switch(q:int):Boolean {
      switch(q) {
      case 0x10:
        hoge();
      case 0x20:
        fuga();
      break;
      case 0x30:
        piyo();
      break;
      default:
        baz();
      break;
      }
      return false;
    }
  }

  class Closures {
    function a() {
      var a:int = 1;
      var f = function(param) {
        var b:int = 10;
        return a + this.fuga() + hoge() + param;
      };
      var q = function() {
        test123();
      };
      q();
      return f;
    }
  }

  class Exceptions {
    function e() {
      if(a) {
        try {
          try {
            a();
          } catch(e) {
            b();
          }
          hoge();
        } finally {
          piyo();
        }
      }
    }
    function d() {
      if(a) {
        try {
          hoge();
        } finally {
          piyo();
        }
      }
    }
    function c() {
      try {
        hoge();
      } finally {
        piyo();
      }
    }
    function b() {
      try {
        hoge();
        throw 1;
        fuga();
      } catch(e: SecurityError) {
        piyo(e);
      }
    }
    function a() {
      try {
        hoge();
        throw 1;
        fuga();
      } catch(e: SecurityError) {
        piyo(e);
      } catch(e: Error) {
        throw e;
      }
    }
  }
}

package test {
  class Literal {
    function test() {
      call(1, 1, 200, 200, -1, -50, 32767, 32768, -32760, -500000);
    }
  }

  class Arithmetics {
    function b() {
      var a:int = 0;
      var b:int;
      b = (a += 1);
      return (b -= (a += 1));
    }

    function a() {
      var a:int = 0;
      var b:int = ++a;
      var c:int = a++;
      return a;
    }
  }

  class Logic {
    function P_a_or_b_p_and_P_c_and_d_p_or_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      var v:Boolean = (a || b) && (c && d) || e;
      away();
      return v;
    }

    function a_or_b(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      if(a || b) {
        yes();
      } else {
        no();
      }
    }

    function a_and_b_and_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a && (b && c);
    }

    function P_a_and_b_p_and_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return (a && b) && c;
    }

    function a_and_b(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a && b;
    }
  }

  class Ternary {
    function hardcore(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      if(((a && b) ? (c || d) && e : b) && (a || b)) {
        return pow();
      } else {
        weeee();
      }
      duh();
    }
    function q(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return ((a ? b : c) ? (b ? c : d) : (c ? d : e));
    }
    function a_I_b_E_c_I_d_E_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a ? b : (c ? d : e);
    }
    function a_I_b_I_c_E_d_E_e(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return a ? (b ? c : d) : e;
    }
    function a_I_b_E_c(a: Boolean, b:Boolean, c:Boolean, d:Boolean, e:Boolean) : Boolean {
      return (1 > 2) ? b : c;
    }
  }

  class Conditionals {
    function elsif(param1:Boolean, param2:Boolean) : Boolean {
      if(param1) {
        return true;
      } else if(param2) {
        return false;
      } else {
        return null;
      }
    }

    function els(param1:Object, param2:String, param3:String) : Boolean {
      var _loc_4:Object = null;
      if (param1 != null)
      {
          _loc_4 = param1[param2];
          if (_loc_4 != null)
          {
              if (_loc_4[param3] != null)
              {
                  return true;
              }
              a();
          }
          b();
      }
      heys();
      return false;
    }

    function els2(param1:Object, param2:String, param3:String) : Boolean {
      var _loc_4:Object = null;
      if (param1 != null)
      {
          _loc_4 = param1[param2];
          if (_loc_4 != null)
          {
              if (_loc_4[param3] != null)
              {
                  return true;
              }
          }
      }
      heys();
      return false;
    }

    function b(a:Boolean) {
      if(a)
        return foo();
      else
        return 1;
    }

    function q(a:Boolean, b:Boolean) {
      if(b) {
        if(a) {
          foo();
        }
      }
      baz();
    }

    function a(a:Boolean) {
      baz();
      if(a) {
        foge();
      } else {
        huga();
      }
      piyo();
    }

    function nested(a:Boolean, b:Boolean) {
      if(a) {
        foo();
      } else {
        if(b) {
          bar();
        } else {
          baz();
        }
      }
    }
  }

  class Loops {
    function e(f:Boolean) {
      do { f = tttest();
      } while(f);
    }

    function d() {
      while(true) {
        pow();
        if(frak()) { a(); break; b(); }
        weee();
      }
    }

    function b() {
      weee();
      for(var q:int = 1; q > 0; q++) {
        frak();
      }
    }

    function a() {
      var i:int = 0;
      while(i++ < 5) {
        hello();
      }
      return i;
    }
  }

  class Switch {
    function improper_switch(q:int):Boolean {
      switch(q) {
      case 0x10:
        hoge();
        break;
      case "test":
        fuga();
        break;
      case 0x30:
        piyo();
        break;
      case 0x40:
        fuck();
        break;
      }
      return false;
    }

    function proper_switch(q:int):Boolean {
      switch(q) {
      case 1:
        hoge();
      break;
      case 2:
        fuga();
      break;
      case 3:
        piyo();
      break;
      case 4:
        bar();
      break;
      default:
        baz();
      break;
      }
      return false;
    }

    function fallthrough_switch(q:int):Boolean {
      switch(q) {
      case 0x10:
        hoge();
      case 0x20:
        fuga();
      break;
      case 0x30:
        piyo();
      break;
      default:
        baz();
      break;
      }
      return false;
    }
  }

  class Closures {
    function a() {
      var a:int = 1;
      var f = function(param) {
        var b:int = 10;
        return a + this.fuga() + hoge() + param;
      };
      var q = function() {
        test123();
      };
      q();
      return f;
    }
  }

  class Exceptions {
    function e() {
      if(a) {
        try {
          try {
            a();
          } catch(e) {
            b();
          }
          hoge();
        } finally {
          piyo();
        }
      }
    }
    function d() {
      if(a) {
        try {
          hoge();
        } finally {
          piyo();
        }
      }
    }
    function c() {
      try {
        hoge();
      } finally {
        piyo();
      }
    }
    function b() {
      try {
        hoge();
        throw 1;
        fuga();
      } catch(e: SecurityError) {
        piyo(e);
      }
    }
    function a() {
      try {
        hoge();
        throw 1;
        fuga();
      } catch(e: SecurityError) {
        piyo(e);
      } catch(e: Error) {
        throw e;
      }
    }
  }
}

Adjust testcases.

Adjust testcases.


package com.axis.rtspclient {
  import com.axis.ClientEvent;
  import com.axis.ErrorManager;
  import com.axis.http.auth;
  import com.axis.http.request;
  import com.axis.http.url;
  import com.axis.Logger;
  import com.axis.rtspclient.GUID;

  import flash.events.ErrorEvent;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IOErrorEvent;
  import flash.events.ProgressEvent;
  import flash.events.SecurityErrorEvent;
  import flash.net.Socket;
  import flash.net.SecureSocket;
  import flash.utils.ByteArray;
  import flash.utils.*;

  import mx.utils.Base64Encoder;

  public class RTSPoverHTTPHandle extends EventDispatcher implements IRTSPHandle {
    private var getChannel:Socket = null;
    private var urlParsed:Object = {};
    private var sessioncookie:String = "";

    private var base64encoder:Base64Encoder;

    private var secure:Boolean;

    private var datacb:Function = null;
    private var connectcb:Function = null;

    private var authState:String = "none";
    private var authOpts:Object = {};
    private var digestNC:uint = 1;

    private var getChannelData:ByteArray;

    public function RTSPoverHTTPHandle(urlParsed:Object, secure:Boolean) {
      this.sessioncookie = GUID.create();
      this.urlParsed = urlParsed;
      this.base64encoder = new Base64Encoder();
      this.secure = secure;
    }

    private function setupSockets():void {
      getChannel = this.secure ? new SecureSocket() : new Socket();
      getChannel.timeout = 5000;
      getChannel.addEventListener(Event.CONNECT, onGetChannelConnect);
      getChannel.addEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
      getChannel.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
      getChannel.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);

      getChannelData = new ByteArray();
    }

    private function base64encode(str:String):String {
      base64encoder.reset();
      base64encoder.insertNewLines = false;
      base64encoder.encode(str);
      return base64encoder.toString();
    }

    public function writeUTFBytes(value:String):void {
      var data:String = base64encode(value);
      var authHeader:String = auth.authorizationHeader("POST", authState, authOpts, urlParsed, digestNC++);
      var socket:Socket = this.secure ? new SecureSocket() : new Socket();
      socket.timeout = 5000;
      socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
      socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);

      socket.addEventListener(Event.CONNECT, function ():void {
        socket.writeUTFBytes("POST " + urlParsed.urlpath + " HTTP/1.0\r\n");
        socket.writeUTFBytes("X-Sessioncookie: " + sessioncookie + "\r\n");
        socket.writeUTFBytes("Content-Length: 32767" + "\r\n");
        socket.writeUTFBytes("Content-Type: application/x-rtsp-tunnelled" + "\r\n");
        socket.writeUTFBytes(authHeader);
        socket.writeUTFBytes("\r\n");

        socket.writeUTFBytes(data);
        socket.flush();

        // Timeout required before close to let the data actually be written to
        // the socket. Flush appears to be asynchronous...
        setTimeout(socket.close, 5000);
      });

      socket.connect(this.urlParsed.host, this.urlParsed.port);
    }

    public function readBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void {
      getChannel.readBytes(bytes, offset, length);
    }

    public function disconnect():void {
      if (getChannel.connected) {
        getChannel.close();

        getChannel.removeEventListener(Event.CONNECT, onGetChannelConnect);
        getChannel.removeEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
        getChannel.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
        getChannel.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
      }

      /* should probably wait for close, but it doesn't seem to fire properly */
      dispatchEvent(new Event('closed'));
    }

    public function connect():void {
      setupSockets();
      Logger.log('RTSP+HTTP' + (this.secure ? 'S' : '') + 'connecting to', this.urlParsed.host + ':' + this.urlParsed.port);
      getChannel.connect(this.urlParsed.host, this.urlParsed.port);
    }

    public function reconnect():void {
      if (getChannel.connected) {
        getChannel.close();
      }
      connect();
    }

    private function onGetChannelConnect(event:Event):void {
      initializeGetChannel();
    }

    public function stop():void {
      disconnect();
    }

    private function onGetChannelData(event:ProgressEvent):void {
      var parsed:* = request.readHeaders(getChannel, getChannelData);
      if (false === parsed) {
        return;
      }

      if (401 === parsed.code) {
        Logger.log('Unauthorized using auth method: ' + authState);
        /* Unauthorized, change authState and (possibly) try again */
        authOpts = parsed.headers['www-authenticate'];
        var newAuthState:String = auth.nextMethod(authState, authOpts);
        if (authState === newAuthState) {
          ErrorManager.dispatchError(parsed.code);
          return;
        }

        Logger.log('switching http-authorization from ' + authState + ' to ' + newAuthState);
        authState = newAuthState;
        getChannelData = new ByteArray();
        getChannel.close();
        getChannel.connect(this.urlParsed.host, this.urlParsed.port);
        return;
      }

      if (200 !== parsed.code) {
        ErrorManager.dispatchError(parsed.code);
        return;
      }

      getChannel.removeEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
      getChannel.addEventListener(ProgressEvent.SOCKET_DATA, function(ev:ProgressEvent):void {
        dispatchEvent(new Event('data'));
      });

      dispatchEvent(new Event('connected'));
    }

    private function initializeGetChannel():void {
      getChannel.writeUTFBytes("GET " + urlParsed.urlpath + " HTTP/1.0\r\n");
      getChannel.writeUTFBytes("X-Sessioncookie: " +  sessioncookie + "\r\n");
      getChannel.writeUTFBytes("Accept: application/x-rtsp-tunnelled\r\n");
      getChannel.writeUTFBytes(auth.authorizationHeader("GET", authState, authOpts, urlParsed, digestNC++));
      getChannel.writeUTFBytes("\r\n");
      getChannel.flush();
    }

    private function onIOError(event:IOErrorEvent):void {
      ErrorManager.dispatchError(732, [event.text]);
      dispatchEvent(new Event('closed'));
    }

    private function onSecurityError(event:SecurityErrorEvent):void {
      ErrorManager.dispatchError(731, [event.text]);
      dispatchEvent(new Event('closed'));
    }
  }
}

package com.axis.rtspclient {
  import com.axis.ClientEvent;
  import com.axis.ErrorManager;
  import com.axis.http.auth;
  import com.axis.http.request;
  import com.axis.http.url;
  import com.axis.Logger;
  import com.axis.rtspclient.GUID;

  import flash.events.ErrorEvent;
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IOErrorEvent;
  import flash.events.ProgressEvent;
  import flash.events.SecurityErrorEvent;
  import flash.net.Socket;
  import flash.net.SecureSocket;
  import flash.utils.ByteArray;
  import flash.utils.*;

  import mx.utils.Base64Encoder;

  public class RTSPoverHTTPHandle extends EventDispatcher implements IRTSPHandle {
    private var getChannel:Socket = null;
    private var urlParsed:Object = {};
    private var sessioncookie:String = "";

    private var base64encoder:Base64Encoder;

    private var secure:Boolean;

    private var datacb:Function = null;
    private var connectcb:Function = null;

    private var authState:String = "none";
    private var authOpts:Object = {};
    private var digestNC:uint = 1;

    private var getChannelData:ByteArray;

    public function RTSPoverHTTPHandle(urlParsed:Object, secure:Boolean) {
      this.sessioncookie = GUID.create();
      this.urlParsed = urlParsed;
      this.base64encoder = new Base64Encoder();
      this.secure = secure;
    }

    private function setupSockets():void {
      getChannel = this.secure ? new SecureSocket() : new Socket();
      getChannel.timeout = 5000;
      getChannel.addEventListener(Event.CONNECT, onGetChannelConnect);
      getChannel.addEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
      getChannel.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
      getChannel.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);

      getChannelData = new ByteArray();
    }

    private function base64encode(str:String):String {
      base64encoder.reset();
      base64encoder.insertNewLines = false;
      base64encoder.encode(str);
      return base64encoder.toString();
    }

    public function writeUTFBytes(value:String):void {
      var data:String = base64encode(value);
      var authHeader:String = auth.authorizationHeader("POST", authState, authOpts, urlParsed, digestNC++);
      var socket:Socket = this.secure ? new SecureSocket() : new Socket();
      socket.timeout = 5000;
      socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
      socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);

      socket.addEventListener(Event.CONNECT, function ():void {
        socket.writeUTFBytes("POST " + urlParsed.urlpath + " HTTP/1.0\r\n");
        socket.writeUTFBytes("X-Sessioncookie: " + sessioncookie + "\r\n");
        socket.writeUTFBytes("Content-Length: " + data.length + "\r\n");
        socket.writeUTFBytes("Content-Type: application/x-rtsp-tunnelled" + "\r\n");
        socket.writeUTFBytes(authHeader);
        socket.writeUTFBytes("\r\n");

        socket.writeUTFBytes(data);
        socket.flush();

        // Timeout required before close to let the data actually be written to
        // the socket. Flush appears to be asynchronous...
        setTimeout(socket.close, 5000);
      });

      socket.connect(this.urlParsed.host, this.urlParsed.port);
    }

    public function readBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0):void {
      getChannel.readBytes(bytes, offset, length);
    }

    public function disconnect():void {
      if (getChannel.connected) {
        getChannel.close();

        getChannel.removeEventListener(Event.CONNECT, onGetChannelConnect);
        getChannel.removeEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
        getChannel.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
        getChannel.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
      }

      /* should probably wait for close, but it doesn't seem to fire properly */
      dispatchEvent(new Event('closed'));
    }

    public function connect():void {
      setupSockets();
      Logger.log('RTSP+HTTP' + (this.secure ? 'S' : '') + 'connecting to', this.urlParsed.host + ':' + this.urlParsed.port);
      getChannel.connect(this.urlParsed.host, this.urlParsed.port);
    }

    public function reconnect():void {
      if (getChannel.connected) {
        getChannel.close();
      }
      connect();
    }

    private function onGetChannelConnect(event:Event):void {
      initializeGetChannel();
    }

    public function stop():void {
      disconnect();
    }

    private function onGetChannelData(event:ProgressEvent):void {
      var parsed:* = request.readHeaders(getChannel, getChannelData);
      if (false === parsed) {
        return;
      }

      if (401 === parsed.code) {
        Logger.log('Unauthorized using auth method: ' + authState);
        /* Unauthorized, change authState and (possibly) try again */
        authOpts = parsed.headers['www-authenticate'];
        var newAuthState:String = auth.nextMethod(authState, authOpts);
        if (authState === newAuthState) {
          ErrorManager.dispatchError(parsed.code);
          return;
        }

        Logger.log('switching http-authorization from ' + authState + ' to ' + newAuthState);
        authState = newAuthState;
        getChannelData = new ByteArray();
        getChannel.close();
        getChannel.connect(this.urlParsed.host, this.urlParsed.port);
        return;
      }

      if (200 !== parsed.code) {
        ErrorManager.dispatchError(parsed.code);
        return;
      }

      getChannel.removeEventListener(ProgressEvent.SOCKET_DATA, onGetChannelData);
      getChannel.addEventListener(ProgressEvent.SOCKET_DATA, function(ev:ProgressEvent):void {
        dispatchEvent(new Event('data'));
      });

      dispatchEvent(new Event('connected'));
    }

    private function initializeGetChannel():void {
      getChannel.writeUTFBytes("GET " + urlParsed.urlpath + " HTTP/1.0\r\n");
      getChannel.writeUTFBytes("X-Sessioncookie: " +  sessioncookie + "\r\n");
      getChannel.writeUTFBytes("Accept: application/x-rtsp-tunnelled\r\n");
      getChannel.writeUTFBytes(auth.authorizationHeader("GET", authState, authOpts, urlParsed, digestNC++));
      getChannel.writeUTFBytes("\r\n");
      getChannel.flush();
    }

    private function onIOError(event:IOErrorEvent):void {
      ErrorManager.dispatchError(732, [event.text]);
      dispatchEvent(new Event('closed'));
    }

    private function onSecurityError(event:SecurityErrorEvent):void {
      ErrorManager.dispatchError(731, [event.text]);
      dispatchEvent(new Event('closed'));
    }
  }
}

Fix correct content length header in RTSP over HTTP


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

	import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.html.beads.ITextFieldView;

	/**
	 *  The NumericOnlyTextInputBead class is a specialty bead that can be used with
	 *  any TextInput control. The bead prevents non-numeric entry into the text input
	 *  area.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 10.2
	 *  @playerversion AIR 2.6
	 *  @productversion FlexJS 0.0
	 */
	public class NumericOnlyTextInputBead implements IBead
	{
		/**
		 *  constructor.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function NumericOnlyTextInputBead()
		{
		}

		private var _strand:IStrand;

		/**
		 *  @copy org.apache.flex.core.IBead#strand
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function set strand(value:IStrand):void
		{
			_strand = value;

			IEventDispatcher(value).addEventListener("viewChanged",viewChangeHandler);
		}

		private var _decimalSeparator:String = ".";

		/**
		 *  The character used to separate the integer and fraction parts of numbers.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function get decimalSeparator():String
		{
			return _decimalSeparator;
		}
		public function set decimalSeparator(value:String):void
		{
			if (_decimalSeparator != value) {
				_decimalSeparator = value;
			}
		}

		/**
		 * @private
		 */
		private function viewChangeHandler(event:Event):void
		{
			// get the ITextFieldView bead, which is required for this bead to work
			var textView:ITextFieldView = _strand.getBeadByType(ITextFieldView) as ITextFieldView;
			if (textView) {
				var textField:CSSTextField = textView.textField;
				textField.restrict = "0-9" + decimalSeparator;

				// listen for changes to this textField and prevent non-numeric values, such
				// as 34.09.94
				textField.addEventListener(TextEvent.TEXT_INPUT, handleTextInput);
			}
			else {
				throw new Error("NumericOnlyTextInputBead requires strand to have an ITextFieldView bead");
			}
		}

		/**
		 * @private
		 */
		private function handleTextInput(event:TextEvent):void
		{
			var insert:String = event.text;
			var caretIndex:int = (event.target as CSSTextField).caretIndex;
			var current:String = (event.target as CSSTextField).text;
			var value:String = current.substring(0,caretIndex) + insert + current.substr(caretIndex);
			var n:Number = Number(value);
			if (isNaN(n)) event.preventDefault();
		}
	}
}

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

	import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;
	import org.apache.flex.html.beads.ITextFieldView;

	/**
	 *  The NumericOnlyTextInputBead class is a specialty bead that can be used with
	 *  any TextInput control. The bead prevents non-numeric entry into the text input
	 *  area.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 10.2
	 *  @playerversion AIR 2.6
	 *  @productversion FlexJS 0.0
	 */
	public class NumericOnlyTextInputBead implements IBead
	{
		/**
		 *  constructor.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function NumericOnlyTextInputBead()
		{
		}

		private var _strand:IStrand;

		/**
		 *  @copy org.apache.flex.core.IBead#strand
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function set strand(value:IStrand):void
		{
			_strand = value;

			IEventDispatcher(value).addEventListener("viewChanged",viewChangeHandler);
		}

		private var _decimalSeparator:String = ".";

		/**
		 *  The character used to separate the integer and fraction parts of numbers.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function get decimalSeparator():String
		{
			return _decimalSeparator;
		}
		public function set decimalSeparator(value:String):void
		{
			if (_decimalSeparator != value) {
				_decimalSeparator = value;
			}
		}

        private var _maxChars:int = 0;

        /**
         *  The character used to separate the integer and fraction parts of numbers.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get maxChars():int
        {
            return _maxChars;
        }
        public function set maxChars(value:int):void
        {
            if (_maxChars != value) {
                _maxChars = value;
            }
        }

        /**
		 * @private
		 */
		private function viewChangeHandler(event:Event):void
		{
			// get the ITextFieldView bead, which is required for this bead to work
			var textView:ITextFieldView = _strand.getBeadByType(ITextFieldView) as ITextFieldView;
			if (textView) {
				var textField:CSSTextField = textView.textField;
				textField.restrict = "0-9" + decimalSeparator;
				textField.maxChars = maxChars;
				// listen for changes to this textField and prevent non-numeric values, such
				// as 34.09.94
				textField.addEventListener(TextEvent.TEXT_INPUT, handleTextInput);
			}
			else {
				throw new Error("NumericOnlyTextInputBead requires strand to have an ITextFieldView bead");
			}
		}

		/**
		 * @private
		 */
		private function handleTextInput(event:TextEvent):void
		{
			var insert:String = event.text;
			var caretIndex:int = (event.target as CSSTextField).caretIndex;
			var current:String = (event.target as CSSTextField).text;
			var value:String = current.substring(0,caretIndex) + insert + current.substr(caretIndex);
			var n:Number = Number(value);
			if (isNaN(n)) event.preventDefault();
		}
	}
}

add maxChars

add maxChars


			import flash.events.MouseEvent;
			import com.example.programmingas3.playlist.PlayList;
			import com.example.programmingas3.playlist.Song;
			import com.example.programmingas3.playlist.SortProperty;

			// constants for the different "states" of the song form
			private static const ADD_SONG:uint = 1;
			private static const SONG_DETAIL:uint = 2;

			private var playList:PlayList = new PlayList();

			private function initApp():void
			{
				// set the initial state of the song form, for adding a new song
				setFormState(ADD_SONG);

				// prepopulate the list with a few songs
				playList.addSong(new Song("Nessun Dorma", "Luciano Pavarotti", 1990, "nessundorma.mp3", ["90's", "Opera"]));
				playList.addSong(new Song("Come Undone", "Duran Duran", 1993, "comeundone.mp3", ["90's", "Pop"]));
				playList.addSong(new Song("Think of Me", "Sarah Brightman", 1987, "thinkofme.mp3", ["Showtunes"]));
				playList.addSong(new Song("Unbelievable", "EMF", 1991, "unbelievable.mp3", ["90's", "Pop"]));

				songList.dataProvider = playList.songList;
			}

			private function sortList(sortField:SortProperty):void
			{
				// Make all the sort type buttons enabled.
				// The active one will be grayed-out below
				sortByTitle.selected = false;
				sortByArtist.selected = false;
				sortByYear.selected = false;

				switch (sortField)
				{
					case SortProperty.TITLE:
						sortByTitle.selected = true;
						break;
					case SortProperty.ARTIST:
						sortByArtist.selected = true;
						break;
					case SortProperty.YEAR:
						sortByYear.selected = true;
						break;
				}

				playList.sortList(sortField);

				refreshList();
			}

			private function refreshList():void
			{
				// remember which song was selected
				var selectedSong:Song = Song(songList.selectedItem);

				// re-assign the song list as the dataprovider to get the newly sorted list
				// and force the List control to refresh itself
				songList.dataProvider = playList.songList;

				// reset the song selection
				if (selectedSong != null)
				{
					songList.selectedItem = selectedSong;
				}
			}

			private function songSelectionChange():void
			{
				if (songList.selectedIndex != -1)
				{
					setFormState(SONG_DETAIL);
				}
				else
				{
					setFormState(ADD_SONG);
				}
			}

			private function addNewSong():void
			{
				// gather the values from the form and add the new song
				var title:String = newSongTitle.text;
				var artist:String = newSongArtist.text;
				var year:uint = newSongYear.value;
				var filename:String = newSongFilename.text;
				var genres:Array = newSongGenres.selectedItems;

				playList.addSong(new Song(title, artist, year, filename, genres));

				refreshList();

				// clear out the "add song" form fields
				setFormState(ADD_SONG);
			}

			private function songListLabel(item:Object):String
			{
				return item.toString();
			}

			private function setFormState(state:uint):void
			{
				// set the form title and control state
				switch (state)
				{
					case ADD_SONG:
						formTitle.text = "Add New Song";
						// show the submit button
						submitSongData.visible = true;
						showAddControlsBtn.visible = false;
						// clear the form fields
						newSongTitle.text = "";
						newSongArtist.text = "";
						newSongYear.value = (new Date()).fullYear;
						newSongFilename.text = "";
						newSongGenres.selectedIndex = -1;
						// deselect the currently selected song (if any)
						songList.selectedIndex = -1;
						break;

					case SONG_DETAIL:
						formTitle.text = "Song Details";
						// populate the form with the selected item's data
						var selectedSong:Song = Song(songList.selectedItem);
						newSongTitle.text = selectedSong.title;
						newSongArtist.text = selectedSong.artist;
						newSongYear.value = selectedSong.year;
						newSongFilename.text = selectedSong.filename;
						newSongGenres.selectedItems = selectedSong.genres;
						// hide the submit button
						submitSongData.visible = false;
						showAddControlsBtn.visible = true;
						break;
				}
			}

			import flash.events.MouseEvent;
			import com.example.programmingas3.playlist.PlayList;
			import com.example.programmingas3.playlist.Song;
			import com.example.programmingas3.playlist.SortProperty;

			// constants for the different "states" of the song form
			private static const ADD_SONG:uint = 1;
			private static const SONG_DETAIL:uint = 2;

			private var playList:PlayList = new PlayList.<T>();

			private function initApp():void
			{
				// set the initial state of the song form, for adding a new song
				setFormState(ADD_SONG);

				// prepopulate the list with a few songs
				playList.addSong(new Song("Nessun Dorma", "Luciano Pavarotti", 1990, "nessundorma.mp3", ["90's", "Opera"]));
				playList.addSong(new Song("Come Undone", "Duran Duran", 1993, "comeundone.mp3", ["90's", "Pop"]));
				playList.addSong(new Song("Think of Me", "Sarah Brightman", 1987, "thinkofme.mp3", ["Showtunes"]));
				playList.addSong(new Song("Unbelievable", "EMF", 1991, "unbelievable.mp3", ["90's", "Pop"]));

				songList.dataProvider = playList.songList;
			}

			private function sortList(sortField:SortProperty.<T>):void
			{
				// Make all the sort type buttons enabled.
				// The active one will be grayed-out below
				sortByTitle.selected = false;
				sortByArtist.selected = false;
				sortByYear.selected = false;

				switch (sortField)
				{
					case SortProperty.TITLE:
						sortByTitle.selected = true;
						break;
					case SortProperty.ARTIST:
						sortByArtist.selected = true;
						break;
					case SortProperty.YEAR:
						sortByYear.selected = true;
						break;
				}

				playList.sortList(sortField);

				refreshList();
			}

			private function refreshList():void
			{
				// remember which song was selected
				var selectedSong:Song = Song(songList.selectedItem);

				// re-assign the song list as the dataprovider to get the newly sorted list
				// and force the List control to refresh itself
				songList.dataProvider = playList.songList;

				// reset the song selection
				if (selectedSong != null)
				{
					songList.selectedItem = selectedSong;
				}
			}

			private function songSelectionChange():void
			{
				if (songList.selectedIndex != -1)
				{
					setFormState(SONG_DETAIL);
				}
				else
				{
					setFormState(ADD_SONG);
				}
			}

			private function addNewSong():void
			{
				// gather the values from the form and add the new song
				var title:String = newSongTitle.text;
				var artist:String = newSongArtist.text;
				var year:uint = newSongYear.value;
				var filename:String = newSongFilename.text;
				var genres:Array = newSongGenres.selectedItems;

				playList.addSong(new Song(title, artist, year, filename, genres));

				refreshList();

				// clear out the "add song" form fields
				setFormState(ADD_SONG);
			}

			private function songListLabel(item:Object):String
			{
				return item.toString();
			}

			private function setFormState(state:uint):void
			{
				// set the form title and control state
				switch (state)
				{
					case ADD_SONG:
						formTitle.text = "Add New Song";
						// show the submit button
						submitSongData.visible = true;
						showAddControlsBtn.visible = false;
						// clear the form fields
						newSongTitle.text = "";
						newSongArtist.text = "";
						newSongYear.value = (new Date()).fullYear;
						newSongFilename.text = "";
						newSongGenres.selectedIndex = -1;
						// deselect the currently selected song (if any)
						songList.selectedIndex = -1;
						break;

					case SONG_DETAIL:
						formTitle.text = "Song Details";
						// populate the form with the selected item's data
						var selectedSong:Song = Song(songList.selectedItem);
						newSongTitle.text = selectedSong.title;
						newSongArtist.text = selectedSong.artist;
						newSongYear.value = selectedSong.year;
						newSongFilename.text = selectedSong.filename;
						newSongGenres.selectedItems = selectedSong.genres;
						// hide the submit button
						submitSongData.visible = false;
						showAddControlsBtn.visible = true;
						break;
				}
			}

Add type generics to AS3 test file.

Add type generics to AS3 test file.