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;
        /** Level Bitrate. **/
        public var bitrate : Number;
        /** Level Name. **/
        public var name : String;
        /** level index **/
        public var index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). **/
        public var url : String;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null && !isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                }
            }
            updateFragmentsProgramDate(_fragments);

            fragments = _fragments;
            start_seqnum = _fragments[0].seqnum;
            end_seqnum = _fragments[len - 1].seqnum;

            if (idx_with_metrics != -1) {
                // if at least one fragment contains PTS info, recompute PTS information for all fragments
                updateFragment(fragments[idx_with_metrics].seqnum, true, fragments[idx_with_metrics].data.pts_start, fragments[idx_with_metrics].data.pts_start + 1000 * fragments[idx_with_metrics].duration);
            } else {
                duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
            }
            averageduration = duration / len;
        }

        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) : Number {
            // 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;
                return frag.start_time;
            } else {
                return 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;
        /** Level Bitrate. **/
        public var bitrate : Number;
        /** Level Name. **/
        public var name : String;
        /** level index **/
        public var index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). **/
        public var url : String;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null && !isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                }
            }
            updateFragmentsProgramDate(_fragments);

            fragments = _fragments;
            start_seqnum = _fragments[0].seqnum;
            end_seqnum = _fragments[len - 1].seqnum;

            if (idx_with_metrics != -1) {
                // if at least one fragment contains PTS info, recompute PTS information for all fragments
                updateFragment(fragments[idx_with_metrics].seqnum, true, fragments[idx_with_metrics].data.pts_start, fragments[idx_with_metrics].data.pts_start + 1000 * fragments[idx_with_metrics].duration);
            } else {
                duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
            }
            averageduration = duration / len;
        }

        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) : Number {
            // 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;
                return frag.start_time;
            } else {
                CONFIG::LOGGING {
                    Log.error("updateFragment:seqnum " + seqnum + "not found!");
                }
                return 0;
            }
        }
    }
}

improve error logs

improve error logs


package org.openforis.collect.presenter {
	/**
	 *
	 * @author S. Ricci
	 * */
	import flash.events.Event;
	import flash.events.MouseEvent;

	import mx.collections.IList;
	import mx.events.IndexChangedEvent;
	import mx.rpc.AsyncResponder;
	import mx.rpc.events.ResultEvent;

	import org.openforis.collect.Application;
	import org.openforis.collect.client.ClientFactory;
	import org.openforis.collect.client.DataClient;
	import org.openforis.collect.event.UIEvent;
	import org.openforis.collect.metamodel.proxy.AttributeDefinitionProxy;
	import org.openforis.collect.metamodel.proxy.EntityDefinitionProxy;
	import org.openforis.collect.metamodel.proxy.ModelVersionProxy;
	import org.openforis.collect.model.proxy.RecordProxy;
	import org.openforis.collect.ui.UIBuilder;
	import org.openforis.collect.ui.component.detail.FormContainer;
	import org.openforis.collect.ui.view.DetailView;

	public class DetailPresenter extends AbstractPresenter {

		private var _dataClient:DataClient;
		private var _view:DetailView;

		public function DetailPresenter(view:DetailView) {
			this._view = view;
			this._dataClient = ClientFactory.dataClient;
			super();
		}

		override internal function initEventListeners():void {
			_view.backToListButton.addEventListener(MouseEvent.CLICK, backToListButtonClickHandler);

			eventDispatcher.addEventListener(UIEvent.ACTIVE_RECORD_CHANGED, activeRecordChangedListener);
		}

		/**
		 * Active record changed
		 * */
		internal function activeRecordChangedListener(event:UIEvent):void {
			var activeRecord:RecordProxy = Application.activeRecord;
			var activeRootEntity:EntityDefinitionProxy = Application.activeRootEntity;

			var keyValues:String = "";
			var keyAttributeDefinitions:IList = activeRootEntity.keyAttributeDefinitions;
			for each (var k:AttributeDefinitionProxy in keyAttributeDefinitions) {
				keyValues += activeRecord.rootEntityKeys.get(k.name);
			}

			var version:ModelVersionProxy = activeRecord.version;
			_view.keyAttributeValuesText.text = keyValues;
			_view.rootEntityDefinitionText.text = activeRootEntity.getLabelText();
			_view.formVersionText.text = version.getLabelText();

			var form:FormContainer = null;
			if (_view.formsContainer.contatinsForm(version,activeRootEntity)){
				_view.currentState = DetailView.EDIT_STATE;
				form = _view.formsContainer.getForm(version, activeRootEntity);
			} else {
				//build form
				_view.currentState = DetailView.LOADING_STATE;
				form = UIBuilder.buildForm(activeRootEntity, version);
				_view.formsContainer.addForm(form, version, activeRootEntity);
				_view.currentState = DetailView.EDIT_STATE;
			}

			form = _view.formsContainer.setActiveForm(version, activeRootEntity);
			form.record = activeRecord;
		}

		/**
		 * Back to list
		 * */
		protected function backToListButtonClickHandler(event:Event):void {
			_dataClient.clearActiveRecord(new AsyncResponder(clearActiveRecordHandler, faultHandler));
		}

		internal function clearActiveRecordHandler(event:ResultEvent, token:Object = null):void {
			var uiEvent:UIEvent = new UIEvent(UIEvent.BACK_TO_LIST);
			eventDispatcher.dispatchEvent(uiEvent);
		}

	}
}

package org.openforis.collect.presenter {
	/**
	 *
	 * @author S. Ricci
	 * */
	import flash.events.Event;
	import flash.events.MouseEvent;

	import mx.collections.IList;
	import mx.events.IndexChangedEvent;
	import mx.rpc.AsyncResponder;
	import mx.rpc.events.ResultEvent;

	import org.openforis.collect.Application;
	import org.openforis.collect.client.ClientFactory;
	import org.openforis.collect.client.DataClient;
	import org.openforis.collect.event.UIEvent;
	import org.openforis.collect.metamodel.proxy.AttributeDefinitionProxy;
	import org.openforis.collect.metamodel.proxy.EntityDefinitionProxy;
	import org.openforis.collect.metamodel.proxy.ModelVersionProxy;
	import org.openforis.collect.model.proxy.RecordProxy;
	import org.openforis.collect.ui.UIBuilder;
	import org.openforis.collect.ui.component.detail.FormContainer;
	import org.openforis.collect.ui.view.DetailView;

	public class DetailPresenter extends AbstractPresenter {

		private var _dataClient:DataClient;
		private var _view:DetailView;

		public function DetailPresenter(view:DetailView) {
			this._view = view;
			this._dataClient = ClientFactory.dataClient;
			super();
		}

		override internal function initEventListeners():void {
			_view.backToListButton.addEventListener(MouseEvent.CLICK, backToListButtonClickHandler);

			eventDispatcher.addEventListener(UIEvent.ACTIVE_RECORD_CHANGED, activeRecordChangedListener);
		}

		/**
		 * Active record changed
		 * */
		internal function activeRecordChangedListener(event:UIEvent):void {
			var activeRecord:RecordProxy = Application.activeRecord;
			var activeRootEntity:EntityDefinitionProxy = Application.activeRootEntity;

			var keyValues:String = "";
			var keyAttributeDefinitions:IList = activeRootEntity.keyAttributeDefinitions;
			for each (var k:AttributeDefinitionProxy in keyAttributeDefinitions) {
				keyValues += activeRecord.rootEntityKeys.get(k.name);
			}

			var version:ModelVersionProxy = activeRecord.version;
			_view.keyAttributeValuesText.text = keyValues;
			_view.rootEntityDefinitionText.text = activeRootEntity.getLabelText();
			_view.formVersionText.text = version.getLabelText();

			var form:FormContainer = null;
			if (_view.formsContainer.contatinsForm(version,activeRootEntity)){
				_view.currentState = DetailView.EDIT_STATE;
				form = _view.formsContainer.getForm(version, activeRootEntity);
			} else {
				//build form
				_view.currentState = DetailView.LOADING_STATE;
				form = UIBuilder.buildForm(activeRootEntity, version);
				_view.formsContainer.addForm(form, version, activeRootEntity);
				_view.currentState = DetailView.EDIT_STATE;
			}

			form = _view.formsContainer.setActiveForm(version, activeRootEntity);
			form.record = activeRecord;
		}

		/**
		 * Back to list
		 * */
		protected function backToListButtonClickHandler(event:Event):void {
			_dataClient.clearActiveRecord(new AsyncResponder(clearActiveRecordHandler, faultHandler));
		}

		internal function clearActiveRecordHandler(event:ResultEvent, token:Object = null):void {
			Application.activeRecord = null;
			var uiEvent:UIEvent = new UIEvent(UIEvent.BACK_TO_LIST);
			eventDispatcher.dispatchEvent(uiEvent);
		}

	}
}

clean active record when going back to list


import "./cpu";
import "./mmu";
import "./machine";
import "./bits";

struct Joypad {
  CPU: *cpu.CPU;

  // Select
  Select_Button: bool;
  Select_Direction: bool;

  // State
  State_Start: bool;
  State_A: bool;
  State_B: bool;
  State_Select: bool;
  State_Up: bool;
  State_Down: bool;
  State_Left: bool;
  State_Right: bool;
}

implement Joypad {
  def New(cpu_: *cpu.CPU): Self {
    let j: Joypad;
    j.CPU = cpu_;

    return j;
  }

  def Reset(self, mode: machine.MachineMode) {
    // Input starts off disabled on CGB (but enabled on GB)
    self.Select_Button = (mode == machine.MODE_GB);
    self.Select_Direction = (mode == machine.MODE_GB);

    self.State_Start = false;
    self.State_A = false;
    self.State_B = false;
    self.State_Select = false;
    self.State_Up = false;
    self.State_Down = false;
    self.State_Left = false;
    self.State_Right = false;
  }

  def OnKey(self, which: uint32, isPressed: bool) {
    if which == 40 {
      // START => ENTER (US Keyboard)
      self.State_Start = isPressed;
    } else if which == 29 {
      // A => Z (US Keyboard)
      self.State_A = isPressed;
    } else if which == 27 {
      // B => X (US Keyboard)
      self.State_B = isPressed;
    } else if which == 225 {
      // SELECT => LEFT SHIFT (US Keyboard)
      self.State_Select = isPressed;
    } else if which == 82 {
      // UP => UP ARROW (US Keyboard)
      self.State_Up = isPressed;
    } else if which == 81 {
      // DOWN => DOWN ARROW (US Keyboard)
      self.State_Down = isPressed;
    } else if which == 80 {
      // LEFT => LEFT ARROW (US Keyboard)
      self.State_Left = isPressed;
    } else if which == 79 {
      // RIGHT => RIGHT ARROW (US Keyboard)
      self.State_Right = isPressed;
    }
  }

  def OnKeyPress(self, which: uint32) {
    self.OnKey(which, true);
  }

  def OnKeyRelease(self, which: uint32) {
    self.OnKey(which, false);
  }

  def Read(self, address: uint16, ptr: *uint8): bool {
    *ptr = if address == 0xFF00 {
      // P1 – Joypad (R/W)
      // Bit 7 - Not used
      // Bit 6 - Not used
      // Bit 5 - P15 Select Button Keys      (0=Select)
      // Bit 4 - P14 Select Direction Keys   (0=Select)
      // Bit 3 - P13 Input Down  or Start    (0=Pressed) (Read Only)
      // Bit 2 - P12 Input Up    or Select   (0=Pressed) (Read Only)
      // Bit 1 - P11 Input Left  or Button B (0=Pressed) (Read Only)
      // Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only)

      // NOTE: This is backwards logic to me. 0 = True ?
      (
        bits.Bit(true, 7) |
        bits.Bit(true, 6) |
        bits.Bit(not self.Select_Button, 5) |
        bits.Bit(not self.Select_Direction, 4) |
        bits.Bit(not ((self.Select_Button and self.State_Start) or (self.Select_Direction and self.State_Down)), 3) |
        bits.Bit(not ((self.Select_Button and self.State_Select) or (self.Select_Direction and self.State_Up)), 2) |
        bits.Bit(not ((self.Select_Button and self.State_B) or (self.Select_Direction and self.State_Left)), 1) |
        bits.Bit(not ((self.Select_Button and self.State_A) or (self.Select_Direction and self.State_Right)), 0)
      );
    } else {
      return false;
    };

    return true;
  }

  def Write(self, address: uint16, value: uint8): bool {
    if address == 0xFF00 {
      self.Select_Button = not bits.Test(value, 5);
      self.Select_Direction = not bits.Test(value, 4);
    } else {
      return false;
    }

    return true;
  }

  def AsMemoryController(self, this: *Joypad): mmu.MemoryController {
    let mc: mmu.MemoryController;
    mc.Read = MCRead;
    mc.Write = MCWrite;
    mc.Data = this as *uint8;
    mc.Release = MCRelease;

    return mc;
  }
}

def MCRelease(this: *mmu.MemoryController) {
  // Do nothing
}

def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool {
  return (this.Data as *Joypad).Read(address, value);
}

def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool {
  return (this.Data as *Joypad).Write(address, value);
}

import "./cpu";
import "./mmu";
import "./machine";
import "./bits";

struct Joypad {
  CPU: *cpu.CPU;

  // Select
  Select_Button: bool;
  Select_Direction: bool;

  // State
  State_Start: bool;
  State_A: bool;
  State_B: bool;
  State_Select: bool;
  State_Up: bool;
  State_Down: bool;
  State_Left: bool;
  State_Right: bool;
}

implement Joypad {
  def New(cpu_: *cpu.CPU): Self {
    let j: Joypad;
    j.CPU = cpu_;

    return j;
  }

  def Reset(self, mode: machine.MachineMode) {
    // Input starts off disabled on CGB (but enabled on GB)
    self.Select_Button = (mode == machine.MODE_GB);
    self.Select_Direction = (mode == machine.MODE_GB);

    self.State_Start = false;
    self.State_A = false;
    self.State_B = false;
    self.State_Select = false;
    self.State_Up = false;
    self.State_Down = false;
    self.State_Left = false;
    self.State_Right = false;
  }

  def OnKey(self, which: uint32, isPressed: bool) {
    if which == 40 {
      // START => ENTER (US Keyboard)
      self.State_Start = isPressed;
    } else if which == 29 {
      // A => Z (US Keyboard)
      self.State_A = isPressed;
    } else if which == 27 {
      // B => X (US Keyboard)
      self.State_B = isPressed;
    } else if which == 225 {
      // SELECT => LEFT SHIFT (US Keyboard)
      self.State_Select = isPressed;
    } else if which == 82 {
      // UP => UP ARROW (US Keyboard)
      self.State_Up = isPressed;
    } else if which == 81 {
      // DOWN => DOWN ARROW (US Keyboard)
      self.State_Down = isPressed;
    } else if which == 80 {
      // LEFT => LEFT ARROW (US Keyboard)
      self.State_Left = isPressed;
    } else if which == 79 {
      // RIGHT => RIGHT ARROW (US Keyboard)
      self.State_Right = isPressed;
    }
  }

  def OnKeyPress(self, which: uint32) {
    self.OnKey(which, true);
  }

  def OnKeyRelease(self, which: uint32) {
    self.OnKey(which, false);
  }

  def Read(self, address: uint16, ptr: *uint8): bool {
    *ptr = if address == 0xFF00 {
      // P1 – Joypad (R/W)
      // Bit 7 - Not used
      // Bit 6 - Not used
      // Bit 5 - P15 Select Button Keys      (0=Select)
      // Bit 4 - P14 Select Direction Keys   (0=Select)
      // Bit 3 - P13 Input Down  or Start    (0=Pressed) (Read Only)
      // Bit 2 - P12 Input Up    or Select   (0=Pressed) (Read Only)
      // Bit 1 - P11 Input Left  or Button B (0=Pressed) (Read Only)
      // Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only)
      (
        bits.Bit(true, 7) |
        bits.Bit(true, 6) |
        bits.Bit(not self.Select_Button, 5) |
        bits.Bit(not self.Select_Direction, 4) |
        bits.Bit(not ((self.Select_Button and self.State_Start) or (self.Select_Direction and self.State_Down)), 3) |
        bits.Bit(not ((self.Select_Button and self.State_Select) or (self.Select_Direction and self.State_Up)), 2) |
        bits.Bit(not ((self.Select_Button and self.State_B) or (self.Select_Direction and self.State_Left)), 1) |
        bits.Bit(not ((self.Select_Button and self.State_A) or (self.Select_Direction and self.State_Right)), 0)
      );
    } else {
      return false;
    };

    return true;
  }

  def Write(self, address: uint16, value: uint8): bool {
    if address == 0xFF00 {
      self.Select_Button = not bits.Test(value, 5);
      self.Select_Direction = not bits.Test(value, 4);
    } else {
      return false;
    }

    return true;
  }

  def AsMemoryController(self, this: *Joypad): mmu.MemoryController {
    let mc: mmu.MemoryController;
    mc.Read = MCRead;
    mc.Write = MCWrite;
    mc.Data = this as *uint8;
    mc.Release = MCRelease;

    return mc;
  }
}

def MCRelease(this: *mmu.MemoryController) {
  // Do nothing
}

def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool {
  return (this.Data as *Joypad).Read(address, value);
}

def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool {
  return (this.Data as *Joypad).Write(address, value);
}

remove unneeded comment

:fire: remove unneeded comment


// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31

package {

import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import com.adobe.net.proxies.RFC2817Socket;
import com.gsolo.encryption.MD5;

[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="error", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {

  private static var CONNECTING:int = 0;
  private static var OPEN:int = 1;
  private static var CLOSING:int = 2;
  private static var CLOSED:int = 3;

  private var socket:RFC2817Socket;
  private var main:WebSocketMain;
  private var url:String;
  private var scheme:String;
  private var host:String;
  private var port:uint;
  private var path:String;
  private var origin:String;
  private var protocol:String;
  private var buffer:ByteArray = new ByteArray();
  private var headerState:int = 0;
  private var readyState:int = CONNECTING;
  private var bufferedAmount:int = 0;
  private var headers:String;
  private var noiseChars:Array;
  private var expectedDigest:String;

  public function WebSocket(
      main:WebSocketMain, url:String, protocol:String,
      proxyHost:String = null, proxyPort:int = 0,
      headers:String = null) {
    this.main = main;
    initNoiseChars();
    this.url = url;
    var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
    if (!m) main.fatal("SYNTAX_ERR: invalid url: " + url);
    this.scheme = m[1];
    this.host = m[2];
    this.port = parseInt(m[4] || "80");
    this.path = m[5] || "/";
    this.origin = main.getOrigin();
    this.protocol = protocol;
    // if present and not the empty string, headers MUST end with \r\n
    // headers should be zero or more complete lines, for example
    // "Header1: xxx\r\nHeader2: yyyy\r\n"
    this.headers = headers;

    socket = new RFC2817Socket();

    // if no proxy information is supplied, it acts like a normal Socket
    // @see RFC2817Socket::connect
    if (proxyHost != null && proxyPort != 0){
      socket.setProxyInfo(proxyHost, proxyPort);
    }

    socket.addEventListener(Event.CLOSE, onSocketClose);
    socket.addEventListener(Event.CONNECT, onSocketConnect);
    socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
    socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
    socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
    socket.connect(host, port);
  }

  public function send(data:String):int {
    if (readyState == OPEN) {
      socket.writeByte(0x00);
      socket.writeUTFBytes(decodeURIComponent(data));
      socket.writeByte(0xff);
      socket.flush();
      main.log("sent: " + data);
      return -1;
    } else if (readyState == CLOSED) {
      var bytes:ByteArray = new ByteArray();
      bytes.writeUTFBytes(decodeURIComponent(data));
      bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
      // We use return value to let caller know bufferedAmount because we cannot fire
      // stateChange event here which causes weird error:
      // > You are trying to call recursively into the Flash Player which is not allowed.
      return bufferedAmount;
    } else {
      main.fatal("INVALID_STATE_ERR: invalid state");
      return 0;
    }
  }

  public function close():void {
    main.log("close");
    try {
      socket.close();
    } catch (ex:Error) { }
    readyState = CLOSED;
    // We don't fire any events here because it causes weird error:
    // > You are trying to call recursively into the Flash Player which is not allowed.
    // We do something equivalent in JavaScript WebSocket#close instead.
  }

  public function getReadyState():int {
    return readyState;
  }

  public function getBufferedAmount():int {
    return bufferedAmount;
  }

  private function onSocketConnect(event:Event):void {
    main.log("connected");
    var hostValue:String = host + (port == 80 ? "" : ":" + port);
    var cookie:String = "";
    if (main.getCallerHost() == host) {
      cookie = ExternalInterface.call("function(){return document.cookie}");
    }
    var key1:String = generateKey();
    var key2:String = generateKey();
    var key3:String = generateKey3();
    expectedDigest = getSecurityDigest(key1, key2, key3);
    var opt:String = "";
    if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
    // if caller passes additional headers they must end with "\r\n"
    if (headers) opt += headers;

    var req:String = StringUtil.substitute(
      "GET {0} HTTP/1.1\r\n" +
      "Upgrade: WebSocket\r\n" +
      "Connection: Upgrade\r\n" +
      "Host: {1}\r\n" +
      "Origin: {2}\r\n" +
      "Cookie: {3}\r\n" +
      "Sec-WebSocket-Key1: {4}\r\n" +
      "Sec-WebSocket-Key2: {5}\r\n" +
      "{6}" +
      "\r\n",
      path, hostValue, origin, cookie, key1, key2, opt);
    main.log("request header:\n" + req);
    socket.writeUTFBytes(req);
    main.log("sent key3: " + key3);
    writeBytes(key3);
    socket.flush();
  }

  private function onSocketClose(event:Event):void {
    main.log("closed");
    readyState = CLOSED;
    notifyStateChange();
    dispatchEvent(new Event("close"));
  }

  private function onSocketIoError(event:IOErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message = "cannot connect to Web Socket server at " + url + " (IoError)";
    } else {
      message = "error communicating with Web Socket server at " + url + " (IoError)";
    }
    onError(message);
  }

  private function onSocketSecurityError(event:SecurityErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message =
          "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
          "make sure the server is running and Flash socket policy file is correctly placed";
    } else {
      message = "error communicating with Web Socket server at " + url + " (SecurityError)";
    }
    onError(message);
  }

  private function onError(message:String):void {
    var state:int = readyState;
    if (state == CLOSED) return;
    main.error(message);
    close();
    notifyStateChange();
    dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
  }

  private function onSocketData(event:ProgressEvent):void {
    var pos:int = buffer.length;
    socket.readBytes(buffer, pos);
    for (; pos < buffer.length; ++pos) {
      if (headerState < 4) {
        // try to find "\r\n\r\n"
        if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
          ++headerState;
        } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
          ++headerState;
        } else {
          headerState = 0;
        }
        if (headerState == 4) {
          var headerStr:String = buffer.readUTFBytes(pos + 1);
          main.log("response header:\n" + headerStr);
          if (!validateHeader(headerStr)) return;
          makeBufferCompact();
          pos = -1;
        }
      } else if (headerState == 4) {
        if (pos == 15) {
          var replyDigest:String = readBytes(buffer, 16);
          main.log("reply digest: " + replyDigest);
          if (replyDigest != expectedDigest) {
            onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
            return;
          }
          headerState = 5;
          makeBufferCompact();
          pos = -1;
          readyState = OPEN;
          notifyStateChange();
          dispatchEvent(new Event("open"));
        }
      } else {
        if (buffer[pos] == 0xff) {
          if (buffer.readByte() != 0x00) {
            onError("data must start with \\x00");
            return;
          }
          var data:String = buffer.readUTFBytes(pos - 1);
          main.log("received: " + data);
          dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
          buffer.readByte();
          makeBufferCompact();
          pos = -1;
        }
      }
    }
  }

  private function validateHeader(headerStr:String):Boolean {
    var lines:Array = headerStr.split(/\r\n/);
    if (!lines[0].match(/^HTTP\/1.1 101 /)) {
      onError("bad response: " + lines[0]);
      return false;
    }
    var header:Object = {};
    for (var i:int = 1; i < lines.length; ++i) {
      if (lines[i].length == 0) continue;
      var m:Array = lines[i].match(/^(\S+): (.*)$/);
      if (!m) {
        onError("failed to parse response header line: " + lines[i]);
        return false;
      }
      header[m[1]] = m[2];
    }
    if (header["Upgrade"] != "WebSocket") {
      onError("invalid Upgrade: " + header["Upgrade"]);
      return false;
    }
    if (header["Connection"] != "Upgrade") {
      onError("invalid Connection: " + header["Connection"]);
      return false;
    }
    var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
    if (resOrigin != origin) {
      onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
      return false;
    }
    if (protocol && header["Sec-WebSocket-Protocol"] != protocol) {
      onError("protocol doesn't match: '" +
        header["WebSocket-Protocol"] + "' != '" + protocol + "'");
      return false;
    }
    return true;
  }

  private function makeBufferCompact():void {
    if (buffer.position == 0) return;
    var nextBuffer:ByteArray = new ByteArray();
    buffer.readBytes(nextBuffer);
    buffer = nextBuffer;
  }

  private function notifyStateChange():void {
    dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
  }

  private function initNoiseChars():void {
    noiseChars = new Array();
    for (var i:int = 0x21; i <= 0x2f; ++i) {
      noiseChars.push(String.fromCharCode(i));
    }
    for (var j:int = 0x3a; j <= 0x7a; ++j) {
      noiseChars.push(String.fromCharCode(j));
    }
  }

  private function generateKey():String {
    var spaces:uint = randomInt(1, 12);
    var max:uint = uint.MAX_VALUE / spaces;
    var number:uint = randomInt(0, max);
    var key:String = (number * spaces).toString();
    var noises:int = randomInt(1, 12);
    var pos:int;
    for (var i:int = 0; i < noises; ++i) {
      var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
      pos = randomInt(0, key.length);
      key = key.substr(0, pos) + char + key.substr(pos);
    }
    for (var j:int = 0; j < spaces; ++j) {
      pos = randomInt(1, key.length - 1);
      key = key.substr(0, pos) + " " + key.substr(pos);
    }
    return key;
  }

  private function generateKey3():String {
    var key3:String = "";
    for (var i:int = 0; i < 8; ++i) {
      key3 += String.fromCharCode(randomInt(0, 255));
    }
    return key3;
  }

  private function getSecurityDigest(key1:String, key2:String, key3:String):String {
    var bytes1:String = keyToBytes(key1);
    var bytes2:String = keyToBytes(key2);
    return MD5.rstr_md5(bytes1 + bytes2 + key3);
  }

  private function keyToBytes(key:String):String {
    var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
    var spaces:uint = 0;
    for (var i:int = 0; i < key.length; ++i) {
      if (key.charAt(i) == " ") ++spaces;
    }
    var resultNum:uint = keyNum / spaces;
    var bytes:String = "";
    for (var j:int = 3; j >= 0; --j) {
      bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
    }
    return bytes;
  }

  private function writeBytes(bytes:String):void {
    for (var i:int = 0; i < bytes.length; ++i) {
      socket.writeByte(bytes.charCodeAt(i));
    }
  }

  private function readBytes(buffer:ByteArray, numBytes:int):String {
    var bytes:String = "";
    for (var i:int = 0; i < numBytes; ++i) {
      // & 0xff is to make \x80-\xff positive number.
      bytes += String.fromCharCode(buffer.readByte() & 0xff);
    }
    return bytes;
  }

  private function randomInt(min:uint, max:uint):uint {
    return min + Math.floor(Math.random() * (Number(max) - min + 1));
  }

  // for debug
  private function dumpBytes(bytes:String):void {
    var output:String = "";
    for (var i:int = 0; i < bytes.length; ++i) {
      output += bytes.charCodeAt(i).toString() + ", ";
    }
    main.log(output);
  }

}

}

// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31

package {

import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import com.adobe.net.proxies.RFC2817Socket;
import com.gsolo.encryption.MD5;

[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="error", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {

  private static var CONNECTING:int = 0;
  private static var OPEN:int = 1;
  private static var CLOSING:int = 2;
  private static var CLOSED:int = 3;

  private var socket:RFC2817Socket;
  private var main:WebSocketMain;
  private var url:String;
  private var scheme:String;
  private var host:String;
  private var port:uint;
  private var path:String;
  private var origin:String;
  private var protocol:String;
  private var buffer:ByteArray = new ByteArray();
  private var headerState:int = 0;
  private var readyState:int = CONNECTING;
  private var bufferedAmount:int = 0;
  private var headers:String;
  private var noiseChars:Array;
  private var expectedDigest:String;

  public function WebSocket(
      main:WebSocketMain, url:String, protocol:String,
      proxyHost:String = null, proxyPort:int = 0,
      headers:String = null) {
    this.main = main;
    initNoiseChars();
    this.url = url;
    var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
    if (!m) main.fatal("SYNTAX_ERR: invalid url: " + url);
    this.scheme = m[1];
    this.host = m[2];
    this.port = parseInt(m[4] || "80");
    this.path = m[5] || "/";
    this.origin = main.getOrigin();
    this.protocol = protocol;
    // if present and not the empty string, headers MUST end with \r\n
    // headers should be zero or more complete lines, for example
    // "Header1: xxx\r\nHeader2: yyyy\r\n"
    this.headers = headers;

    socket = new RFC2817Socket();

    // if no proxy information is supplied, it acts like a normal Socket
    // @see RFC2817Socket::connect
    if (proxyHost != null && proxyPort != 0){
      socket.setProxyInfo(proxyHost, proxyPort);
    }

    socket.addEventListener(Event.CLOSE, onSocketClose);
    socket.addEventListener(Event.CONNECT, onSocketConnect);
    socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
    socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
    socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
    socket.connect(host, port);
  }

  public function send(data:String):int {
    if (readyState == OPEN) {
      socket.writeByte(0x00);
      socket.writeUTFBytes(decodeURIComponent(data));
      socket.writeByte(0xff);
      socket.flush();
      main.log("sent: " + data);
      return -1;
    } else if (readyState == CLOSED) {
      var bytes:ByteArray = new ByteArray();
      bytes.writeUTFBytes(decodeURIComponent(data));
      bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
      // We use return value to let caller know bufferedAmount because we cannot fire
      // stateChange event here which causes weird error:
      // > You are trying to call recursively into the Flash Player which is not allowed.
      return bufferedAmount;
    } else {
      main.fatal("INVALID_STATE_ERR: invalid state");
      return 0;
    }
  }

  public function close():void {
    main.log("close");
    try {
      socket.close();
    } catch (ex:Error) { }
    readyState = CLOSED;
    // We don't fire any events here because it causes weird error:
    // > You are trying to call recursively into the Flash Player which is not allowed.
    // We do something equivalent in JavaScript WebSocket#close instead.
  }

  public function getReadyState():int {
    return readyState;
  }

  public function getBufferedAmount():int {
    return bufferedAmount;
  }

  private function onSocketConnect(event:Event):void {
    main.log("connected");
    var hostValue:String = host + (port == 80 ? "" : ":" + port);
    var cookie:String = "";
    if (main.getCallerHost() == host) {
      cookie = ExternalInterface.call("function(){return document.cookie}");
    }
    var key1:String = generateKey();
    var key2:String = generateKey();
    var key3:String = generateKey3();
    expectedDigest = getSecurityDigest(key1, key2, key3);
    var opt:String = "";
    if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
    // if caller passes additional headers they must end with "\r\n"
    if (headers) opt += headers;

    var req:String = StringUtil.substitute(
      "GET {0} HTTP/1.1\r\n" +
      "Upgrade: WebSocket\r\n" +
      "Connection: Upgrade\r\n" +
      "Host: {1}\r\n" +
      "Origin: {2}\r\n" +
      "Cookie: {3}\r\n" +
      "Sec-WebSocket-Key1: {4}\r\n" +
      "Sec-WebSocket-Key2: {5}\r\n" +
      "{6}" +
      "\r\n",
      path, hostValue, origin, cookie, key1, key2, opt);
    main.log("request header:\n" + req);
    socket.writeUTFBytes(req);
    main.log("sent key3: " + key3);
    main.log("expected digest: " + expectedDigest);
    writeBytes(key3);
    socket.flush();
  }

  private function onSocketClose(event:Event):void {
    main.log("closed");
    readyState = CLOSED;
    notifyStateChange();
    dispatchEvent(new Event("close"));
  }

  private function onSocketIoError(event:IOErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message = "cannot connect to Web Socket server at " + url + " (IoError)";
    } else {
      message = "error communicating with Web Socket server at " + url + " (IoError)";
    }
    onError(message);
  }

  private function onSocketSecurityError(event:SecurityErrorEvent):void {
    var message:String;
    if (readyState == CONNECTING) {
      message =
          "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
          "make sure the server is running and Flash socket policy file is correctly placed";
    } else {
      message = "error communicating with Web Socket server at " + url + " (SecurityError)";
    }
    onError(message);
  }

  private function onError(message:String):void {
    var state:int = readyState;
    if (state == CLOSED) return;
    main.error(message);
    close();
    notifyStateChange();
    dispatchEvent(new Event(state == CONNECTING ? "close" : "error"));
  }

  private function onSocketData(event:ProgressEvent):void {
    var pos:int = buffer.length;
    socket.readBytes(buffer, pos);
    for (; pos < buffer.length; ++pos) {
      if (headerState < 4) {
        // try to find "\r\n\r\n"
        if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
          ++headerState;
        } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
          ++headerState;
        } else {
          headerState = 0;
        }
        if (headerState == 4) {
          var headerStr:String = buffer.readUTFBytes(pos + 1);
          main.log("response header:\n" + headerStr);
          if (!validateHeader(headerStr)) return;
          makeBufferCompact();
          pos = -1;
        }
      } else if (headerState == 4) {
        var replyDigest:String = readBytes(buffer, 16);
        main.log("reply digest: " + replyDigest);
        if (replyDigest != expectedDigest) {
          onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
          return;
        }
        headerState = 5;
        makeBufferCompact();
        pos = -1;
        readyState = OPEN;
        notifyStateChange();
        dispatchEvent(new Event("open"));
      } else {
        if (buffer[pos] == 0xff) {
          if (buffer.readByte() != 0x00) {
            onError("data must start with \\x00");
            return;
          }
          var data:String = buffer.readUTFBytes(pos - 1);
          main.log("received: " + data);
          dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
          buffer.readByte();
          makeBufferCompact();
          pos = -1;
        }
      }
    }
  }

  private function validateHeader(headerStr:String):Boolean {
    var lines:Array = headerStr.split(/\r\n/);
    if (!lines[0].match(/^HTTP\/1.1 101 /)) {
      onError("bad response: " + lines[0]);
      return false;
    }
    var header:Object = {};
    for (var i:int = 1; i < lines.length; ++i) {
      if (lines[i].length == 0) continue;
      var m:Array = lines[i].match(/^(\S+): (.*)$/);
      if (!m) {
        onError("failed to parse response header line: " + lines[i]);
        return false;
      }
      header[m[1]] = m[2];
    }
    if (header["Upgrade"] != "WebSocket") {
      onError("invalid Upgrade: " + header["Upgrade"]);
      return false;
    }
    if (header["Connection"] != "Upgrade") {
      onError("invalid Connection: " + header["Connection"]);
      return false;
    }
    var resOrigin:String = header["Sec-WebSocket-Origin"].toLowerCase();
    if (resOrigin != origin) {
      onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
      return false;
    }
    if (protocol && header["Sec-WebSocket-Protocol"] != protocol) {
      onError("protocol doesn't match: '" +
        header["WebSocket-Protocol"] + "' != '" + protocol + "'");
      return false;
    }
    return true;
  }

  private function makeBufferCompact():void {
    if (buffer.position == 0) return;
    var nextBuffer:ByteArray = new ByteArray();
    buffer.readBytes(nextBuffer);
    buffer = nextBuffer;
  }

  private function notifyStateChange():void {
    dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
  }

  private function initNoiseChars():void {
    noiseChars = new Array();
    for (var i:int = 0x21; i <= 0x2f; ++i) {
      noiseChars.push(String.fromCharCode(i));
    }
    for (var j:int = 0x3a; j <= 0x7a; ++j) {
      noiseChars.push(String.fromCharCode(j));
    }
  }

  private function generateKey():String {
    var spaces:uint = randomInt(1, 12);
    var max:uint = uint.MAX_VALUE / spaces;
    var number:uint = randomInt(0, max);
    var key:String = (number * spaces).toString();
    var noises:int = randomInt(1, 12);
    var pos:int;
    for (var i:int = 0; i < noises; ++i) {
      var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
      pos = randomInt(0, key.length);
      key = key.substr(0, pos) + char + key.substr(pos);
    }
    for (var j:int = 0; j < spaces; ++j) {
      pos = randomInt(1, key.length - 1);
      key = key.substr(0, pos) + " " + key.substr(pos);
    }
    return key;
  }

  private function generateKey3():String {
    var key3:String = "";
    for (var i:int = 0; i < 8; ++i) {
      key3 += String.fromCharCode(randomInt(0, 255));
    }
    return key3;
  }

  private function getSecurityDigest(key1:String, key2:String, key3:String):String {
    var bytes1:String = keyToBytes(key1);
    var bytes2:String = keyToBytes(key2);
    return MD5.rstr_md5(bytes1 + bytes2 + key3);
  }

  private function keyToBytes(key:String):String {
    var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
    var spaces:uint = 0;
    for (var i:int = 0; i < key.length; ++i) {
      if (key.charAt(i) == " ") ++spaces;
    }
    var resultNum:uint = keyNum / spaces;
    var bytes:String = "";
    for (var j:int = 3; j >= 0; --j) {
      bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
    }
    return bytes;
  }

  private function writeBytes(bytes:String):void {
    for (var i:int = 0; i < bytes.length; ++i) {
      socket.writeByte(bytes.charCodeAt(i));
    }
  }

  private function readBytes(buffer:ByteArray, numBytes:int):String {
    var bytes:String = "";
    for (var i:int = 0; i < numBytes; ++i) {
      // & 0xff is to make \x80-\xff positive number.
      bytes += String.fromCharCode(buffer.readByte() & 0xff);
    }
    return bytes;
  }

  private function randomInt(min:uint, max:uint):uint {
    return min + Math.floor(Math.random() * (Number(max) - min + 1));
  }

  // for debug
  private function dumpBytes(bytes:String):void {
    var output:String = "";
    for (var i:int = 0; i < bytes.length; ++i) {
      output += bytes.charCodeAt(i).toString() + ", ";
    }
    main.log(output);
  }

}

}

Remove invalid check of 'pos'.

Remove invalid check of 'pos'.

This check causes the new (hixie-76) WebSockets protocol check to
hang.


/* 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.demux {
    import flash.utils.ByteArray;
    import org.mangui.hls.model.Level;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    public class DemuxHelper {
        public static function probe(data : ByteArray, level : Level, audioselect : Function, progress : Function, complete : Function, videometadata : Function, id3tagfound : Function, audioOnly : Boolean) : Demuxer {
            data.position = 0;
            CONFIG::LOGGING {
                Log.debug("probe fragment type");
            }
            var aac_match : Boolean = AACDemuxer.probe(data);
            var mp3_match : Boolean = MP3Demuxer.probe(data);
            var ts_match : Boolean = TSDemuxer.probe(data);
            CONFIG::LOGGING {
                Log.debug("AAC/MP3/TS match:" + aac_match + "/" + mp3_match + "/" + ts_match);
            }
            /* prioritize level info :
             * if ts_match && codec_avc  => TS demuxer
             * if aac_match && codec_aac => AAC demuxer
             * if mp3_match && codec_mp3 => MP3 demuxer
             * if no codec info in Manifest, use fallback order : AAC/MP3/TS
             */
            if (ts_match && level.codec_h264) {
                CONFIG::LOGGING {
                    Log.debug("TS match + H264 signaled in Manifest, use TS demuxer");
                }
                return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly);
            } else if (aac_match && level.codec_aac) {
                CONFIG::LOGGING {
                    Log.debug("AAC match + AAC signaled in Manifest, use AAC demuxer");
                }
                return new AACDemuxer(audioselect, progress, complete, id3tagfound);
            } else if (mp3_match && level.codec_mp3) {
                CONFIG::LOGGING {
                    Log.debug("MP3 match + MP3 signaled in Manifest, use MP3 demuxer");
                }
                return new MP3Demuxer(audioselect, progress, complete, id3tagfound);
            } else if (aac_match) {
                return new AACDemuxer(audioselect, progress, complete, id3tagfound);
            } else if (mp3_match) {
                return new MP3Demuxer(audioselect, progress, complete, id3tagfound);
            } else if (ts_match) {
                return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly);
            } else {
                CONFIG::LOGGING {
                    Log.debug("probe fails");
                }
                return null;
            }
        }
    }
}

/* 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.demux {
    import flash.utils.ByteArray;
    import org.mangui.hls.model.Level;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    public class DemuxHelper {
        public static function probe(data : ByteArray, level : Level, audioselect : Function, progress : Function, complete : Function, videometadata : Function, id3tagfound : Function, audioOnly : Boolean) : Demuxer {
            data.position = 0;
            CONFIG::LOGGING {
                Log.debug("probe fragment type");
            }
            var aac_match : Boolean = AACDemuxer.probe(data);
            var mp3_match : Boolean = MP3Demuxer.probe(data);
            var ts_match : Boolean = TSDemuxer.probe(data);
            CONFIG::LOGGING {
                Log.debug("AAC/MP3/TS match:" + aac_match + "/" + mp3_match + "/" + ts_match);
            }
            /* prioritize level info :
             * if ts_match && codec_avc  => TS demuxer
             * if aac_match && codec_aac => AAC demuxer
             * if mp3_match && codec_mp3 => MP3 demuxer
             * if no codec info in Manifest, use fallback order : AAC/MP3/TS
             */
            if (ts_match && level && level.codec_h264) {
                CONFIG::LOGGING {
                    Log.debug("TS match + H264 signaled in Manifest, use TS demuxer");
                }
                return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly);
            } else if (aac_match && level && level.codec_aac) {
                CONFIG::LOGGING {
                    Log.debug("AAC match + AAC signaled in Manifest, use AAC demuxer");
                }
                return new AACDemuxer(audioselect, progress, complete, id3tagfound);
            } else if (mp3_match && level && level.codec_mp3) {
                CONFIG::LOGGING {
                    Log.debug("MP3 match + MP3 signaled in Manifest, use MP3 demuxer");
                }
                return new MP3Demuxer(audioselect, progress, complete, id3tagfound);
            } else if (aac_match) {
                return new AACDemuxer(audioselect, progress, complete, id3tagfound);
            } else if (mp3_match) {
                return new MP3Demuxer(audioselect, progress, complete, id3tagfound);
            } else if (ts_match) {
                return new TSDemuxer(audioselect, progress, complete, videometadata, audioOnly);
            } else {
                CONFIG::LOGGING {
                    Log.debug("probe fails");
                }
                return null;
            }
        }
    }
}

Make probe Level parameter optional

[Mangui.DemuxHelper] Make probe Level parameter optional

Selection logic ensures proper Demuxer is used anyway


package
{

	import flash.events.*;
	import flash.net.*;
	import flash.display.Loader;
	import flash.external.ExternalInterface;

	public class SendAndLoadExample
	{
		private var _url:String;
		private var lastSendedUrl:String = "";

		public function SendAndLoadExample()
		{

		}

		public function sendData(url:String,msg:String):void
		{
			_url = url;
			if(lastSendedUrl != _url){
				var src = encodeURI(_url);
				var request:URLRequest = new URLRequest(src);
				var loader:Loader = new Loader();
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleComplete);
				loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
				loader.load(request);
				lastSendedUrl = _url;
				//Config.app.msgbox.text += msg;
				//ExternalInterface.call('console.log','url Loaded');
			}
		}
		private function handleComplete(event:Event):void
		{
			//var loader:URLLoader = URLLoader(event.target);
			//trace("post happened");
		//	ExternalInterface.call('console.log','url Loaded');
		}
		private function onIOError(event:IOErrorEvent):void
		{
		//	ExternalInterface.call('console.log','url error');
		}
	}
}

package
{

	import flash.events.*;
	import flash.net.*;
	import flash.display.Loader;
	import flash.external.ExternalInterface;

	public class SendAndLoadExample
	{
		private var _url:String;
		private var lastSendedUrl:String = "";

		public function SendAndLoadExample()
		{

		}

		public function sendData(url:String,msg:String):void
		{
			_url = url;
			if(lastSendedUrl != _url){
				var src = encodeURI(_url);
				var request:URLRequest = new URLRequest(src);
				var loader:Loader = new Loader();
				loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleComplete);
				loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
				loader.load(request);
				lastSendedUrl = _url;
			}
		}
		private function handleComplete(event:Event):void
		{
			var loadedContent = event.target;
		}
		private function onIOError(event:IOErrorEvent):void
		{
		//	ExternalInterface.call('console.log','url error');
		}
	}
}

comment outs

comment outs