////////////////////////////////////////////////////////////////////////////////
//
//  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.beads
{
	import flash.display.Loader;
	import flash.display.Shape;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldType;

	import org.apache.flex.core.BeadViewBase;
    import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IBeadView;
	import org.apache.flex.core.IStrand;
    import org.apache.flex.core.IStrandWithModel;
	import org.apache.flex.core.ValuesManager;
    import org.apache.flex.html.beads.models.ImageAndTextModel;

	/**
	 *  The ImageButtonView class provides an image-only view
	 *  for the standard Button. Unlike the CSSButtonView, this
	 *  class does not support background and border; only images
	 *  for the up, over, and active states.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 10.2
	 *  @playerversion AIR 2.6
	 *  @productversion FlexJS 0.0
	 */
	public class ImageAndTextButtonView extends BeadViewBase implements IBeadView, IBead
	{
		/**
		 *  Constructor.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function ImageAndTextButtonView()
		{
			upSprite = new Sprite();
			downSprite = new Sprite();
			overSprite = new Sprite();
            upTextField = new CSSTextField();
            downTextField = new CSSTextField();
            overTextField = new CSSTextField();
            upTextField.selectable = false;
            upTextField.type = TextFieldType.DYNAMIC;
            downTextField.selectable = false;
            downTextField.type = TextFieldType.DYNAMIC;
            overTextField.selectable = false;
            overTextField.type = TextFieldType.DYNAMIC;
            upTextField.autoSize = "left";
            downTextField.autoSize = "left";
            overTextField.autoSize = "left";
		}

		/**
		 *  @copy org.apache.flex.core.IBead#strand
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		override public function set strand(value:IStrand):void
		{
			super.strand = value;
            textModel = IStrandWithModel(value).model as ImageAndTextModel;
            textModel.addEventListener("textChange", textChangeHandler);
            textModel.addEventListener("htmlChange", htmlChangeHandler);

			shape = new Shape();
			shape.graphics.beginFill(0xCCCCCC);
			shape.graphics.drawRect(0, 0, 10, 10);
			shape.graphics.endFill();
			SimpleButton(value).upState = upSprite;
			SimpleButton(value).downState = downSprite;
			SimpleButton(value).overState = overSprite;
			SimpleButton(value).hitTestState = shape;

			setupBackground(upSprite, upTextField, 0xCCCCCC);
			setupBackground(overSprite, overTextField, 0xFFCCCC, "hover");
			setupBackground(downSprite, downTextField, 0x808080, "active");
            upTextField.styleParent = value;
            downTextField.styleParent = value;
            overTextField.styleParent = value;

		}

		private var upSprite:Sprite;
		private var downSprite:Sprite;
		private var overSprite:Sprite;
		private var shape:Shape;

        private var textModel:ImageAndTextModel;

        /**
         *  The URL of an icon to use in the button
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get image():String
        {
            return textModel.image;
        }

		/**
		 * @private
		 */
		private function setupBackground(sprite:Sprite, textField:CSSTextField, color:uint, state:String = null):void
		{
			var backgroundImage:Object = image;
			if (backgroundImage)
			{
				var loader:Loader = new Loader();
				sprite.addChildAt(loader, 0);
                sprite.addChild(textField);
				var url:String = backgroundImage as String;
				loader.load(new URLRequest(url));
				loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, function (e:flash.events.Event):void {
					updateHitArea();
                    textField.x = loader.width;
                    sprite.graphics.clear();
                    sprite.graphics.beginFill(color);
                    sprite.graphics.drawRect(0, 0, sprite.width, sprite.height);
                    sprite.graphics.endFill();
				});
			}
		}

        private function textChangeHandler(event:Event):void
        {
            text = textModel.text;
        }

        private function htmlChangeHandler(event:Event):void
        {
            html = textModel.html;
        }

        /**
         *  The CSSTextField in the up state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var upTextField:CSSTextField;

        /**
         *  The CSSTextField in the down state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var downTextField:CSSTextField;

        /**
         *  The CSSTextField in the over state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var overTextField:CSSTextField;

        /**
         *  @copy org.apache.flex.html.core.ITextModel#text
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get text():String
        {
            return upTextField.text;
        }

        /**
         *  @private
         */
        public function set text(value:String):void
        {
            upTextField.text = value;
            downTextField.text = value;
            overTextField.text = value;
            shape.graphics.clear();
            shape.graphics.beginFill(0xCCCCCC);
            shape.graphics.drawRect(0, 0, upSprite.width, upSprite.height);
            shape.graphics.endFill();

        }

        /**
         *  @copy org.apache.flex.html.core.ITextModel#text
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get html():String
        {
            return upTextField.htmlText;
        }

        /**
         *  @private
         */
        public function set html(value:String):void
        {
            upTextField.htmlText = value;
            downTextField.htmlText = value;
            overTextField.htmlText = value;
        }

        /**
		 * @private
		 */
		private function updateHitArea():void
		{
			shape.graphics.clear();
			shape.graphics.beginFill(0xCCCCCC);
			shape.graphics.drawRect(0, 0, upSprite.width, upSprite.height);
			shape.graphics.endFill();

		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  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.beads
{
	import flash.display.Loader;
	import flash.display.Shape;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFieldType;

	import org.apache.flex.core.BeadViewBase;
	import org.apache.flex.core.CSSTextField;
	import org.apache.flex.core.IBead;
	import org.apache.flex.core.IBeadView;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IStrandWithModel;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.html.beads.models.ImageAndTextModel;
    import org.apache.flex.utils.SolidBorderUtil;

	/**
	 *  The ImageButtonView class provides an image-only view
	 *  for the standard Button. Unlike the CSSButtonView, this
	 *  class does not support background and border; only images
	 *  for the up, over, and active states.
	 *
	 *  @langversion 3.0
	 *  @playerversion Flash 10.2
	 *  @playerversion AIR 2.6
	 *  @productversion FlexJS 0.0
	 */
	public class ImageAndTextButtonView extends BeadViewBase implements IBeadView, IBead
	{
		/**
		 *  Constructor.
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		public function ImageAndTextButtonView()
		{
			upSprite = new Sprite();
			downSprite = new Sprite();
			overSprite = new Sprite();
            upTextField = new CSSTextField();
            downTextField = new CSSTextField();
            overTextField = new CSSTextField();
            upTextField.selectable = false;
            upTextField.type = TextFieldType.DYNAMIC;
            downTextField.selectable = false;
            downTextField.type = TextFieldType.DYNAMIC;
            overTextField.selectable = false;
            overTextField.type = TextFieldType.DYNAMIC;
            upTextField.autoSize = "left";
            downTextField.autoSize = "left";
            overTextField.autoSize = "left";
		}

		/**
		 *  @copy org.apache.flex.core.IBead#strand
		 *
		 *  @langversion 3.0
		 *  @playerversion Flash 10.2
		 *  @playerversion AIR 2.6
		 *  @productversion FlexJS 0.0
		 */
		override public function set strand(value:IStrand):void
		{
			super.strand = value;
            textModel = IStrandWithModel(value).model as ImageAndTextModel;
            textModel.addEventListener("textChange", textChangeHandler);
            textModel.addEventListener("htmlChange", htmlChangeHandler);
            textModel.addEventListener("imageChange", imageChangeHandler);

			shape = new Shape();
			shape.graphics.beginFill(0xCCCCCC);
			shape.graphics.drawRect(0, 0, 10, 10);
			shape.graphics.endFill();
			SimpleButton(value).upState = upSprite;
			SimpleButton(value).downState = downSprite;
			SimpleButton(value).overState = overSprite;
			SimpleButton(value).hitTestState = shape;

			setupBackground(upSprite, upTextField, 0xCCCCCC);
			setupBackground(overSprite, overTextField, 0xFFCCCC, "hover");
			setupBackground(downSprite, downTextField, 0x808080, "active");
            upTextField.styleParent = value;
            downTextField.styleParent = value;
            overTextField.styleParent = value;

		}

		private var upSprite:Sprite;
		private var downSprite:Sprite;
		private var overSprite:Sprite;
		private var shape:Shape;

        private var textModel:ImageAndTextModel;

        /**
         *  The URL of an icon to use in the button
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get image():String
        {
            return textModel.image;
        }

		/**
		 * @private
		 */
		private function setupBackground(sprite:Sprite, textField:CSSTextField, color:uint, state:String = null):void
		{
			var backgroundImage:Object = image;
			if (backgroundImage)
			{
				var loader:Loader = new Loader();
				sprite.addChildAt(loader, 0);
                sprite.addChild(textField);
				var url:String = backgroundImage as String;
				loader.load(new URLRequest(url));
				loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, function (e:flash.events.Event):void {
                    var padding:int = 2;
                    var borderWidth:int = 1;
					updateHitArea();
                    loader.x = padding;
                    textField.x = loader.width + padding;
                    textField.y = padding;
                    loader.y = (textField.height + padding + padding - loader.height) / 2;
                    sprite.graphics.clear();
                    sprite.graphics.beginFill(color);
                    sprite.graphics.drawRect(0, 0, sprite.width, sprite.height);
                    sprite.graphics.endFill();
                    SolidBorderUtil.drawBorder(sprite.graphics,
                        0, 0, textField.x + textField.width + padding, textField.height + padding + padding,
                        0x000000, color, borderWidth);
				});
			}
		}

        private function textChangeHandler(event:Event):void
        {
            text = textModel.text;
        }

        private function htmlChangeHandler(event:Event):void
        {
            html = textModel.html;
        }

        private function imageChangeHandler(event:Event):void
        {
            setupBackground(upSprite, upTextField, 0xCCCCCC);
            setupBackground(overSprite, overTextField, 0xFFCCCC, "hover");
            setupBackground(downSprite, downTextField, 0x808080, "active");
        }

        /**
         *  The CSSTextField in the up state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var upTextField:CSSTextField;

        /**
         *  The CSSTextField in the down state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var downTextField:CSSTextField;

        /**
         *  The CSSTextField in the over state
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public var overTextField:CSSTextField;

        /**
         *  @copy org.apache.flex.html.core.ITextModel#text
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get text():String
        {
            return upTextField.text;
        }

        /**
         *  @private
         */
        public function set text(value:String):void
        {
            upTextField.text = value;
            downTextField.text = value;
            overTextField.text = value;
            shape.graphics.clear();
            shape.graphics.beginFill(0xCCCCCC);
            shape.graphics.drawRect(0, 0, upSprite.width, upSprite.height);
            shape.graphics.endFill();

        }

        /**
         *  @copy org.apache.flex.html.core.ITextModel#text
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
        public function get html():String
        {
            return upTextField.htmlText;
        }

        /**
         *  @private
         */
        public function set html(value:String):void
        {
            upTextField.htmlText = value;
            downTextField.htmlText = value;
            overTextField.htmlText = value;
        }

        /**
		 * @private
		 */
		private function updateHitArea():void
		{
			shape.graphics.clear();
			shape.graphics.beginFill(0xCCCCCC);
			shape.graphics.drawRect(0, 0, upSprite.width, upSprite.height);
			shape.graphics.endFill();

		}
	}
}

fix ImageAndTextButton on AS

fix ImageAndTextButton on AS


//
// $Id$

package com.threerings.flash.video {

import flash.display.DisplayObject;

import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;

import flash.geom.Point;

import flash.media.SoundTransform;
import flash.media.Video;

import flash.net.NetConnection;
import flash.net.NetStream;

import flash.utils.Timer;

import com.threerings.util.Log;
import com.threerings.util.ValueEvent;

public class FlvVideoPlayer extends EventDispatcher
    implements VideoPlayer
{
    public function FlvVideoPlayer (autoPlay :Boolean = true)
    {
        _autoPlay = autoPlay;
        _sizeChecker = new Timer(100);
        _sizeChecker.addEventListener(TimerEvent.TIMER, handleSizeCheck);

        _positionChecker = new Timer(250);
        _positionChecker.addEventListener(TimerEvent.TIMER, handlePositionCheck);
    }

    /**
     * Start playing a video!
     */
    public function load (url :String) :void
    {
        _netCon = new NetConnection();
        _netCon.addEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);

        // error handlers
        _netCon.addEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
        _netCon.addEventListener(IOErrorEvent.IO_ERROR, handleIOError);
        _netCon.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);

        _netCon.connect(null);
        _netStream = new NetStream(_netCon);
        // pass in refs to some of our protected methods
        _netStream.client = {
            onMetaData: handleMetaData
        };
        _netStream.soundTransform = new SoundTransform(_volume);
        _netStream.addEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);

        _vid.attachNetStream(_netStream);
        _sizeChecker.start();

        _netStream.play(url);
        if (_autoPlay) {
            updateState(VideoPlayerCodes.STATE_PLAYING);

        } else {
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_READY);
        }
    }

    // from VideoPlayer
    public function getDisplay () :DisplayObject
    {
        return _vid;
    }

    // from VideoPlayer
    public function getState () :int
    {
        return _state;
    }

    // from VideoPlayer
    public function getSize () :Point
    {
        return _size.clone();
    }

    // from VideoPlayer
    public function play () :void
    {
        if (_netStream != null) {
            _netStream.resume();
            updateState(VideoPlayerCodes.STATE_PLAYING);
        }
        // TODO: throw an error if _netStream == null??
    }

    // from VideoPlayer
    public function pause () :void
    {
        if (_netStream != null) {
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_PAUSED);
        }
    }

    // from VideoPlayer
    public function getDuration () :Number
    {
        return _duration;
    }

    // from VideoPlayer
    public function getPosition () :Number
    {
        if (_netStream != null) {
            return _netStream.time;
        }
        return NaN;
    }

    // from VideoPlayer
    public function seek (position :Number) :void
    {
        if (_netStream != null) {
            _netStream.seek(position);
        }
    }

    // from VideoPlayer
    public function getVolume () :Number
    {
        return _volume;
    }

    // from VideoPlayer
    public function setVolume (volume :Number) :void
    {
        _volume = Math.max(0, Math.min(1, volume));
        if (_netStream != null) {
            _netStream.soundTransform = new SoundTransform(_volume);
        }
    }

    // from VideoPlayer
    public function unload () :void
    {
        _sizeChecker.reset();
        _vid.attachNetStream(null);

        if (_netStream != null) {
            _netStream.close();
            _netStream.removeEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
            _netStream = null;
        }
        if (_netCon != null) {
            _netCon.close();
            _netCon.removeEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);
            _netCon.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
            _netCon.removeEventListener(IOErrorEvent.IO_ERROR, handleIOError);
            _netCon.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
            _netCon = null;
        }
    }

    /**
     * Check to see if we now know the dimensions of the video.
     */
    protected function handleSizeCheck (event :TimerEvent) :void
    {
        if (_vid.videoWidth == 0 || _vid.videoHeight == 0) {
            return; // not known yet!
        }

        // stop the checker timer
        _sizeChecker.stop();
        _size = new Point(_vid.videoWidth, _vid.videoHeight);

        // set up the width/height
        _vid.width = _size.x;
        _vid.height = _size.y;

        log.debug("====> size known: " + _size);
        dispatchEvent(new ValueEvent(VideoPlayerCodes.SIZE, _size.clone()));
    }

    protected function handlePositionCheck (event :TimerEvent) :void
    {
        dispatchEvent(new ValueEvent(VideoPlayerCodes.POSITION, getPosition()));
    }

    protected function handleNetStatus (event :NetStatusEvent) :void
    {
        var info :Object = event.info;
        if ("error" == info.level) {
            log.warning("NetStatus error: " + info.code);
            dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, info.code));
            return;
        }
        // else info.level == "status"
        switch (info.code) {
        case "NetConnection.Connect.Success":
        case "NetConnection.Connect.Closed":
            // these status events we ignore
            break;

        default:
            log.info("NetStatus status: " + info.code);
            break;
        }
    }

    protected function handleStreamNetStatus (event :NetStatusEvent) :void
    {
        log.debug("NetStatus", "level", event.info.level, "code", event.info.code);

        switch (event.info.code) {
        case "NetStream.Play.Stop":
            // rewind to the beginning
            _netStream.seek(0);
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_PAUSED);
            break;

        case "NetStream.Seek.Notify":
            handlePositionCheck(null);
            break;
        }
    }

    protected function handleAsyncError (event :AsyncErrorEvent) :void
    {
        redispatchError(event);
    }

    protected function handleIOError (event :IOErrorEvent) :void
    {
        redispatchError(event);
    }

    protected function handleSecurityError (event :SecurityErrorEvent) :void
    {
        redispatchError(event);
    }

    /**
     * Redispatch some error we received to our listeners.
     */
    protected function redispatchError (event :ErrorEvent) :void
    {
        log.warning("got video error: " + event);
        dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, event.text));
    }

    /**
     * Called when metadata (if any) is found in the video stream.
     */
    protected function handleMetaData (info :Object) :void
    {
        log.info("Got video metadata:");
        for (var n :String in info) {
            log.info("    " + n + ": " + info[n]);
        }

        // TODO: total duration is info.duration

        if ("duration" in info) {
            _duration = Number(info.duration);
            if (!isNaN(_duration)) {
                dispatchEvent(new ValueEvent(VideoPlayerCodes.DURATION, _duration));
            }
        }
    }

    protected function updateState (newState :int) :void
    {
        const oldState :int = _state;

        _state = newState;
        dispatchEvent(new ValueEvent(VideoPlayerCodes.STATE, newState));

        if (_state == VideoPlayerCodes.STATE_PLAYING) {
            _positionChecker.start();

        } else {
            _positionChecker.reset();
            if (oldState == VideoPlayerCodes.STATE_PLAYING) {
                handlePositionCheck(null);
            }
        }
    }

    protected const log :Log = Log.getLog(this);

    protected var _autoPlay :Boolean;

    protected var _vid :Video = new Video();

    protected var _netCon :NetConnection;

    protected var _netStream :NetStream;

    protected var _state :int = VideoPlayerCodes.STATE_UNREADY;

    /** Our size, null until known. */
    protected var _size :Point;

    protected var _duration :Number = NaN;

    protected var _volume :Number = 1;

    /** Checks the video every 100ms to see if the dimensions are now know.
     * Yes, this is how to do it. We could trigger on ENTER_FRAME, but then
     * we may not know the dimensions unless we're added on the display list,
     * and we want this to work in the general case. */
    protected var _sizeChecker :Timer;

    protected var _positionChecker :Timer;
}
}

//
// $Id$

package com.threerings.flash.video {

import flash.display.DisplayObject;

import flash.events.AsyncErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;

import flash.geom.Point;

import flash.media.SoundTransform;
import flash.media.Video;

import flash.net.NetConnection;
import flash.net.NetStream;

import flash.utils.Timer;

import com.threerings.util.Log;
import com.threerings.util.ValueEvent;

public class FlvVideoPlayer extends EventDispatcher
    implements VideoPlayer
{
    public function FlvVideoPlayer (autoPlay :Boolean = true)
    {
        _autoPlay = autoPlay;
        _sizeChecker = new Timer(100);
        _sizeChecker.addEventListener(TimerEvent.TIMER, handleSizeCheck);

        _positionChecker = new Timer(250);
        _positionChecker.addEventListener(TimerEvent.TIMER, handlePositionCheck);
    }

    /**
     * Start playing a video!
     */
    public function load (url :String) :void
    {
        _netCon = new NetConnection();
        _netCon.addEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);

        // error handlers
        _netCon.addEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
        _netCon.addEventListener(IOErrorEvent.IO_ERROR, handleIOError);
        _netCon.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);

        _netCon.connect(null);
        _netStream = new NetStream(_netCon);
        // pass in refs to some of our protected methods
        _netStream.client = {
            onMetaData: handleMetaData
        };
        _netStream.soundTransform = new SoundTransform(_volume);
        _netStream.addEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);

        _vid.attachNetStream(_netStream);
        _sizeChecker.start();

        _netStream.play(url);
        if (_autoPlay) {
            updateState(VideoPlayerCodes.STATE_PLAYING);

        } else {
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_READY);
        }
    }

    // from VideoPlayer
    public function getDisplay () :DisplayObject
    {
        return _vid;
    }

    // from VideoPlayer
    public function getState () :int
    {
        return _state;
    }

    // from VideoPlayer
    public function getSize () :Point
    {
        return _size.clone();
    }

    // from VideoPlayer
    public function play () :void
    {
        if (_netStream != null) {
            _netStream.resume();
            updateState(VideoPlayerCodes.STATE_PLAYING);
        }
        // TODO: throw an error if _netStream == null??
    }

    // from VideoPlayer
    public function pause () :void
    {
        if (_netStream != null) {
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_PAUSED);
        }
    }

    // from VideoPlayer
    public function getDuration () :Number
    {
        return _duration;
    }

    // from VideoPlayer
    public function getPosition () :Number
    {
        if (_netStream != null) {
            return _netStream.time;
        }
        return NaN;
    }

    // from VideoPlayer
    public function seek (position :Number) :void
    {
        if (_netStream != null) {
            _netStream.seek(position);
        }
    }

    // from VideoPlayer
    public function getVolume () :Number
    {
        return _volume;
    }

    // from VideoPlayer
    public function setVolume (volume :Number) :void
    {
        _volume = Math.max(0, Math.min(1, volume));
        if (_netStream != null) {
            _netStream.soundTransform = new SoundTransform(_volume);
        }
    }

    // from VideoPlayer
    public function unload () :void
    {
        _sizeChecker.reset();
        _positionChecker.reset();
        _vid.attachNetStream(null);

        if (_netStream != null) {
            _netStream.close();
            _netStream.removeEventListener(NetStatusEvent.NET_STATUS, handleStreamNetStatus);
            _netStream = null;
        }
        if (_netCon != null) {
            _netCon.close();
            _netCon.removeEventListener(NetStatusEvent.NET_STATUS, handleNetStatus);
            _netCon.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, handleAsyncError);
            _netCon.removeEventListener(IOErrorEvent.IO_ERROR, handleIOError);
            _netCon.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSecurityError);
            _netCon = null;
        }
    }

    /**
     * Check to see if we now know the dimensions of the video.
     */
    protected function handleSizeCheck (event :TimerEvent) :void
    {
        if (_vid.videoWidth == 0 || _vid.videoHeight == 0) {
            return; // not known yet!
        }

        // stop the checker timer
        _sizeChecker.stop();
        _size = new Point(_vid.videoWidth, _vid.videoHeight);

        // set up the width/height
        _vid.width = _size.x;
        _vid.height = _size.y;

        log.debug("====> size known: " + _size);
        dispatchEvent(new ValueEvent(VideoPlayerCodes.SIZE, _size.clone()));
    }

    protected function handlePositionCheck (event :TimerEvent) :void
    {
        dispatchEvent(new ValueEvent(VideoPlayerCodes.POSITION, getPosition()));
    }

    protected function handleNetStatus (event :NetStatusEvent) :void
    {
        var info :Object = event.info;
        if ("error" == info.level) {
            log.warning("NetStatus error: " + info.code);
            dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, info.code));
            return;
        }
        // else info.level == "status"
        switch (info.code) {
        case "NetConnection.Connect.Success":
        case "NetConnection.Connect.Closed":
            // these status events we ignore
            break;

        default:
            log.info("NetStatus status: " + info.code);
            break;
        }
    }

    protected function handleStreamNetStatus (event :NetStatusEvent) :void
    {
        log.debug("NetStatus", "level", event.info.level, "code", event.info.code);

        switch (event.info.code) {
        case "NetStream.Play.Stop":
            // rewind to the beginning
            _netStream.seek(0);
            _netStream.pause();
            updateState(VideoPlayerCodes.STATE_PAUSED);
            break;

        case "NetStream.Seek.Notify":
            handlePositionCheck(null);
            break;
        }
    }

    protected function handleAsyncError (event :AsyncErrorEvent) :void
    {
        redispatchError(event);
    }

    protected function handleIOError (event :IOErrorEvent) :void
    {
        redispatchError(event);
    }

    protected function handleSecurityError (event :SecurityErrorEvent) :void
    {
        redispatchError(event);
    }

    /**
     * Redispatch some error we received to our listeners.
     */
    protected function redispatchError (event :ErrorEvent) :void
    {
        log.warning("got video error: " + event);
        dispatchEvent(new ValueEvent(VideoPlayerCodes.ERROR, event.text));
    }

    /**
     * Called when metadata (if any) is found in the video stream.
     */
    protected function handleMetaData (info :Object) :void
    {
        log.info("Got video metadata:");
        for (var n :String in info) {
            log.info("    " + n + ": " + info[n]);
        }

        // TODO: total duration is info.duration

        if ("duration" in info) {
            _duration = Number(info.duration);
            if (!isNaN(_duration)) {
                dispatchEvent(new ValueEvent(VideoPlayerCodes.DURATION, _duration));
            }
        }
    }

    protected function updateState (newState :int) :void
    {
        const oldState :int = _state;

        _state = newState;
        dispatchEvent(new ValueEvent(VideoPlayerCodes.STATE, newState));

        if (_state == VideoPlayerCodes.STATE_PLAYING) {
            _positionChecker.start();

        } else {
            _positionChecker.reset();
            if (oldState == VideoPlayerCodes.STATE_PLAYING) {
                handlePositionCheck(null);
            }
        }
    }

    protected const log :Log = Log.getLog(this);

    protected var _autoPlay :Boolean;

    protected var _vid :Video = new Video();

    protected var _netCon :NetConnection;

    protected var _netStream :NetStream;

    protected var _state :int = VideoPlayerCodes.STATE_UNREADY;

    /** Our size, null until known. */
    protected var _size :Point;

    protected var _duration :Number = NaN;

    protected var _volume :Number = 1;

    /** Checks the video every 100ms to see if the dimensions are now know.
     * Yes, this is how to do it. We could trigger on ENTER_FRAME, but then
     * we may not know the dimensions unless we're added on the display list,
     * and we want this to work in the general case. */
    protected var _sizeChecker :Timer;

    protected var _positionChecker :Timer;
}
}

stop the positionTimer when we unload.

Bugfix: stop the positionTimer when we unload.

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


////////////////////////////////////////////////////////////////////////////////
//
//  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.beads.layouts
{

	import org.apache.flex.core.IBeadLayout;
    import org.apache.flex.core.ILayoutChild;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParentIUIBase;
	import org.apache.flex.core.IStrand;
    import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The NonVirtualBasicLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  as specified by CSS properties like left, right, top
     *  and bottom.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class NonVirtualBasicLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function NonVirtualBasicLayout()
		{
		}

		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("heightChanged", changeHandler);
            IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
			IEventDispatcher(value).addEventListener("childrenAdded", changeHandler);
			IEventDispatcher(value).addEventListener("itemsCreated", changeHandler);
			IEventDispatcher(value).addEventListener("beadsAdded", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var layoutParent:ILayoutParent = _strand.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParentIUIBase = layoutParent ? layoutParent.contentView : IParentIUIBase(_strand);

            var w:Number = contentView.width;
            var h:Number = contentView.height;
			var n:int = contentView.numElements;
			for (var i:int = 0; i < n; i++)
			{
				var child:ILayoutChild = contentView.getElementAt(i) as ILayoutChild;
                var left:Number = ValuesManager.valuesImpl.getValue(child, "left");
                var right:Number = ValuesManager.valuesImpl.getValue(child, "right");
                var top:Number = ValuesManager.valuesImpl.getValue(child, "top");
                var bottom:Number = ValuesManager.valuesImpl.getValue(child, "bottom");

                if (!isNaN(left))
                {
                    child.x = left;
                }
                if (!isNaN(top))
                {
                    child.y = top;
                }
                var ilc:ILayoutChild;
                if (child is ILayoutChild)
                {
                    ilc = child as ILayoutChild;
                    if (!isNaN(ilc.percentHeight))
                        ilc.setHeight(contentView.height * ilc.percentHeight / 100);
                }
                if (child is ILayoutChild)
                {
                    ilc = child as ILayoutChild;
                    if (!isNaN(ilc.percentWidth))
                        ilc.setWidth(contentView.width * ilc.percentWidth / 100);
                }
                if (!isNaN(right))
                {
                    if (!isNaN(left))
                        child.width = w - right - left;
                    else
                        child.x = w - right - child.width;
                }
                if (!isNaN(bottom))
                {
                    if (!isNaN(top))
                        child.height = h - bottom - top;
                    else
                        child.y = h - bottom - child.height;
                }
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
//  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.beads.layouts
{

	import org.apache.flex.core.IBeadLayout;
	import org.apache.flex.core.ILayoutChild;
	import org.apache.flex.core.ILayoutParent;
	import org.apache.flex.core.IParentIUIBase;
	import org.apache.flex.core.IStrand;
	import org.apache.flex.core.IUIBase;
	import org.apache.flex.core.ValuesManager;
	import org.apache.flex.events.Event;
	import org.apache.flex.events.IEventDispatcher;

    /**
     *  The NonVirtualBasicLayout class is a simple layout
     *  bead.  It takes the set of children and lays them out
     *  as specified by CSS properties like left, right, top
     *  and bottom.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.2
     *  @playerversion AIR 2.6
     *  @productversion FlexJS 0.0
     */
	public class NonVirtualBasicLayout implements IBeadLayout
	{
        /**
         *  Constructor.
         *
         *  @langversion 3.0
         *  @playerversion Flash 10.2
         *  @playerversion AIR 2.6
         *  @productversion FlexJS 0.0
         */
		public function NonVirtualBasicLayout()
		{
		}

		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("heightChanged", changeHandler);
            IEventDispatcher(value).addEventListener("widthChanged", changeHandler);
			IEventDispatcher(value).addEventListener("childrenAdded", changeHandler);
            IEventDispatcher(value).addEventListener("layoutNeeded", changeHandler);
			IEventDispatcher(value).addEventListener("itemsCreated", changeHandler);
			IEventDispatcher(value).addEventListener("beadsAdded", changeHandler);
		}

		private function changeHandler(event:Event):void
		{
			var layoutParent:ILayoutParent = _strand.getBeadByType(ILayoutParent) as ILayoutParent;
			var contentView:IParentIUIBase = layoutParent ? layoutParent.contentView : IParentIUIBase(_strand);

            var w:Number = contentView.width;
            var h:Number = contentView.height;
			var n:int = contentView.numElements;
			for (var i:int = 0; i < n; i++)
			{
				var child:ILayoutChild = contentView.getElementAt(i) as ILayoutChild;
                var left:Number = ValuesManager.valuesImpl.getValue(child, "left");
                var right:Number = ValuesManager.valuesImpl.getValue(child, "right");
                var top:Number = ValuesManager.valuesImpl.getValue(child, "top");
                var bottom:Number = ValuesManager.valuesImpl.getValue(child, "bottom");
                var ww:Number = w;
                var hh:Number = h;

                if (!isNaN(left))
                {
                    child.x = left;
                    ww -= left;
                }
                if (!isNaN(top))
                {
                    child.y = top;
                    hh -= top;
                }
                var ilc:ILayoutChild;
                if (child is ILayoutChild)
                {
                    ilc = child as ILayoutChild;
                    if (!isNaN(ilc.percentWidth))
                        ilc.setWidth((ww - (isNaN(right) ? 0 : right)) * ilc.percentWidth / 100);
                }
                if (!isNaN(right))
                {
                    if (!isNaN(left))
                        child.width = ww - right;
                    else
                        child.x = w - right - child.width;
                }
                if (child is ILayoutChild)
                {
                    ilc = child as ILayoutChild;
                    if (!isNaN(ilc.percentHeight))
                        ilc.setHeight((hh - (isNaN(bottom) ? 0 : bottom)) * ilc.percentHeight / 100);
                }
                if (!isNaN(bottom))
                {
                    if (!isNaN(top))
                        child.height = hh - bottom;
                    else
                        child.y = h - bottom - child.height;
                }
			}
		}
	}
}

fix sizing logic

fix sizing logic


package org.openforis.collect.presenter {
	/**
	 *
	 * @author Mino Togna
	 * */
	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.EntityDefinitionProxy;
	import org.openforis.collect.model.CollectRecord$Step;
	import org.openforis.collect.model.proxy.RecordProxy;
	import org.openforis.collect.ui.view.MasterView;
	import org.openforis.collect.util.AlertUtil;

	public class MasterPresenter extends AbstractPresenter {

		private var _view:MasterView;
		private var _dataClient:DataClient;

		public function MasterPresenter(view:MasterView) {
			super();

			this._view = view;
			this._dataClient = ClientFactory.dataClient;

			_view.currentState = MasterView.LOADING_STATE;

			/*
			wait for surveys and sessionState loading, then dispatch APPLICATION_INITIALIZED event
			if more than one survey is found, then whow surveySelection view
			*/

			/*
			flow: loading -> surveySelection (optional) -> rootEntitySelection (optional) -> list -> edit
			*/
		}

		override internal function initEventListeners():void {
			//eventDispatcher.addEventListener(ApplicationEvent.APPLICATION_INITIALIZED, applicationInitializedHandler);
			eventDispatcher.addEventListener(UIEvent.ROOT_ENTITY_SELECTED, rootEntitySelectedHandler);
			eventDispatcher.addEventListener(UIEvent.BACK_TO_LIST, backToListHandler);
			eventDispatcher.addEventListener(UIEvent.RECORD_SELECTED, recordSelectedHandler);
			eventDispatcher.addEventListener(UIEvent.RECORD_CREATED, recordCreatedHandler);
		}

		/**
		 * RecordSummary selected from list page
		 * */
		internal function recordSelectedHandler(uiEvent:UIEvent):void {
			var record:RecordProxy = uiEvent.obj as RecordProxy;
			_dataClient.loadRecord(new AsyncResponder(loadRecordResultHandler, faultHandler, record), record.id, record.step);
		}

		/**
		 * New Record created
		 * */
		internal function recordCreatedHandler(uiEvent:UIEvent):void {
			var record:RecordProxy = uiEvent.obj as RecordProxy;
			setActiveRecord(record);
		}

		/**
		 * Record selected in list page loaded from server
		 * */
		protected function loadRecordResultHandler(event:ResultEvent, token:Object = null):void {
			var record:RecordProxy = RecordProxy(event.result);
			setActiveRecord(record);
		}

		protected function setActiveRecord(record:RecordProxy):void {
			Application.activeRecord = record;
			_view.currentState = MasterView.DETAIL_STATE;

			var uiEvent:UIEvent = new UIEvent(UIEvent.ACTIVE_RECORD_CHANGED);
			eventDispatcher.dispatchEvent(uiEvent);
		}

		/**
		 * Root entity selected
		 * */
		internal function rootEntitySelectedHandler(event:UIEvent):void {
			var rootEntityDef:EntityDefinitionProxy = event.obj as EntityDefinitionProxy;
			Application.activeRootEntity = rootEntityDef;
			_view.currentState = MasterView.LIST_STATE;

			var uiEvent:UIEvent = new UIEvent(UIEvent.LOAD_RECORD_SUMMARIES);
			eventDispatcher.dispatchEvent(uiEvent);
		}

		internal function newRecordCreatedHandler(event:UIEvent):void {
			_view.currentState = MasterView.DETAIL_STATE;
		}

		internal function backToListHandler(event:UIEvent):void {
			//reload record summaries
			_view.currentState = MasterView.LIST_STATE;
			var uiEvent:UIEvent = new UIEvent(UIEvent.RELOAD_RECORD_SUMMARIES);
			eventDispatcher.dispatchEvent(uiEvent);
		}

	}
}

package org.openforis.collect.presenter {
	/**
	 *
	 * @author Mino Togna
	 * */
	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.EntityDefinitionProxy;
	import org.openforis.collect.model.CollectRecord$Step;
	import org.openforis.collect.model.proxy.RecordProxy;
	import org.openforis.collect.ui.view.MasterView;
	import org.openforis.collect.util.AlertUtil;

	public class MasterPresenter extends AbstractPresenter {

		private var _view:MasterView;
		private var _dataClient:DataClient;

		public function MasterPresenter(view:MasterView) {
			super();

			this._view = view;
			this._dataClient = ClientFactory.dataClient;

			_view.currentState = MasterView.LOADING_STATE;

			/*
			wait for surveys and sessionState loading, then dispatch APPLICATION_INITIALIZED event
			if more than one survey is found, then whow surveySelection view
			*/

			/*
			flow: loading -> surveySelection (optional) -> rootEntitySelection (optional) -> list -> edit
			*/
		}

		override internal function initEventListeners():void {
			//eventDispatcher.addEventListener(ApplicationEvent.APPLICATION_INITIALIZED, applicationInitializedHandler);
			eventDispatcher.addEventListener(UIEvent.ROOT_ENTITY_SELECTED, rootEntitySelectedHandler);
			eventDispatcher.addEventListener(UIEvent.BACK_TO_LIST, backToListHandler);
			eventDispatcher.addEventListener(UIEvent.RECORD_SELECTED, recordSelectedHandler);
			eventDispatcher.addEventListener(UIEvent.RECORD_CREATED, recordCreatedHandler);
		}

		/**
		 * RecordSummary selected from list page
		 * */
		internal function recordSelectedHandler(uiEvent:UIEvent):void {
			var record:RecordProxy = uiEvent.obj as RecordProxy;
			_dataClient.loadRecord(new AsyncResponder(loadRecordResultHandler, faultHandler, record), record.id, record.step);
		}

		/**
		 * New Record created
		 * */
		internal function recordCreatedHandler(uiEvent:UIEvent):void {
			var record:RecordProxy = uiEvent.obj as RecordProxy;
			setActiveRecord(record);
		}

		/**
		 * Record selected in list page loaded from server
		 * */
		protected function loadRecordResultHandler(event:ResultEvent, token:Object = null):void {
			var record:RecordProxy = RecordProxy(event.result);
			record.markNodesAsVisited();
			setActiveRecord(record);
		}

		protected function setActiveRecord(record:RecordProxy):void {
			Application.activeRecord = record;
			_view.currentState = MasterView.DETAIL_STATE;

			var uiEvent:UIEvent = new UIEvent(UIEvent.ACTIVE_RECORD_CHANGED);
			eventDispatcher.dispatchEvent(uiEvent);
		}

		/**
		 * Root entity selected
		 * */
		internal function rootEntitySelectedHandler(event:UIEvent):void {
			var rootEntityDef:EntityDefinitionProxy = event.obj as EntityDefinitionProxy;
			Application.activeRootEntity = rootEntityDef;
			_view.currentState = MasterView.LIST_STATE;

			var uiEvent:UIEvent = new UIEvent(UIEvent.LOAD_RECORD_SUMMARIES);
			eventDispatcher.dispatchEvent(uiEvent);
		}

		internal function newRecordCreatedHandler(event:UIEvent):void {
			_view.currentState = MasterView.DETAIL_STATE;
		}

		internal function backToListHandler(event:UIEvent):void {
			//reload record summaries
			_view.currentState = MasterView.LIST_STATE;
			var uiEvent:UIEvent = new UIEvent(UIEvent.RELOAD_RECORD_SUMMARIES);
			eventDispatcher.dispatchEvent(uiEvent);
		}

	}
}

Mark all nodes as visited when opening a saved record so that validation errors will be shown


package aerys.minko.render.material.basic
{
	import aerys.minko.render.DrawCall;
	import aerys.minko.render.RenderTarget;
	import aerys.minko.render.shader.SFloat;
	import aerys.minko.render.shader.Shader;
	import aerys.minko.render.shader.ShaderInstance;
	import aerys.minko.render.shader.ShaderSettings;
	import aerys.minko.render.shader.part.DiffuseShaderPart;
	import aerys.minko.render.shader.part.animation.VertexAnimationShaderPart;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.BlendingSource;
	import aerys.minko.type.enum.DepthTest;
	import aerys.minko.type.enum.StencilAction;
	import aerys.minko.type.enum.TriangleCulling;

	/**
	 * The BasicShader is a simple shader program handling hardware vertex
	 * animations (skinning and morphing), a diffuse color or a texture and
	 * a directional light.
	 *
	 *

	 *

	 * 	Property Name
	 * 	Source
	 * 	Type
	 * 	Description
	 * 	Requires
	 *

	 *

	 * 	lightEnabled
	 * 	Scene
	 * 	Boolean
	 * 	Whether the light is enabled or not on the scene.
	 * 	lightDirection, lightAmbient, lightAmbientColor, lightDiffuse, lightDiffuseColor
	 *

	 *

	 * 	lightDirection
	 * 	Scene
	 * 	Vector4
	 * 	The direction of the light.
	 *
	 *

	 *

	 * 	lightAmbient
	 * 	Scene
	 * 	Number
	 * 	The ambient factor of the light.
	 *
	 *

	 *

	 * 	lightAmbientColor
	 * 	Scene
	 * 	uint or Vector4
	 * 	The ambient color of the light.
	 *
	 *

	 *

	 * 	lightDiffuse
	 * 	Scene
	 * 	Number
	 * 	The diffuse factor of the light
	 *
	 *

	 *

	 * 	lightDiffuseColor
	 * 	Scene
	 * 	uint or Vector4
	 * 	The diffuse color of the light.
	 *
	 *

	 *

	 * 	lightEnabled
	 * 	Mesh
	 * 	Boolean
	 * 	Whether the light is enabled or not on the mesh.
	 *
	 *

	 *

	 * 	diffuseMap
	 * 	Mesh
	 * 	TextureResource
	 * 	The texture to use for rendering.
	 *
	 *

	 *

	 * 	diffuseColor
	 * 	Mesh
	 * 	uint or Vector4
	 * 	The color to use for rendering. If the "diffuseMap" binding is set, this
	 * 	value is not used.
	 *
	 *

	 *

	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class BasicShader extends Shader
	{
		private var _vertexAnimationPart	: VertexAnimationShaderPart;
		private var _diffuseShaderPart		: DiffuseShaderPart;

		protected function get diffuse() : DiffuseShaderPart
		{
			return _diffuseShaderPart;
		}

		protected function get vertexAnimation() : VertexAnimationShaderPart
		{
			return _vertexAnimationPart;
		}

		/**
		 * @param priority Default value is 0.
		 * @param target Default value is null.
		 */
		public function BasicShader(target		: RenderTarget	= null,
									priority	: Number		= 0.)
		{
			super(target, priority);

			// init shader parts
			_vertexAnimationPart	= new VertexAnimationShaderPart(this);
			_diffuseShaderPart		= new DiffuseShaderPart(this);
		}

		override protected function initializeSettings(settings : ShaderSettings) : void
		{
			super.initializeSettings(settings);

			// depth test
			settings.depthWriteEnabled = meshBindings.getProperty(
				BasicProperties.DEPTH_WRITE_ENABLED, true
			);
			settings.depthTest = meshBindings.getProperty(
				BasicProperties.DEPTH_TEST, DepthTest.LESS
			);

			settings.triangleCulling = meshBindings.getProperty(
				BasicProperties.TRIANGLE_CULLING, TriangleCulling.BACK
			);

			// stencil operations
			settings.stencilTriangleFace = meshBindings.getProperty(
				BasicProperties.STENCIL_TRIANGLE_FACE, TriangleCulling.BOTH
			);
			settings.stencilCompareMode = meshBindings.getProperty(
				BasicProperties.STENCIL_COMPARE_MODE, DepthTest.EQUAL
			);
			settings.stencilActionOnBothPass = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_BOTH_PASS, StencilAction.KEEP
			);
			settings.stencilActionOnDepthFail = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_DEPTH_FAIL, StencilAction.KEEP
			);
			settings.stencilActionOnDepthPassStencilFail = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL, StencilAction.KEEP
			);
			settings.stencilReferenceValue = meshBindings.getProperty(
				BasicProperties.STENCIL_REFERENCE_VALUE, 0
			);
			settings.stencilReadMask = meshBindings.getProperty(
				BasicProperties.STENCIL_READ_MASK, 255
			);
			settings.stencilWriteMask = meshBindings.getProperty(
				BasicProperties.STENCIL_WRITE_MASK, 255
			);

			// blending and priority
			var blending : uint = meshBindings.getProperty(
				BasicProperties.BLENDING, Blending.OPAQUE
			);

			if ((blending & 0xff) == BlendingSource.SOURCE_ALPHA)
			{
				settings.priority -= 0.5;
				settings.depthSortDrawCalls = true;
			}

			settings.blending			= blending;
			settings.enabled			= true;
			settings.scissorRectangle	= null;
		}

		/**
		 * @return The position of the vertex in clip space (normalized
		 * screen space).
		 *
		 */
		override protected function getVertexPosition() : SFloat
		{
			var triangleCulling	: uint	= meshBindings.getProperty(
				BasicProperties.TRIANGLE_CULLING, TriangleCulling.BACK
			);

			return localToScreen(
				_vertexAnimationPart.getAnimatedVertexPosition()
			);
		}

		/**
		 * @return The pixel color using a diffuse color/map and an optional
		 * directional light.
		 */
		override protected function getPixelColor() : SFloat
		{
			var diffuse	: SFloat = _diffuseShaderPart.getDiffuseColor();

			return diffuse;
		}
	}
}

package aerys.minko.render.material.basic
{
	import aerys.minko.render.RenderTarget;
	import aerys.minko.render.shader.SFloat;
	import aerys.minko.render.shader.Shader;
	import aerys.minko.render.shader.ShaderSettings;
	import aerys.minko.render.shader.part.DiffuseShaderPart;
	import aerys.minko.render.shader.part.animation.VertexAnimationShaderPart;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.BlendingSource;
	import aerys.minko.type.enum.DepthTest;
	import aerys.minko.type.enum.StencilAction;
	import aerys.minko.type.enum.TriangleCulling;

	/**
	 * The BasicShader is a simple shader program handling hardware vertex
	 * animations (skinning and morphing), a diffuse color or a texture and
	 * a directional light.
	 *
	 *

	 *

	 * 	Property Name
	 * 	Source
	 * 	Type
	 * 	Description
	 * 	Requires
	 *

	 *

	 * 	lightEnabled
	 * 	Scene
	 * 	Boolean
	 * 	Whether the light is enabled or not on the scene.
	 * 	lightDirection, lightAmbient, lightAmbientColor, lightDiffuse, lightDiffuseColor
	 *

	 *

	 * 	lightDirection
	 * 	Scene
	 * 	Vector4
	 * 	The direction of the light.
	 *
	 *

	 *

	 * 	lightAmbient
	 * 	Scene
	 * 	Number
	 * 	The ambient factor of the light.
	 *
	 *

	 *

	 * 	lightAmbientColor
	 * 	Scene
	 * 	uint or Vector4
	 * 	The ambient color of the light.
	 *
	 *

	 *

	 * 	lightDiffuse
	 * 	Scene
	 * 	Number
	 * 	The diffuse factor of the light
	 *
	 *

	 *

	 * 	lightDiffuseColor
	 * 	Scene
	 * 	uint or Vector4
	 * 	The diffuse color of the light.
	 *
	 *

	 *

	 * 	lightEnabled
	 * 	Mesh
	 * 	Boolean
	 * 	Whether the light is enabled or not on the mesh.
	 *
	 *

	 *

	 * 	diffuseMap
	 * 	Mesh
	 * 	TextureResource
	 * 	The texture to use for rendering.
	 *
	 *

	 *

	 * 	diffuseColor
	 * 	Mesh
	 * 	uint or Vector4
	 * 	The color to use for rendering. If the "diffuseMap" binding is set, this
	 * 	value is not used.
	 *
	 *

	 *

	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class BasicShader extends Shader
	{
		private var _vertexAnimationPart	: VertexAnimationShaderPart;
		private var _diffuseShaderPart		: DiffuseShaderPart;

		protected function get diffuse() : DiffuseShaderPart
		{
			return _diffuseShaderPart;
		}

		protected function get vertexAnimation() : VertexAnimationShaderPart
		{
			return _vertexAnimationPart;
		}

		/**
		 * @param priority Default value is 0.
		 * @param target Default value is null.
		 */
		public function BasicShader(target		: RenderTarget	= null,
									priority	: Number		= 0.)
		{
			super(target, priority);

			// init shader parts
			_vertexAnimationPart	= new VertexAnimationShaderPart(this);
			_diffuseShaderPart		= new DiffuseShaderPart(this);
		}

		override protected function initializeSettings(settings : ShaderSettings) : void
		{
			super.initializeSettings(settings);

			// depth test
			settings.depthWriteEnabled = meshBindings.getProperty(
				BasicProperties.DEPTH_WRITE_ENABLED, true
			);
			settings.depthTest = meshBindings.getProperty(
				BasicProperties.DEPTH_TEST, DepthTest.LESS
			);

			settings.triangleCulling = meshBindings.getProperty(
				BasicProperties.TRIANGLE_CULLING, TriangleCulling.BACK
			);

			// stencil operations
			settings.stencilTriangleFace = meshBindings.getProperty(
				BasicProperties.STENCIL_TRIANGLE_FACE, TriangleCulling.BOTH
			);
			settings.stencilCompareMode = meshBindings.getProperty(
				BasicProperties.STENCIL_COMPARE_MODE, DepthTest.EQUAL
			);
			settings.stencilActionOnBothPass = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_BOTH_PASS, StencilAction.KEEP
			);
			settings.stencilActionOnDepthFail = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_DEPTH_FAIL, StencilAction.KEEP
			);
			settings.stencilActionOnDepthPassStencilFail = meshBindings.getProperty(
				BasicProperties.STENCIL_ACTION_DEPTH_PASS_STENCIL_FAIL, StencilAction.KEEP
			);
			settings.stencilReferenceValue = meshBindings.getProperty(
				BasicProperties.STENCIL_REFERENCE_VALUE, 0
			);
			settings.stencilReadMask = meshBindings.getProperty(
				BasicProperties.STENCIL_READ_MASK, 255
			);
			settings.stencilWriteMask = meshBindings.getProperty(
				BasicProperties.STENCIL_WRITE_MASK, 255
			);

			// blending and priority
			var blending : uint = meshBindings.getProperty(
				BasicProperties.BLENDING, Blending.OPAQUE
			);

			if ((blending & 0xff) == BlendingSource.SOURCE_ALPHA)
			{
				settings.priority -= 0.5;
				settings.depthSortDrawCalls = true;
			}

			settings.blending			= blending;
			settings.enabled			= true;
			settings.scissorRectangle	= null;
		}

		/**
		 * @return The position of the vertex in clip space (normalized
		 * screen space).
		 *
		 */
		override protected function getVertexPosition() : SFloat
		{
			return localToScreen(
				_vertexAnimationPart.getAnimatedVertexPosition()
			);
		}

		/**
		 * @return The pixel color using a diffuse color/map and an optional
		 * directional light.
		 */
		override protected function getPixelColor() : SFloat
		{
			return _diffuseShaderPart.getDiffuseColor();
		}
	}
}

remove useless imports

remove useless imports


/*
 *  FRESteamWorks.h
 *  This file is part of FRESteamWorks.
 *
 *  Created by David ´Oldes´ Oliva on 3/29/12.
 *  Contributors: Ventero <http://github.com/Ventero>
 *  Copyright (c) 2012 Amanita Design. All rights reserved.
 *  Copyright (c) 2012-2013 Level Up Labs, LLC. All rights reserved.
 */

package
{
	import com.amanitadesign.steam.FRESteamWorks;
	import com.amanitadesign.steam.SteamConstants;
	import com.amanitadesign.steam.SteamEvent;
	import com.amanitadesign.steam.WorkshopConstants;

	import flash.desktop.NativeApplication;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.display.StageDisplayState;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.ByteArray;

	public class FRESteamWorksTest extends Sprite
	{
		public var Steamworks:FRESteamWorks = new FRESteamWorks();
		public var tf:TextField;

		private var _buttonPos:int = 5;
		private var _appId:uint;

		public function FRESteamWorksTest()
		{
			tf = new TextField();
			tf.x = 160;
			tf.width = stage.stageWidth - tf.x;
			tf.height = stage.stageHeight;
			addChild(tf);

			addButton("Check stats/achievements", checkAchievements);
			addButton("Toggle achievement", toggleAchievement);
			addButton("Toggle cloud enabled", toggleCloudEnabled);
			addButton("Toggle file", toggleFile);
			addButton("Publish file", publishFile);
			addButton("Toggle fullscreen", toggleFullscreen);
			addButton("Show Friends overlay", activateOverlay);

			Steamworks.addEventListener(SteamEvent.STEAM_RESPONSE, onSteamResponse);
			NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit);
			try {
				//Steamworks.useCrashHandler(480, "1.0", "Feb 20 2013", "21:42:20");
				if(!Steamworks.init()){
					log("STEAMWORKS API is NOT available");
					return;
				}

				log("STEAMWORKS API is available\n");
				log("User ID: " + Steamworks.getUserID());
				_appId = Steamworks.getAppID();
				log("App ID: " + _appId);
				log("Persona name: " + Steamworks.getPersonaName());
				log("isCloudEnabledForApp() == "+Steamworks.isCloudEnabledForApp() );
				log("getFileCount() == "+Steamworks.getFileCount() );
				log("fileExists('test.txt') == "+Steamworks.fileExists('test.txt') );

				Steamworks.resetAllStats(true);
			} catch(e:Error) {
				log("*** ERROR ***");
				log(e.message);
				log(e.getStackTrace());
			}

		}
		private function log(value:String):void{
			tf.appendText(value+"\n");
			tf.scrollV = tf.maxScrollV;
		}

		public function writeFileToCloud(fileName:String, data:String):Boolean {
			var dataOut:ByteArray = new ByteArray();
			dataOut.writeUTFBytes(data);
			return Steamworks.fileWrite(fileName, dataOut);
		}

		public function readFileFromCloud(fileName:String):String {
			var dataIn:ByteArray = new ByteArray();
			var result:String;
			dataIn.position = 0;
			dataIn.length = Steamworks.getFileSize(fileName);

			if(dataIn.length>0 && Steamworks.fileRead(fileName,dataIn)){
				result = dataIn.readUTFBytes(dataIn.length);
			}
			return result;
		}

		private function checkAchievements(e:Event = null):void {
			if(!Steamworks.isReady) return;

			// current stats and achievement ids are from steam example app
			log("isAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.isAchievement("ACH_WIN_ONE_GAME"));
			log("isAchievement('ACH_TRAVEL_FAR_SINGLE') == "+Steamworks.isAchievement("ACH_TRAVEL_FAR_SINGLE"));
			log("setStatFloat('FeetTraveled') == "+Steamworks.setStatFloat('FeetTraveled', 21.3));
			log("setStatInt('NumGames', 2) == "+Steamworks.setStatInt('NumGames', 2));
			Steamworks.storeStats();
			log("getStatInt('NumGames') == "+Steamworks.getStatInt('NumGames'));
			log("getStatFloat('FeetTraveled') == "+Steamworks.getStatFloat('FeetTraveled'));
		}

		private function toggleAchievement(e:Event = null):void{
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.isAchievement("ACH_WIN_ONE_GAME");
			log("isAchievement('ACH_WIN_ONE_GAME') == " + result);
			if(!result) {
				log("setAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.setAchievement("ACH_WIN_ONE_GAME"));
			} else {
				log("clearAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.clearAchievement("ACH_WIN_ONE_GAME"));
			}
		}

		private function toggleCloudEnabled(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var enabled:Boolean = Steamworks.isCloudEnabledForApp();
			log("isCloudEnabledForApp() == " + enabled);
			log("setCloudEnabledForApp(" + !enabled + ") == " + Steamworks.setCloudEnabledForApp(!enabled));
			log("isCloudEnabledForApp() == " + Steamworks.isCloudEnabledForApp());
		}

		private function toggleFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.fileExists('test.txt');
			log("fileExists('test.txt') == " + result);
			if(result){
				log("readFileFromCloud('test.txt') == "+readFileFromCloud('test.txt') );
				log("fileDelete('test.txt') == "+Steamworks.fileDelete('test.txt'));
			} else {
				log("writeFileToCloud('test.txt','click') == "+writeFileToCloud('test.txt','click'));
			}
		}

		private function publishFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var res:Boolean = Steamworks.publishWorkshopFile("test.txt", "", _appId,
				"Test.txt", "Test.txt", WorkshopConstants.VISIBILITY_Private,
				["TestTag"], WorkshopConstants.FILETYPE_Community);
			log("publishWorkshopFile('test.txt' ...) == " + res);
		}

		private function toggleFullscreen(e:Event = null):void {
			if(stage.displayState == StageDisplayState.NORMAL)
				stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
			else
				stage.displayState = StageDisplayState.NORMAL;
		}

		private function activateOverlay(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("activateGameOverlay('Friends') == " + Steamworks.activateGameOverlay("Friends"));
		}

		private function onSteamResponse(e:SteamEvent):void{
			switch(e.req_type){
				case SteamConstants.RESPONSE_OnUserStatsStored:
					log("RESPONSE_OnUserStatsStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnUserStatsReceived:
					log("RESPONSE_OnUserStatsReceived: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnAchievementStored:
					log("RESPONSE_OnAchievementStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnPublishWorkshopFile:
					log("RESPONSE_OnPublishWorkshopFile: " + e.response);
					log("File published as " + Steamworks.publishWorkshopFileResult());
				default:
					log("STEAMresponse type:"+e.req_type+" response:"+e.response);
			}
		}

		private function onExit(e:Event):void{
			log("Exiting application, cleaning up");
			Steamworks.dispose();
		}

		private function addButton(label:String, callback:Function):void {
			var button:Sprite = new Sprite();
			button.graphics.beginFill(0xaaaaaa);
			button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
			button.graphics.endFill();
			button.buttonMode = true;
			button.useHandCursor = true;
			button.addEventListener(MouseEvent.CLICK, callback);
			button.x = 5;
			button.y = _buttonPos;
			_buttonPos += button.height + 5;

			var text:TextField = new TextField();
			text.text = label;
			text.width = 140;
			text.height = 25;
			text.x = 5;
			text.y = 5;
			text.mouseEnabled = false;

			button.addChild(text);
			addChild(button);
		}
	}
}

/*
 *  FRESteamWorks.h
 *  This file is part of FRESteamWorks.
 *
 *  Created by David ´Oldes´ Oliva on 3/29/12.
 *  Contributors: Ventero <http://github.com/Ventero>
 *  Copyright (c) 2012 Amanita Design. All rights reserved.
 *  Copyright (c) 2012-2013 Level Up Labs, LLC. All rights reserved.
 */

package
{
	import com.amanitadesign.steam.FRESteamWorks;
	import com.amanitadesign.steam.SteamConstants;
	import com.amanitadesign.steam.SteamEvent;
	import com.amanitadesign.steam.WorkshopConstants;

	import flash.desktop.NativeApplication;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.display.StageDisplayState;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.ByteArray;

	public class FRESteamWorksTest extends Sprite
	{
		public var Steamworks:FRESteamWorks = new FRESteamWorks();
		public var tf:TextField;

		private var _buttonPos:int = 5;
		private var _appId:uint;

		public function FRESteamWorksTest()
		{
			tf = new TextField();
			tf.x = 160;
			tf.width = stage.stageWidth - tf.x;
			tf.height = stage.stageHeight;
			addChild(tf);

			addButton("Check stats/achievements", checkAchievements);
			addButton("Toggle achievement", toggleAchievement);
			addButton("Toggle cloud enabled", toggleCloudEnabled);
			addButton("Toggle file", toggleFile);
			addButton("Publish file", publishFile);
			addButton("Toggle fullscreen", toggleFullscreen);
			addButton("Show Friends overlay", activateOverlay);
			addButton("List subscribed files", enumerateSubscribedFiles);

			Steamworks.addEventListener(SteamEvent.STEAM_RESPONSE, onSteamResponse);
			NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit);
			try {
				//Steamworks.useCrashHandler(480, "1.0", "Feb 20 2013", "21:42:20");
				if(!Steamworks.init()){
					log("STEAMWORKS API is NOT available");
					return;
				}

				log("STEAMWORKS API is available\n");
				log("User ID: " + Steamworks.getUserID());
				_appId = Steamworks.getAppID();
				log("App ID: " + _appId);
				log("Persona name: " + Steamworks.getPersonaName());
				log("isCloudEnabledForApp() == "+Steamworks.isCloudEnabledForApp() );
				log("getFileCount() == "+Steamworks.getFileCount() );
				log("fileExists('test.txt') == "+Steamworks.fileExists('test.txt') );

				Steamworks.resetAllStats(true);
			} catch(e:Error) {
				log("*** ERROR ***");
				log(e.message);
				log(e.getStackTrace());
			}

		}
		private function log(value:String):void{
			tf.appendText(value+"\n");
			tf.scrollV = tf.maxScrollV;
		}

		public function writeFileToCloud(fileName:String, data:String):Boolean {
			var dataOut:ByteArray = new ByteArray();
			dataOut.writeUTFBytes(data);
			return Steamworks.fileWrite(fileName, dataOut);
		}

		public function readFileFromCloud(fileName:String):String {
			var dataIn:ByteArray = new ByteArray();
			var result:String;
			dataIn.position = 0;
			dataIn.length = Steamworks.getFileSize(fileName);

			if(dataIn.length>0 && Steamworks.fileRead(fileName,dataIn)){
				result = dataIn.readUTFBytes(dataIn.length);
			}
			return result;
		}

		private function checkAchievements(e:Event = null):void {
			if(!Steamworks.isReady) return;

			// current stats and achievement ids are from steam example app
			log("isAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.isAchievement("ACH_WIN_ONE_GAME"));
			log("isAchievement('ACH_TRAVEL_FAR_SINGLE') == "+Steamworks.isAchievement("ACH_TRAVEL_FAR_SINGLE"));
			log("setStatFloat('FeetTraveled') == "+Steamworks.setStatFloat('FeetTraveled', 21.3));
			log("setStatInt('NumGames', 2) == "+Steamworks.setStatInt('NumGames', 2));
			Steamworks.storeStats();
			log("getStatInt('NumGames') == "+Steamworks.getStatInt('NumGames'));
			log("getStatFloat('FeetTraveled') == "+Steamworks.getStatFloat('FeetTraveled'));
		}

		private function toggleAchievement(e:Event = null):void{
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.isAchievement("ACH_WIN_ONE_GAME");
			log("isAchievement('ACH_WIN_ONE_GAME') == " + result);
			if(!result) {
				log("setAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.setAchievement("ACH_WIN_ONE_GAME"));
			} else {
				log("clearAchievement('ACH_WIN_ONE_GAME') == "+Steamworks.clearAchievement("ACH_WIN_ONE_GAME"));
			}
		}

		private function toggleCloudEnabled(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var enabled:Boolean = Steamworks.isCloudEnabledForApp();
			log("isCloudEnabledForApp() == " + enabled);
			log("setCloudEnabledForApp(" + !enabled + ") == " + Steamworks.setCloudEnabledForApp(!enabled));
			log("isCloudEnabledForApp() == " + Steamworks.isCloudEnabledForApp());
		}

		private function toggleFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var result:Boolean = Steamworks.fileExists('test.txt');
			log("fileExists('test.txt') == " + result);
			if(result){
				log("readFileFromCloud('test.txt') == "+readFileFromCloud('test.txt') );
				log("fileDelete('test.txt') == "+Steamworks.fileDelete('test.txt'));
			} else {
				log("writeFileToCloud('test.txt','click') == "+writeFileToCloud('test.txt','click'));
			}
		}

		private function publishFile(e:Event = null):void {
			if(!Steamworks.isReady) return;

			var res:Boolean = Steamworks.publishWorkshopFile("test.txt", "", _appId,
				"Test.txt", "Test.txt", WorkshopConstants.VISIBILITY_Private,
				["TestTag"], WorkshopConstants.FILETYPE_Community);
			log("publishWorkshopFile('test.txt' ...) == " + res);
		}

		private function toggleFullscreen(e:Event = null):void {
			if(stage.displayState == StageDisplayState.NORMAL)
				stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
			else
				stage.displayState = StageDisplayState.NORMAL;
		}

		private function activateOverlay(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("activateGameOverlay('Friends') == " + Steamworks.activateGameOverlay("Friends"));
		}

		private function enumerateSubscribedFiles(e:Event = null):void {
			if(!Steamworks.isReady) return;

			log("enumerateUserSubscribedFiles(0) == " + Steamworks.enumerateUserSubscribedFiles(0));
		}

		private function onSteamResponse(e:SteamEvent):void{
			switch(e.req_type){
				case SteamConstants.RESPONSE_OnUserStatsStored:
					log("RESPONSE_OnUserStatsStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnUserStatsReceived:
					log("RESPONSE_OnUserStatsReceived: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnAchievementStored:
					log("RESPONSE_OnAchievementStored: "+e.response);
					break;
				case SteamConstants.RESPONSE_OnPublishWorkshopFile:
					log("RESPONSE_OnPublishWorkshopFile: " + e.response);
					var file:String = Steamworks.publishWorkshopFileResult();
					log("File published as " + file);
					log("subscribePublishedFile(...) == " + Steamworks.subscribePublishedFile(file));
					break;
				case SteamConstants.RESPONSE_OnEnumerateUserSubscribedFiles:
					log("RESPONSE_OnEnumerateUserSubscribedFiles: " + e.response);
					var result:SubscribedFilesResult = Steamworks.enumerateUserSubscribedFilesResult();
					log("User subscribed files: " + result.resultsReturned + "/" + result.totalResults);
					for(var i:int = 0; i < result.resultsReturned; i++)
						log(i + ": " + result.publishedFileId[i] + " (" + result.timeSubscribed[i] + ")");
					break;
				default:
					log("STEAMresponse type:"+e.req_type+" response:"+e.response);
			}
		}

		private function onExit(e:Event):void{
			log("Exiting application, cleaning up");
			Steamworks.dispose();
		}

		private function addButton(label:String, callback:Function):void {
			var button:Sprite = new Sprite();
			button.graphics.beginFill(0xaaaaaa);
			button.graphics.drawRoundRect(0, 0, 150, 30, 5, 5);
			button.graphics.endFill();
			button.buttonMode = true;
			button.useHandCursor = true;
			button.addEventListener(MouseEvent.CLICK, callback);
			button.x = 5;
			button.y = _buttonPos;
			_buttonPos += button.height + 5;

			var text:TextField = new TextField();
			text.text = label;
			text.width = 140;
			text.height = 25;
			text.x = 5;
			text.y = 5;
			text.mouseEnabled = false;

			button.addChild(text);
			addChild(button);
		}
	}
}

Add subscribed file test

Add subscribed file test


package org.denivip.osmf.elements
{
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.getTimer;

	import org.denivip.osmf.elements.m3u8Classes.M3U8PlaylistParser;
	import org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingHLSNetLoader;
	import org.osmf.elements.VideoElement;
	import org.osmf.elements.proxyClasses.LoadFromDocumentLoadTrait;
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.events.ParseEvent;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.media.MediaElement;
	import org.osmf.media.MediaResourceBase;
	import org.osmf.media.URLResource;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.LoadTrait;
	import org.osmf.traits.LoaderBase;

	/**
	 * Loader for .m3u8 playlist file.
	 * Works like a F4MLoader
	 */
	public class M3U8Loader extends LoaderBase
	{

		private var _loadTrait:LoadTrait;
		private var _parser:M3U8PlaylistParser = null;
		private var _loadTime:int = 0;
		private var _playlistLoader:URLLoader = null;

		public function M3U8Loader(){
			super();
		}

        public static function canHandle(resource:MediaResourceBase):Boolean
        {
            if (resource !== null && resource is URLResource) {
                var urlResource:URLResource = URLResource(resource);
                if (urlResource.url.search(/(https?|file)\:\/\/.*?\.m3u8(\?.*)?/i) !== -1) {
                    return true;
                }

                var contentType:Object = urlResource.getMetadataValue("content-type");
                if (contentType && contentType is String) {
                    // If the filename doesn't include a .m3u8 extension, but
                    // explicit content-type metadata is found on the
                    // URLResource, we can handle it.  Must be either of:
                    // - "application/x-mpegURL"
                    // - "vnd.apple.mpegURL"
                    if ((contentType as String).search(/(application\/x-mpegURL|vnd.apple.mpegURL)/i) !== -1) {
                        return true;
                    }
                }
            }
            return false;
        }

		override public function canHandleResource(resource:MediaResourceBase):Boolean{
            return canHandle(resource);
		}

		private function onError(e:ErrorEvent):void
		{
			removeListeners();

			updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
			_loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(0, 'This video is not available.')));
		}

		private function onComplete(e:Event):void
		{
			removeListeners();

			try{
				CONFIG::LOGGING
				{
					_loadTime = getTimer() - _loadTime;
					var url:String = _loadTrait.resource['url'];
					logger.info("Playlist {0} loaded", url);
					logger.info("size = {0}Kb", (_playlistLoader.bytesLoaded/1024).toFixed(3));
					logger.info("load time = {0} sec", (_loadTime/1000));
				}

				var resData:String = String((e.target as URLLoader).data);

				_parser = new M3U8PlaylistParser();

				_parser.addEventListener(ParseEvent.PARSE_COMPLETE, parseComplete);
				_parser.addEventListener(ParseEvent.PARSE_ERROR, parseError);

				_parser.parse(resData, URLResource(_loadTrait.resource));
			}catch(err:Error){
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(err.errorID, err.message)));
			}
		}

		private function onHTTPStatus(event:flash.events.HTTPStatusEvent):void
		{
			if( event.status >= 400 )
			{
				// some 400-level fail, let's forward this out to jscript land
				removeListeners();
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				_loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(event.status, "")));
			}
		}

		private function removeListeners():void
		{
			_playlistLoader.removeEventListener(Event.COMPLETE, onComplete);
			_playlistLoader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			_playlistLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			_playlistLoader.removeEventListener(flash.events.HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);
		}

		override protected function executeLoad(loadTrait:LoadTrait):void
		{
			if( _playlistLoader != null )
			{
				// There's some previous request that is lingering in abject misery--kill it dead.  This is
				// important because otherwise you can have strange race conditions happen when multiple
				// executeLoad calls are fired in rapid succession.
				// Remove all listeners, close it (which under the hood cancels everything), set to null.
				this.removeListeners();
				_playlistLoader.close();
				_playlistLoader = null;

			}

			if( _parser != null )
			{
				// If there's an outstanding parser, also kill that dead.
				removeParserListeners();
				_parser = null;
			}

			_loadTrait = loadTrait;
			updateLoadTrait(loadTrait, LoadState.LOADING);

			_playlistLoader = new URLLoader(new URLRequest(URLResource(loadTrait.resource).url));
			_playlistLoader.addEventListener(Event.COMPLETE, onComplete);
			_playlistLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
			_playlistLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			_playlistLoader.addEventListener(flash.events.HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);

			CONFIG::LOGGING
				{
					_loadTime = getTimer();
				}
		}

		override protected function executeUnload(loadTrait:LoadTrait):void
		{
			updateLoadTrait(loadTrait, LoadState.UNINITIALIZED);
		}

		private function removeParserListeners():void
		{
			_parser.removeEventListener(ParseEvent.PARSE_COMPLETE, parseComplete);
			_parser.removeEventListener(ParseEvent.PARSE_ERROR, parseError);
		}

		private function parseComplete(event:ParseEvent):void
		{
			removeParserListeners();
			finishPlaylistLoading(MediaResourceBase(event.data));
		}

		private function parseError(event:ParseEvent):void{
			removeParserListeners();
		}

		private function finishPlaylistLoading(resource:MediaResourceBase):void{
			try{
				var loadedElem:MediaElement = new VideoElement(null, new HTTPStreamingHLSNetLoader());
				loadedElem.resource = resource;
				VideoElement(loadedElem).smoothing = true;

				LoadFromDocumentLoadTrait(_loadTrait).mediaElement = loadedElem;

				updateLoadTrait(_loadTrait, LoadState.READY);
			}catch(e:Error){
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				_loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(e.errorID, e.message)));
			}
		}

		CONFIG::LOGGING
		{
			protected var logger:Logger = Log.getLogger("org.denivip.osmf.elements.M3U8Loader") as Logger;
		}
	}
}

package org.denivip.osmf.elements
{
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.utils.getTimer;

	import org.denivip.osmf.elements.m3u8Classes.M3U8PlaylistParser;
	import org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingHLSNetLoader;
	import org.osmf.elements.VideoElement;
	import org.osmf.elements.proxyClasses.LoadFromDocumentLoadTrait;
	import org.osmf.events.MediaError;
	import org.osmf.events.MediaErrorEvent;
	import org.osmf.events.ParseEvent;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.media.MediaElement;
	import org.osmf.media.MediaResourceBase;
	import org.osmf.media.URLResource;
	import org.osmf.traits.LoadState;
	import org.osmf.traits.LoadTrait;
	import org.osmf.traits.LoaderBase;

	/**
	 * Loader for .m3u8 playlist file.
	 * Works like a F4MLoader
	 */
	public class M3U8Loader extends LoaderBase
	{

		private var _loadTrait:LoadTrait;
		private var _parser:M3U8PlaylistParser = null;
		private var _loadTime:int = 0;
		private var _playlistLoader:URLLoader = null;
        private var _errorHit:Boolean = false;  // So we only handle HTTP errors once; handling stuff twice dorks things.

		public function M3U8Loader(){
			super();
		}

        public static function canHandle(resource:MediaResourceBase):Boolean
        {
            if (resource !== null && resource is URLResource) {
                var urlResource:URLResource = URLResource(resource);
                if (urlResource.url.search(/(https?|file)\:\/\/.*?\.m3u8(\?.*)?/i) !== -1) {
                    return true;
                }

                var contentType:Object = urlResource.getMetadataValue("content-type");
                if (contentType && contentType is String) {
                    // If the filename doesn't include a .m3u8 extension, but
                    // explicit content-type metadata is found on the
                    // URLResource, we can handle it.  Must be either of:
                    // - "application/x-mpegURL"
                    // - "vnd.apple.mpegURL"
                    if ((contentType as String).search(/(application\/x-mpegURL|vnd.apple.mpegURL)/i) !== -1) {
                        return true;
                    }
                }
            }
            return false;
        }

		override public function canHandleResource(resource:MediaResourceBase):Boolean{
            return canHandle(resource);
		}

		private function onError(e:ErrorEvent):void
		{
            if( !_errorHit) {
                _errorHit = true;
                updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
                _loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(0, 'This video is not available.')));
            }
		}

		private function onComplete(e:Event):void
		{
			removeListeners();

			try
            {
				CONFIG::LOGGING
				{
					_loadTime = getTimer() - _loadTime;
					var url:String = _loadTrait.resource['url'];
					logger.info("Playlist {0} loaded", url);
					logger.info("size = {0}Kb", (_playlistLoader.bytesLoaded/1024).toFixed(3));
					logger.info("load time = {0} sec", (_loadTime/1000));
				}

				var resData:String = String((e.target as URLLoader).data);

				_parser = new M3U8PlaylistParser();

				_parser.addEventListener(ParseEvent.PARSE_COMPLETE, parseComplete);
				_parser.addEventListener(ParseEvent.PARSE_ERROR, parseError);

				_parser.parse(resData, URLResource(_loadTrait.resource));
			}catch(err:Error){
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(err.errorID, err.message)));
			}
		}

		private function onHTTPStatus(event:flash.events.HTTPStatusEvent):void
		{
			if( event.status >= 400 && !_errorHit )
			{
				// some 400-level fail, let's forward this out to jscript land
				_errorHit = true;
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				_loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(event.status, "")));
			}
		}

		private function removeListeners():void
		{
			_playlistLoader.removeEventListener(Event.COMPLETE, onComplete);
			_playlistLoader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			_playlistLoader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			_playlistLoader.removeEventListener(flash.events.HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);
		}

		override protected function executeLoad(loadTrait:LoadTrait):void
		{
			if( _playlistLoader != null )
			{
				// There's some previous request that is lingering in abject misery--kill it dead.  This is
				// important because otherwise you can have strange race conditions happen when multiple
				// executeLoad calls are fired in rapid succession.
				// Remove all listeners, close it (which under the hood cancels everything), set to null.
				this.removeListeners();
				_playlistLoader.close();
				_playlistLoader = null;
			}

            // Reset our error hit logic since we're trying again.
            _errorHit = false;

			if( _parser != null )
			{
				// If there's an outstanding parser, also kill that dead.
				removeParserListeners();
				_parser = null;
			}

			_loadTrait = loadTrait;
			updateLoadTrait(loadTrait, LoadState.LOADING);

			_playlistLoader = new URLLoader();
			_playlistLoader.addEventListener(Event.COMPLETE, onComplete);
			_playlistLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
			_playlistLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			_playlistLoader.addEventListener(flash.events.HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);
            _playlistLoader.load(new URLRequest(URLResource(loadTrait.resource).url));

			CONFIG::LOGGING
            {
                _loadTime = getTimer();
            }
		}

		override protected function executeUnload(loadTrait:LoadTrait):void
		{
			updateLoadTrait(loadTrait, LoadState.UNINITIALIZED);
		}

		private function removeParserListeners():void
		{
			_parser.removeEventListener(ParseEvent.PARSE_COMPLETE, parseComplete);
			_parser.removeEventListener(ParseEvent.PARSE_ERROR, parseError);
		}

		private function parseComplete(event:ParseEvent):void
		{
			removeParserListeners();
			finishPlaylistLoading(MediaResourceBase(event.data));
		}

		private function parseError(event:ParseEvent):void{
			removeParserListeners();
		}

		private function finishPlaylistLoading(resource:MediaResourceBase):void{
			try{
				var loadedElem:MediaElement = new VideoElement(null, new HTTPStreamingHLSNetLoader());
				loadedElem.resource = resource;
				VideoElement(loadedElem).smoothing = true;

				LoadFromDocumentLoadTrait(_loadTrait).mediaElement = loadedElem;

				updateLoadTrait(_loadTrait, LoadState.READY);
			}catch(e:Error){
				updateLoadTrait(_loadTrait, LoadState.LOAD_ERROR);
				_loadTrait.dispatchEvent(new MediaErrorEvent(MediaErrorEvent.MEDIA_ERROR, false, false, new MediaError(e.errorID, e.message)));
			}
		}

		CONFIG::LOGGING
		{
			protected var logger:Logger = Log.getLogger("org.denivip.osmf.elements.M3U8Loader") as Logger;
		}
	}
}

Fix two race conditions: event handlers were being added _after_ the loader was supplied a URL, and the error event handlers should not be removing the error handlers. If an event handler is removed and there's an outstanding event, that results in an unhandled exception


package dolly {

import dolly.core.dolly_internal;
import dolly.core.errors.CloningError;
import dolly.core.metadata.MetadataName;
import dolly.utils.ClassUtil;

import flash.utils.ByteArray;
import flash.utils.Dictionary;

import org.as3commons.reflect.Accessor;
import org.as3commons.reflect.AccessorAccess;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;

use namespace dolly_internal;

public class Cloner {

	private static var _isClassPreparedForCloningMap:Dictionary = new Dictionary();

	private static function isTypeCloneable(type:Type):Boolean {
		return type.hasMetadata(MetadataName.CLONEABLE);
	}

	private static function isTypePreparedForCloning(type:Type):Boolean {
		return _isClassPreparedForCloningMap[type.clazz];
	}

	private static function failIfTypeIsNotCloneable(type:Type):void {
		if (!isTypeCloneable(type)) {
			throw new CloningError(
					CloningError.CLASS_IS_NOT_CLONEABLE_MESSAGE,
					CloningError.CLASS_IS_NOT_CLONEABLE_CODE
			);
		}
	}

	private static function prepareTypeForCloning(type:Type):void {
		if (isTypePreparedForCloning(type)) {
			return;
		}

		ClassUtil.registerAliasFor(type.clazz);

		_isClassPreparedForCloningMap[type.clazz] = true;
	}

	dolly_internal static function findAllWritableFieldsForType(type:Type):Vector.<Field> {
		failIfTypeIsNotCloneable(type);

		if (!isTypePreparedForCloning(type)) {
			prepareTypeForCloning(type);
		}

		const result:Vector.<Field> = new Vector.<Field>();

		for each(var field:Field in type.properties) {
			if (!(field is Accessor) || (field as Accessor).access == AccessorAccess.READ_WRITE) {
				result.push(field);
			}
		}

		return result;
	}

	dolly_internal static function doClone(source:*, type:Type = null):* {
		if (!type) {
			type = Type.forInstance(source);
		}

		prepareTypeForCloning(type);

		const fieldsToClone:Vector.<Field> = findAllWritableFieldsForType(type);

		for each(var field:Field in fieldsToClone) {
			var fieldType:Type = field.type;
			if (isTypeCloneable(fieldType)) {
				prepareTypeForCloning(fieldType);
			}
		}

		const byteArray:ByteArray = new ByteArray();
		byteArray.writeObject(source);
		byteArray.position = 0;

		const clonedInstance:* = byteArray.readObject();

		return clonedInstance;
	}

	public static function clone(source:*):* {
		const type:Type = Type.forInstance(source);

		failIfTypeIsNotCloneable(type);

		return doClone(source, type);
	}
}
}

package dolly {

import dolly.core.dolly_internal;
import dolly.core.errors.CloningError;
import dolly.core.metadata.MetadataName;
import dolly.utils.ClassUtil;

import flash.utils.ByteArray;
import flash.utils.Dictionary;

import org.as3commons.reflect.Accessor;
import org.as3commons.reflect.AccessorAccess;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;

use namespace dolly_internal;

public class Cloner {

	private static var _isClassPreparedForCloningMap:Dictionary = new Dictionary();

	private static function isTypeCloneable(type:Type):Boolean {
		return type.hasMetadata(MetadataName.CLONEABLE);
	}

	private static function isTypePreparedForCloning(type:Type):Boolean {
		return _isClassPreparedForCloningMap[type.clazz];
	}

	private static function failIfTypeIsNotCloneable(type:Type):void {
		if (!isTypeCloneable(type)) {
			throw new CloningError(
					CloningError.CLASS_IS_NOT_CLONEABLE_MESSAGE,
					CloningError.CLASS_IS_NOT_CLONEABLE_CODE
			);
		}
	}

	private static function prepareTypeForCloning(type:Type):void {
		if (isTypePreparedForCloning(type)) {
			return;
		}

		ClassUtil.registerAliasFor(type.clazz);

		_isClassPreparedForCloningMap[type.clazz] = true;

		const fieldsToClone:Vector.<Field> = findAllWritableFieldsForType(type);
		for each(var field:Field in fieldsToClone) {
			var fieldType:Type = field.type;
			if (isTypeCloneable(fieldType)) {
				prepareTypeForCloning(fieldType);
			}
		}
	}

	dolly_internal static function findAllWritableFieldsForType(type:Type):Vector.<Field> {
		failIfTypeIsNotCloneable(type);

		if (!isTypePreparedForCloning(type)) {
			prepareTypeForCloning(type);
		}

		const result:Vector.<Field> = new Vector.<Field>();

		for each(var field:Field in type.properties) {
			if (!(field is Accessor) || (field as Accessor).access == AccessorAccess.READ_WRITE) {
				result.push(field);
			}
		}

		return result;
	}

	dolly_internal static function doClone(source:*, type:Type = null):* {
		if (!type) {
			type = Type.forInstance(source);
		}

		prepareTypeForCloning(type);

		const byteArray:ByteArray = new ByteArray();
		byteArray.writeObject(source);
		byteArray.position = 0;

		const clonedInstance:* = byteArray.readObject();

		return clonedInstance;
	}

	public static function clone(source:*):* {
		const type:Type = Type.forInstance(source);

		failIfTypeIsNotCloneable(type);

		return doClone(source, type);
	}
}
}

Move code preparing cloneable fields of type for cloning to method Cloner.prepareTypeForCloning().