In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

flash on 2015-7-26

Copyright (c) 2010-2011, Kundan Singh. See website for LICENSING.
Copyright (c) 2010-2011, VoIP Researcher.
Copyright (c) 2011-2012, Intencity Cloud Technologies.

The main application for Flash-VideoIO hides the internal implementation of VideoIOInternal
and translates the API using ExternalInterface. This model allows clear separation between
the application interface and the implementation.
Get Adobe Flash player
by marek.ulwanski 26 Jul 2015
    Embed
/**
 * Copyright marek.ulwanski ( http://wonderfl.net/user/marek.ulwanski )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/nhAt
 */

/*  Copyright (c) 2010-2011, Kundan Singh. See website for LICENSING.*/
/*  Copyright (c) 2010-2011, VoIP Researcher.*/
/*  Copyright (c) 2011-2012, Intencity Cloud Technologies.*/
package {
    import flash.display.DisplayObject;
    import flash.display.LoaderInfo;
    import flash.events.Event;
    import flash.events.DataEvent;
    import flash.external.ExternalInterface;
    import flash.net.LocalConnection;
    import flash.system.Security;
    
    import mx.core.Application;
    import mx.events.DynamicEvent;
    import mx.events.FlexEvent;
    import mx.events.PropertyChangeEvent;
    import mx.events.VideoEvent;
    import mx.utils.ObjectUtil;
    
    /**
     * The main application for Flash-VideoIO hides the internal implementation of VideoIOInternal
     * and translates the API using ExternalInterface. This model allows clear separation between
     * the application interface and the implementation.
     */
    public class VideoIO extends Application {
        // a reference to the internal implementation class
        private var component:Class = VideoIOInternal;
        
        // a reference to an instance of type VideoIOInternal.
        private var obj:Object = null;
        
        // facebook specific interfaces.
        private var fbConnection1:LocalConnection;
        private var fbConnectionName1:String;
        private var fbConnection2:LocalConnection;
        private var fbConnectionName2:String;
        
        // enable notification or not?
        private var isChild:Boolean = false;
        
        // The URL of the project for facebook.
        private static const BASE_URL:String = "http://myprojectguide.org/p/face-talk";

        /**
         * The constructor sets the absolute layout with transparent background, no border, and
         * installes the listener for start up on "addedToStage" event.
         */
        public function VideoIO() {
            this.layout = "absolute";
            this.setStyle("backgroundAlpha", 0);
            this.setStyle("borderStyle", "none");
            this.setStyle("borderThickness", 0);
            this.setStyle("borderVisible", false);
            addEventListener("addedToStage", creationCompleteHandler);
        }

        /**
         * When the application is added to stage this initialization function is run.
         * It creates a new VideoIOInternal object with 100% dimension, and adds it as a
         * child object. It also installs various ExternalInterface functions.
         */
        private function creationCompleteHandler(event:Event):void
        {
            obj = new component();
            obj.percentWidth = obj.percentHeight = 100;
            
            // whether this is a child application or the top-level application.
            if (CONFIG::sdk4) {
                isChild = (mx.core.FlexGlobals.topLevelApplication != this);
            }
            else {
                isChild = (Application.application != this);
            }
            trace("isChild=" + isChild);
            
            if (!isChild) {
                // Facebook specific initialization
                fbInitialize(null);

                // minimum dimension to popup SecurityPanel
                //TODO: does not work on Chrome with Flash Player 10.3
                //obj.minWidth = 215;
                //obj.minHeight = 138;
            }
            
            try {
                // Listen for all interesting events from the internal implementation.
                obj.addEventListener(FlexEvent.CREATION_COMPLETE, componentCompleteHandler, false, 0, true);
                obj.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true);
                obj.addEventListener("callback", callbackHandler, false, 0, true);
                obj.addEventListener("postingNotify", postingNotifyHandler, false, 0, true);
                obj.addEventListener("showingSettings", showingSettingsHandler, false, 0, true);
                obj.addEventListener("hidingSettings", showingSettingsHandler, false, 0, true);
                obj.addEventListener("receiveData", receiveDataHandler, false, 0, true);
                
                if (ExternalInterface.available) {
                    // For top-level application, install the Javascript API.
                    if (!isChild) {
                        ExternalInterface.addCallback("setProperty", setProperty);
                        ExternalInterface.addCallback("getProperty", getProperty);
                        ExternalInterface.addCallback("callProperty", callProperty);
                    }
                } 
                else {
                    trace("ExternalInterface is not available");
                }
            } catch (e:SecurityError) {
                trace("security exception: " + e.message); 
            }
            
            // For top-level application, process the "flashVars"
            if (!isChild) {
                for (var name:String in parameters) {
                    var value:String = parameters[name];
                    if (value == "true" || value == "false")
                        setProperty(name,  (value == "true"));  // boolean
                    else
                        setProperty(name, value);               // string
                }
            }
            
            // add the object as child of main application
            addChild(DisplayObject(obj));
        }
        
        /**
         * When "setProperty" is invoked from Javascript or parent application, it passes it
         * to the internal implementation.
         */
        public function setProperty(name:String, value:Object):void
        {
            if (obj.hasOwnProperty(name)) {
                trace("setProperty(" + name + "," + (name != "snapshot" ? value : "hidden") + ")");
                obj[name] = (value != '' ? value : null);
            }
            else {
                trace("setProperty(name=" + name + ") ignored");
            }
        }
        
        /**
         * When "getProperty" is invoked from Javascript or parent application, it passes it
         * to the internal implementation.
         */
        public function getProperty(name:String):Object
        {
            var result:Object = obj.hasOwnProperty(name) ? obj[name] : null;
            trace("getProperty(" + name + ")=>" + (name != "snapshot" ? result : "hidden"));
            return result;
        }
        
        /**
         * When "callProperty" is invoked from Javascript or parent application, it passes it
         * to the internal implementation.
         */
        public function callProperty(name:String, ...args):void
        {
            try {
                trace("callProperty(" + name + ",...)");
                var func:Function = obj[name] as Function;
                func.apply(obj, args);
            } catch (e:Error) {
                trace("callProperty(" + name + ",...) exception\n" + e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation is created, it dispatches the onCreationComplete
         * event to parent application and onCreationComplete callback to javascript.
         */
        private function componentCompleteHandler(event:Event):void
        {
            try {
                if (isChild) {
                    dispatchEvent2(new Event("onCreationComplete"));
                }
                else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                    var param:Object = {objectID: ExternalInterface.objectID};
                    trace("invoking JavaScript onCreationComplete objectID=" + ExternalInterface.objectID);
                    if (fbConnectionName1 == null)
                        ExternalInterface.call("onCreationComplete", param);
                    else
                        fbConnection1.send(fbConnectionName1, "callFBJS", "onCreationComplete", [param]); 
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation's property changes, it dispatches the onPropertyChange
         * event to parent application and onPropertyChange callback to javascript.
         */
        private function propertyChangeHandler(event:PropertyChangeEvent):void
        {
            try {
                var type:String = typeof(obj[event.property]);
                if (type == "string" || type == "boolean" || type == "number") {
                    if (isChild) {
                        var ev:DynamicEvent = new DynamicEvent("onPropertyChange");
                        ev.property = event.property;
                        ev.oldValue = event.oldValue;
                        ev.newValue = event.newValue;
                        dispatchEvent2(ev);
                    }
                    else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                        var param:Object = {
                                objectID: ExternalInterface.objectID, 
                                property: event.property, 
                                oldValue: event.oldValue, 
                                newValue: event.newValue
                            };
                            
                        trace("invoking JavaScript onPropertyChange objectID=" + ExternalInterface.objectID
                            + " property=" + event.property
                            + " oldValue=" + event.oldValue
                            + " newValue=" + event.newValue);
                        if (fbConnectionName1 == null)
                            ExternalInterface.call("onPropertyChange", param);
                        else
                            fbConnection1.send(fbConnectionName1, "callFBJS", "onPropertyChange", [param]);
                    }
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation invokes a callback, it dispatches the onCallback
         * event to parent application and onCallback callback to javascript.
         */
        private function callbackHandler(event:DynamicEvent):void
        {
            try {
                if (isChild) {
                    var ev:DynamicEvent = new DynamicEvent("onCallback");
                    ev.method = event.method;
                    ev.args = event.args;
                    dispatchEvent2(ev);
                }
                else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                    var param:Object = {
                            objectID: ExternalInterface.objectID,
                            method: event.method,
                            args: event.args
                        };
                        
                        trace("invoking JavaScript onCallback objectID=" + ExternalInterface.objectID
                            + " method=" + event.method
                            + " args=" + event.args);
                    if (fbConnectionName1 == null)
                        ExternalInterface.call("onCallback", param);
                    else
                        fbConnection1.send(fbConnectionName1, "callFBJS", "onCallback", [param]);
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation dispatches postingNotify , it dispatches the onPostingNotify
         * event to parent application and onPostingNotify callback to javascript.
         */
        private function postingNotifyHandler(event:DynamicEvent):void
        {
            try {
                if (isChild) {
                    var ev:DynamicEvent = new DynamicEvent("onPostingNotify");
                    ev.user = event.user;
                    ev.text = event.text;
                    dispatchEvent2(ev);
                }
                else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                    var param:Object = {
                            objectID: ExternalInterface.objectID,
                            user: event.user,
                            text: event.text
                        };
                        
                        trace("invoking JavaScript onPostingNotify objectID=" + ExternalInterface.objectID
                            + " user=" + event.user
                            + " text=" + event.text);
                    if (fbConnectionName1 == null)
                        ExternalInterface.call("onPostingNotify", param);
                    else
                        fbConnection1.send(fbConnectionName1, "callFBJS", "onPostingNotify", [param]);
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation dispatches receiveData, it dispatches the onReceiveData
         * event to parent application and onReceiveData callback to javascript.
         */
        private function receiveDataHandler(event:DataEvent):void
        {
            try {
                if (isChild) {
                    var ev:DynamicEvent = new DynamicEvent("onReceiveData");
                    ev.data = event.data;
                    dispatchEvent2(ev);
                }
                else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                    var param:Object = {
                            objectID: ExternalInterface.objectID,
                            data: event.data
                        };
                        
                        trace("invoking JavaScript onReceiveData objectID=" + ExternalInterface.objectID
                            + " data=" + event.data);
                    if (fbConnectionName1 == null)
                        ExternalInterface.call("onReceiveData", param);
                    else
                        fbConnection1.send(fbConnectionName1, "callFBJS", "onReceiveData", [param]);
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * When the internal implementation dispatches showingSettings or hidingSettings,
         * it dispatches the onShowingSettings event to parent application and onShowingSettings
         * callback to javascript. The showing (Boolean) property determines the showing or
         * hiding mode.
         */
        private function showingSettingsHandler(event:Event):void
        {
            try {
                if (isChild) {
                    var ev:DynamicEvent = new DynamicEvent("onShowingSettings");
                    ev.showing = (event.type != "hidingSettings");
                    dispatchEvent2(ev);
                }
                else if (ExternalInterface.available && ExternalInterface.objectID != null) {
                    var param:Object = {
                            objectID: ExternalInterface.objectID,
                            showing: (event.type != "hidingSettings")
                        };
                        
                        trace("invoking JavaScript onShowingSettings objectID=" + ExternalInterface.objectID
                            + " showing=" + (event.type != "hidingSettings"));
                    if (fbConnectionName1 == null)
                        ExternalInterface.call("onShowingSettings", param);
                    else
                        fbConnection1.send(fbConnectionName1, "callFBJS", "onShowingSettings", [param]);
                }
            } catch (e:Error) {
                trace(e.getStackTrace());
            }
        }
        
        /**
         * To initialize for Facebook interface, we need to load the crossdomain and allow the facebook domains.
         * Finally we need to map the setProperty and getProperty functions.
         */
        private function fbInitialize(event:Event):void
        {
            try {
                if (('fb_local_connection' in LoaderInfo(this.root.loaderInfo).parameters)
                || ('fb_fbjs_connection' in LoaderInfo(this.root.loaderInfo).parameters)) {
                    Security.loadPolicyFile(BASE_URL + "/crossdomain.xml");
                    Security.allowDomain("apps.facebook.com");
                    Security.allowDomain("*.facebook.com");
                }
            }
            catch (e:Error) {
                trace("Error in Facebook security domain");
            }
            
            try {
                if ('fb_local_connection' in LoaderInfo(this.root.loaderInfo).parameters) {
                    fbConnection1 = new LocalConnection();
                    fbConnectionName1 = LoaderInfo(this.root.loaderInfo).parameters.fb_local_connection;
                    trace("Facebook local connection " + fbConnectionName1);
                }
            }
            catch (e:Error) {
                trace("Error in Facebook local connection");
                trace(e.getStackTrace());
            }

            try {
                if ('fb_fbjs_connection' in LoaderInfo(this.root.loaderInfo).parameters) {
                    fbConnection2 = new LocalConnection();
                    fbConnectionName2 = LoaderInfo(this.root.loaderInfo).parameters.fb_fbjs_connection; 
                    
                    fbConnection2.allowDomain("*");
                    fbConnection2.client = {
                        "setProperty": function(name:String, value:Object):void {
                            this.setProperty(name, value);
                        },
                        "getProperty": function(name:String):Object {
                            return this.getProperty(name);
                        }
                    };
                    fbConnection2.connect(fbConnectionName2);
                    trace("Facebook JS connection " + fbConnectionName2);
                }
            }
            catch (e:Error) {
                trace("Error in Facebook JS connection");
                trace(e.getStackTrace());
            }
        }    
        
        /**
         * When dispatching an event, also dispatch using the loader info of the system manager
         * so that if this is a child application then the parent receives the event.
         */
        private function dispatchEvent2(event:Event):void
        {
            //trace("dispatchEvent type=" + event.type);
            dispatchEvent(event);
            if (this.systemManager != null && this.systemManager.loaderInfo != null 
            && this.systemManager.loaderInfo.sharedEvents != null) 
                this.systemManager.loaderInfo.sharedEvents.dispatchEvent(event);
        }
    }
}


import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.DisplayObject;
import flash.display.Stage;
import flash.display.StageDisplayState;
import flash.events.AsyncErrorEvent;
import flash.events.ContextMenuEvent;
import flash.events.DataEvent;
import flash.events.ErrorEvent;
import flash.events.FocusEvent;
import flash.events.FullScreenEvent;
import flash.events.IOErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.events.StatusEvent;
import flash.events.TimerEvent;
import flash.geom.Matrix;
import flash.media.Camera;
import flash.media.Microphone;
import flash.media.SoundMixer;
import flash.media.SoundTransform;
import flash.media.Video;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.net.ObjectEncoding;
import flash.net.URLRequest;
import flash.net.navigateToURL;
import flash.system.Capabilities;
import flash.system.Security;
import flash.system.SecurityPanel;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.utils.Timer;
import flash.utils.ByteArray;

import mx.binding.utils.BindingUtils;
import mx.containers.Canvas;
import mx.controls.Alert;
import mx.controls.Image;
import mx.controls.VideoDisplay;
import mx.core.Application;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import mx.events.MetadataEvent;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
import mx.events.ResizeEvent;
import mx.events.VideoEvent;
import mx.events.DynamicEvent;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.Base64Encoder;
import mx.utils.Base64Decoder;

import mx.controls.Button;

// Following are dynamically used based on Flash Player version    
//    import flash.net.GroupSpecifier;
//    import flash.net.NetGroup;

/**
 * Dispatched when the camera active state changes.
 */
[Event(name="cameraChange", type="flash.events.Event")]

/**
 * Dispatched when the microphone active state changes.
 */
[Event(name="micChange", type="flash.events.Event")]

/**
 * Dispatched when "privacyEvent" is set to true and Flash Player is about to
 * show the security settings dialog box. This is also dispatched when "showSettings"
 * method is called to explicitly show the settings dialog box.
 */
[Event(name="showingSettings", type="flash.events.Event")]

/**
 * Dispatched when "privacyEvent" is set to true and Flash Player earlier showed
 * the security settings dialog box either implicitly or explicitly on "showSettings",
 * then now the application received the focusIn event to indicate that the
 * security dialog box is no longer active.
 */
[Event(name="hidingSettings", type="flash.events.Event")]

/**
 * Dispatched when the server calls a method. The method and args properties are useful.
 */
[Event(name="callback", type="mx.events.DynamicEvent")]

/**
 * Dispatched when the NetGroup.Posting.Notify is received. The user and text properties are useful.
 */
[Event(name="postingNotify", type="mx.events.DynamicEvent")]

/**
 * Dispatched when the sendData is received on a stream. This is done when the remote side
 * called sendData method to send some data. The data property contains the received data.
 */
[Event(name="receiveData", type="flash.events.DataEvent")]

/**
 * The VideoIO class handles various audio/video recording and playback modes.
 */    
class VideoIOInternal extends Canvas
{
    // the product page URL
    private static const COMPONENT_URL:String = "http://code.google.com/p/flash-videoio";
    
    // the version string
    private static const COMPONENT_VERSION:String = "Powered by Flash-VideoIO " + CONFIG::version;
    
    private var _src:String;
    
    private var _url:String;
    private var _scheme:String;
    private var _args:Array;
    private var _play:String;
    private var _publish:String;
    private var _record:Boolean;
    private var _recordMode:String = "record";
    private var _playMode:String = "live";
    private var _live:Boolean;
    private var _mirrored:Boolean = true;
    private var _farID:String;
    private var _nearID:String;
    
    // other flags for the video
    private var _poster:String;
    private var _autoplay:Boolean = true;
    private var _loop:Boolean = false;
    private var _controls:UIComponent;
    private var _preload:Boolean = false;
    private var _detectActivity:Boolean = true;
    
    // internal objects and flags
    private var nc:NetConnection;
    private var _local:NetStream;
    private var _remote:NetStream;
    private var _dispatchDisconnect:Boolean = false;
    
    private var _playing:Boolean = false;
    private var _recording:Boolean = false;
    private var _bidirection:Boolean = false;
    private var _camera:Boolean = false;
    private var _microphone:Boolean = false;
    private var _display:Boolean = true;
    private var _deviceAllowed:Boolean = false;
    private var _privacyEvent:Boolean = false;
    
    private var _gain:Number = 0.5;
    private var _level:Number = 0;
    private var _rate:int = 16;
    private var _codec:String = "Speex";
    private var _encodeQuality:int = 6;
    private var _framesPerPacket:int = 1;
    private var _silenceLevel:int = 0;
    private var _echoSuppression:Boolean = true;
    private var _echoCancel:Boolean = true;
     
    private var _sound:Boolean = true;
    private var _volume:Number = 0.5;
    
    private var _cameraObject:Camera = null;
    private var _microphoneObject:Microphone = null;
    private var _micLevelTimer:Timer;
    private var _smoothing:Boolean = true;
    
    private var _cameraWidth:int = 320;
    private var _cameraHeight:int = 240;
    private var _cameraFPS:int = 12;
    private var _cameraBandwidth:int = 0;
    private var _cameraQuality:int = 0;
    private var _keyFrameInterval:int = 15;
    private var _cameraLoopback:Boolean = false;
    
    private var _videoCodec:String = "Sorenson";
    
    private var _video:Video;
    private var _videoDisplay:VideoDisplay;
    private var _currentTimer:Timer;
    private var _currentTime:Number;
    private var _duration:Number;
    private var _bytesLoaded:Number;
    private var _bytesTotal:Number;
    private var _videoWidth:Number;
    private var _videoHeight:Number;
    private var _liveDelay:Number;
    private var _currentFPS:Number;
    private var _zoom:String = "in";
    private var _playerState:String;
    
    private var _bandwidth:Number = 0;
    private var _quality:Number = 0.0;
    private var _bufferTime:Number = -1.0;
    private var _bufferTimeMax:Number = -1.0;
    
    private var _image:Image;
    private var _posterBackgroundAlpha:Number;
    
    private var _fullscreen:Boolean = false;
    private var _enableFullscreen:Boolean = true;
    private var _enableFullscreenOnDoubleClick:Boolean = false;
    
    private var _lastSendTime:Number=0;
    private var publishWidth:Number;
    private var publishHeight:Number;
        
//    private var _sip:String = "idle";
    
    // group communication
    private var _group:String;
    private var _groupspec:Object;
    private var _netGroup:Object;
// Since NetGroup and GroupSpecifier should be available for earlier Flash Player version
//    private var _groupspec:GroupSpecifier;
//    private var _netGroup:NetGroup;

    private var _snapshot:String;
    
    private var settingsTimer:Timer;
    private var stageChildren:int = 0;
    
    private var _proxyType:String = "none";
    private var _objectEncoding:uint = ObjectEncoding.DEFAULT;
    
    private var _cameraName:String = "default";
    private var _microphoneName:String = "default";
    
    //--------------------------------------
    // CONSTRUCTOR
    //--------------------------------------
    
    public function VideoIOInternal()
    {
        super();
        
        trace("Created " + VideoIOInternal.COMPONENT_VERSION);
        
        horizontalScrollPolicy = verticalScrollPolicy = "off";
        setStyle("backgroundAlpha", 1.0);
        setStyle("borderStyle", "solid");
        setStyle("borderThickness", 0);
        
        addEventListener("cameraChange", cameraChangeHandler);
        addEventListener("micChange", micChangeHandler);
        
        addEventListener(Event.ADDED_TO_STAGE, addHandler);
        addEventListener(Event.REMOVED_FROM_STAGE, removeHandler);
        
        this.doubleClickEnabled = true;
        addEventListener(MouseEvent.DOUBLE_CLICK, doubleClickHandler);
        
        installContextMenu();
    }
    
    //--------------------------------------
    // GETTERS/SETTERS
    //--------------------------------------
    
    public const __doc__src:String = 
    'The "src" read-write string property represents the source URL of the component that controls ' + 
    'the publish, playback or live mode. For example\n' + 
    ' rtmp://localhost/call/123?publish=live -- connect and publish local stream\n' + 
    ' rtmp://localhost/call/123?play=live&arg=mypass -- play remote stream with auth\n' + 
    ' http://server/path/file1.flv -- play the web downloaded video file\n' + 
    ' rtmp://server/path/file1     -- play the streamed video file\n' + 
    ' ?live=true  -- just display local video\n' + 
    ' rtmp://localhost/record?publish=file1&record=true -- record to file1.flv\n' + 
    'For local demo you can use these URLs\n' + 
    ' rtmp://localhost/call/123?publish=live   -- sender stream of call\n' + 
    ' rtmp://localhost/call/123?play=live      -- receiver stream of call\n' + 
    ' rtmp://localhost/call/123?publish=test2&record=true  -- record test2.flv\n' + 
    ' http://localhost:8080/123/test2.flv      -- play test2.flv\n' + 
    ' rtmp://localhost/call/123?play=test2\n';
    
    [Bindable('propertyChange')]
    /**
     * The src property controls the connect URL and publish, play or live mode.
     */
    public function get src():String
    {
        return _src;
    }
    public function set src(value:String):void
    {
        var oldValue:String = _src;
        _src = value;
        
        if (oldValue != value) {
            
            var result:Object = { url:null, scheme:null, args:[], farID:null,
                                  publish:null, play:null, record:false, recordMode:null, playMode:null, live:false, name:null};
            var params:String = '';
            
            if (value != null) {
                var index:int = value.indexOf(':');
                result.scheme = (index >= 0 ? value.substr(0, index).toLowerCase() : null);
                if (result.scheme == 'http' || result.scheme == 'https') {
                    result.url = value;
                }
                else {
                    index = value.indexOf('?');
                    params = (index >= 0 ? value.substr(index+1) : '');
                    result.url = (index >= 0 ? value.substr(0, index) : value);
                }
            }
            
            for each (var part:String in params.split('&')) {
                index = part.indexOf('=');
                var name:String = (index >= 0 ? part.substr(0, index) : part);
                var val:String = (index >= 0 ? part.substr(index+1) : null);
                if (name == "publish" || name == "play" || name =="farID" || name == "group" || name == "recordMode" || name == "playMode")
                    result[name] = val;
                else if (name == "live" || name == "record" || name == "bidirection")
                    result[name] = (val != "false");
                else if (name == "arg")
                    result.args.push(val);
            }
    
            // initialize all read-only propeties
            setProperty("args", result.args);
            setProperty("scheme", result.scheme);
            setProperty("farID", result.farID);
            setProperty("group", result.group);
            setProperty("bytesLoaded", new Number(0));
            setProperty("bytesTotal", new Number(0));
            setProperty("videoWidth", NaN);
            setProperty("videoHeight", NaN);
            setProperty("liveDelay", NaN);
            setProperty("currentFPS", NaN);
            setProperty("playerState", null);
            if ('bidirection' in result)
                setProperty("bidirection", result.bidirection);
                
            this.level = 0;
            
            this.playMode = (result.scheme == "http" || result.scheme == "https" ? "web" : (result.playMode == "vod" ? "vod" : "live"));
            this.recordMode = result.recordMode == "append" ? "append" : "record";
            this.record = result.record || (result.recordMode == "append" || result.recordMode == "record");
            this.url = result.url;
            
            
            if (result.publish || result.play) {
                if (this.bidirection) {
                    this.publish = result.publish;
                    this.play = result.play;
                }
                else if (result.publish) {
                    this.play = null;
                    this.publish = result.publish;
                }
                else if (result.play) {
                    this.publish = null;
                    this.live = false;
                    this.play = result.play;
                }
            }
            else {
                this.publish = null;
                this.play = null;
                this.live = result.live;
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "src", oldValue, value));
        }
    }
    
    public const __doc__poster:String = 
    'The "poster" read-write string property represents the initial image to display before video ' + 
    'is started to publish or play. This should be an http URL. Example\n' + 
    ' http://kundansingh.com/images/face.jpg\n';
    
    [Bindable('propertyChange')]
    /**
     * The poster (or initial image) to display before video is started or 
     * connected.
     */
    public function get poster():String
    {
        return _poster;
    }
    public function set poster(value:String):void
    {
        var oldValue:String = _poster;
        _poster = value;
        if (oldValue != value) {
            if (oldValue != null)
                detachPoster();
            if (value != null)
                attachPoster();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "poster", oldValue, value));
        }
    }
    
    public const __doc__posterBackgroundAlpha:String =
    'The "posterBackgroundAlpha" read-write number property represents the background alpha ' + 
    'of the poster image. If the value is 0 or NaN the background is not displayed. Otherwise ' + 
    'the same poster images is faded using the value alpha, streched and displayed as background\n';
    
    [Bindable('propertyChange')]
    /**
     * The posterBackgroundAlpha is greater than 0 indicates that a background is displayed
     * as a faded poster using this alpha.
     */
    public function get posterBackgroundAlpha():Number
    {
        return _posterBackgroundAlpha;
    }
    public function set posterBackgroundAlpha(value:Number):void
    {
        var oldValue:Number = _posterBackgroundAlpha;
        _posterBackgroundAlpha = value;
        if (oldValue != value) {
            if (!isNaN(oldValue))
                detachPosterBackground();
            if (!isNaN(value))
                attachPosterBackground();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "posterBackgroundAlpha", oldValue, value));
        }
    }
    
    public const __doc__preload:String =
    'The "preload" read-write boolean property controls whether the video should be pre-loaded ' + 
    'before it is played.\n';
    
    [Bindable('propertyChange')]
    /**
     * The preload property controls whether the video should be pre-loaded before
     * the user starts play.
     */
    public function get preload():Boolean
    {
        return _preload;
    }
    public function set preload(value:Boolean):void
    {
        var oldValue:Boolean = _preload;
        _preload = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "src", oldValue, value));
        }
    }
    
    public const __doc__autoplay:String =
    'The "autoplay" read-write boolean property controls whether the video should be played ' + 
    'automatically when the "src" property is assigned and video is loaded, or should it wait ' + 
    'for explicit play command using the "playing" property. The default is true indicating ' + 
    'automatic play when ready.\n';
    
    [Bindable('propertyChange')]
    /**
     * The autoplay property controls whether the video should be played automatically
     * when started.
     */
    public function get autoplay():Boolean
    {
        return _autoplay;
    }
    public function set autoplay(value:Boolean):void
    {
        var oldValue:Boolean = _autoplay;
        _autoplay = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "autoplay", oldValue, value));
        }
    }
    
    public const __doc__loop:String =
    'The "loop" read-write boolean property controls whether the video should loop to begining ' + 
    'when it reaches the end.\n';
     
    [Bindable('propertyChange')]
    /**
     * The loop property controls whether the video should be looped after completed.
     */
    public function get loop():Boolean
    {
        return _loop;
    }
    public function set loop(value:Boolean):void
    {
        var oldValue:Boolean = _loop;
        _loop = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "loop", oldValue, value));
        }
    }
    
    public const __doc__controls:String = 
    'The "controls" read-write boolean property controls whether the video control panel should ' + 
    'be displayed or not. Default is false. The current implementation displays a video control ' + 
    'panel at the bottom of the user interface view. The control panel automatically hides if ' + 
    'there is no mouse activity for some time, and the mouse is not over the control panel. If ' + 
    'the mouse is rolled over the control panel, the view remains visible. Later, if the user moves ' + 
    'the mouse anywhere on the video view, the control panel re-appears if it was hidden before. ' + 
    'By the default, the control panel displays various control buttons based on the current state. ' + 
    'For example, if the "play" property is set, then the play/pause, speaker and volume ' + 
    'controls are displayed, whereas if the "publish" property is set, then the record/stop, ' + 
    'camera, microphone and gain controls are displayed.\n';
     
    [Bindable('propertyChange')]
    /**
     * The controls property controls whether the video should display user controls or not.
     */
    public function get controls():Boolean
    {
        return (_controls != null);
    }
    public function set controls(value:Boolean):void
    {
        var oldValue:Boolean = (_controls != null);
        if (value && _controls == null) {
            _controls = new VideoControl();
            this.addChild(_controls);
        }
        else if (!value && _controls != null) {
            this.removeChild(_controls);
            _controls = null;
        }
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "controls", oldValue, value));
        }
    }
    
    public const __doc__detectActivity:String = 
    'The "detectActivity" read-write boolean property controls whether the mouse activity is ' + 
    'detected, e.g., to automatically hide the control bar. Default is "true".\n';
     
    [Bindable('propertyChange')]
    /**
     * The detectActivity property controls whether the mouse activity is detected or not?
     */
    public function get detectActivity():Boolean
    {
        return _detectActivity;
    }
    public function set detectActivity(value:Boolean):void
    {
        var oldValue:Boolean = _detectActivity;
        _detectActivity = value;
        if (oldValue != value) {
            if (_controls != null && (_controls is VideoControl)) {
                if (value)
                    VideoControl(_controls).startHideTimer();
                else
                    VideoControl(_controls).stopHideTimer();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "detectActivity", oldValue, value));
        }
    }
    
    public const __doc__args:String = 
    'The "args" read-only array of values represents the connection arguments captured from ' + 
    'the "src" URL\'s "arg" parameters. For example if "src" is set to ' + 
    '"rtmp://server/record?publish=live&arg=myuser&arg=mypass" then the "args" property will ' + 
    'be ["myuser","mypass"].\n';
    
    /**
     * The read-only args property defines the connect() arguments if any.
     * This is set using zero of more arg params in the "src" URL.
     */
    public function get args():Array
    {
        return _args;
    }
    
    public const __doc__scheme:String =
    'The "scheme" read-only string property indicates the scheme of the "src" URL. ' + 
    'It is set automatically when the "src" property is set.\n';
    
    /**
     * The read-only URL scheme property of the URL.
     * This is set using the scheme of the "src" URL.
     */
    public function get scheme():String
    {
        return _scheme;
    }
    
    public const __doc__farID:String =
    'The "farID" read-write string property indicates the farID parameter of the "src" ' + 
    'URL. It is set automatically when the "src" property is set. This property is ' + 
    'used to create NetStream object in scheme of "rtmfp" to establish direct connection ' + 
    'to the given farID peer. For example, if "src" is set to ' + 
    '"rtmfp://stratus.adobe.com/some-developer-key?play=live1&farID=some-far-id" then ' + 
    '"some-far-id" is assumed to be the ID of the remote side who is publishing the stream ' + 
    'named "live1".\n';
    
    /**
     * The read-write farID property of the URL to play.
     * This is set using farID param in the "src" URL.
     */
    public function get farID():String
    {
        return _farID;
    }
    public function set farID(val:String):void
    {
        _farID = val;
    }
    
    public const __doc__nearID:String =
    'The "nearID" read-only string property indicates the nearID property of the established ' + 
    'NetConnection when the "rtmfp" scheme is used to establish direct connection. ' + 
    'It is available only after the connection to the server is established using the ' + 
    '"src" URL of the form "rtmfp://stratus.adobe.com/some-developer-key...". The application ' + 
    'should capture the "nearID" and give it to the remote side who wants to play the ' + 
    'stream published by this side, so that the remote side can use this property as its ' + 
    '"farID" property to play the stream.\n';
    
    /**
     * The read-only nearID property of the URL after publish.
     * This is available after connection is complete using an rtmfp URL.
     */
    public function get nearID():String
    {
        return _nearID;
    }
    
    public const __doc__url:String =
    'The "url" read-write string property refers to the url part of the "src" property. ' + 
    'The url part excludes any parameters if the URL scheme is "rtmp" or "rtmfp" but includes ' + 
    'those if the scheme is "http" or "https". Although "url" property is read-write, ' + 
    'the application should not directly write this property. Instead the application writes ' + 
    'the "src" property, which implicitly sets the "url" property. Setting the "url" property ' + 
    'connects, disconnects or reconnects to the services in case of "rtmp" or "rtmfp" URLs, ' + 
    'and starts "load" or "unload" in case of "http" or "https" URLs.\n';
    
    [Bindable('propertyChange')]
    /**
     * The url property controls the connection. 
     * This can also be set using the "src" property.
     */
    public function get url():String
    {
        return _url;
    }
    public function set url(value:String):void
    {
        var oldValue:String = _url;
        _url = value;
        
        if (oldValue != value) {
            setProperty("nearID", null); // nearID will be set on success
            
            var index:int = (value != null ? value.indexOf(':') : -1);
            _scheme = (index >= 0 ? value.substr(0, index).toLowerCase() : null);
            
            if (oldValue != null) {
                if (oldValue.substr(0, 4) == "http")
                    unload(true);
                else 
                    disconnect();
            }
            if (value != null) {
                if (value.substr(0, 4) == "http")
                    load();
                else
                    connect();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "url", oldValue, value));
        }
    }
    
    public const __doc__isDownload:String =
    'The "isDownload" (deprecated) read-only boolean property indicates whether the "src" will cause ' + 
    'progressive download of media or not? It is true for "http" and "https" URLs, and ' + 
    'false for other URLs such as "rtmp" and "rtmfp".\n'
    
    public function get isDownload():Boolean
    {
        return _scheme == 'http' || _scheme == 'https';            
    }
    
    public const __doc__play:String =
    'The "play" read-write string property refers to the play stream name for streaming ' + 
    '"src" value, such as "rtmp" or "rtmfp" URLs. Although this is a read-write property ' + 
    'the application should use the "play" parameter of the "src" property to set this. ' + 
    'For example, if the "src" is set to "rtmp://server/path?play=live1" then the ' + 
    '"play" property is automatically set to "live1". Setting this property resets the ' + 
    '"publish" and "live" properties and creates the NetStream for the given play stream ' + 
    'name. Resetting this property to null resets the "playing" property and stops the' + 
    'previous play stream if any.\n';
    
    [Bindable('propertyChange')]
    /**
     * The stream-name for playback.
     */
    public function get play():String
    {
        return _play;
    }
    public function set play(value:String):void
    {
        var oldValue:String = _play;
        _play = value;
        
        if (oldValue != value) {
            if (oldValue != null && (url == null || url.substr(0, 4) != "http"))
                playing = false;
            if (value != null) {
                if (!bidirection) {
                    if (publish != null)
                        publish = null;
                    if (live)
                        live = false;
                }
                
                if (display)
                    attachVideo();
                if (autoplay)
                    createStream();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "play", oldValue, value));
        }
    }
    
    public const __doc__publish:String =
    'The "publish" read-write string property refers to the publish stream name for streaming ' + 
    '"src" value, such as "rtmp" or "rtmfp" URLs. Although this is a read-write property ' + 
    'the application should use the "publish" parameter of the "src" property to set this. ' + 
    'For example, if the "src" is set to "rtmp://server/path?publish=live1" then the ' + 
    '"publish" property is automatically set to "live1". Setting this property resets the ' + 
    '"play" and sets the "live" properties, enables "camera" and "microphone" properties ' + 
    'and creates the given publish stream name. Resetting this property to null resets the ' + 
    '"recording", "live", "camera" and "microphone" properties and destroys the previous ' + 
    'publish stream if any.\n';
    
    [Bindable('propertyChange')]
    /**
     * The stream-name for publish.
     */
    public function get publish():String
    {
        return _publish;
    }
    public function set publish(value:String):void
    {
        var oldValue:String = _publish;
        _publish = value;
        
        if (oldValue != value) {
            if (oldValue != null) {
                recording = false;
                if (live)
                    live = false;
                camera = microphone = false;
            }
            if (value != null) {
                if (!bidirection) {
                    if (play != null)
                        play = null;
                }
                if (!live)
                    live = true;
                camera = microphone = true;
                if (display)
                    attachVideo();
                if (autoplay)
                    createStream();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "publish", oldValue, value));
        }
    }
    
    public const __doc__record:String =
    'The "record" read-write boolean property refers to whether the publish stream is also ' + 
    'recorded on the server, and is derived from the "record" parameter of the "src" property. ' + 
    'For example if the "src" is set to "rtmp://server/path?publish=test1&record=true" ' + 
    'then the "record" property is set to true. Although this is a read-write property ' + 
    'the application should use the "record" parameter of the "src" property to set this.\n';
    
    [Bindable('propertyChange')]
    /**
     * The record property along with publish allows recording of video to server.
     */
    public function get record():Boolean
    {
        return _record;
    }
    public function set record(value:Boolean):void
    {
        var oldValue:Boolean = _record;
        _record = value;
        if (oldValue != value) {
            if (recording) { // reset recording stream
                destroyStream();
                createStream();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "record", oldValue, value));
        }
    }
    
    public const __doc__recordMode:String =
    'The "recordMode" read-write string property refers to whether the publish stream is also ' + 
    'recorded on the server, and is derived from the "recordMode" parameter of the "src" property. ' + 
    'For example if the "src" is set to "rtmp://server/path?publish=test1&record=true&recordMode=append" ' + 
    'then the "recordMode" property is set to "append". Although this is a read-write property ' + 
    'the application should use the "recordMode" parameter of the "src" property to set this. ' +
    'This property could be "record" or "append", and defaulst to "record". If this parameter is set ' +
    'in "src", the "record" property is implicitly set.\n';
    
    [Bindable('propertyChange')]
    /**
     * The recordMode property along with record controls whether to overwrite or append.
     */
    public function get recordMode():String
    {
        return _recordMode;
    }
    public function set recordMode(value:String):void
    {
        if (value == "record" || value == "append") {
            var oldValue:String = _recordMode;
            _recordMode = value;
            if (oldValue != value) {
                if (recording) { // reset recording stream
                    destroyStream();
                    createStream();
                }
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "recordMode", oldValue, value));
            }
        }
        else {
            trace("setting invalid value to recordMode=" + value);
        }
    }
    
    public const __doc__playMode:String =
    'The "playMode" read-write string property refers to whether the playing stream is downloaded ' + 
    'from web, is live stream or a video-on-demand (vod) stream. It is derived from the "src" property ' +
    'as follows. If the URL scheme is "http" or "https" then "playMode" is set to "web". ' +
    'Otherwise, if the "playMode" parameter is present in "src" property, then it is used. Otherwise ' +
    'it is set to "live" as default. For example if the "src" is set to "rtmp://server/path?play=test1&playMode=vod" ' + 
    'then the "playMode" property is set to "vod". Although this is a read-write property ' + 
    'the application should use the "playMode" parameter of the "src" property to set this if needed. ' +
    'The possible values are "web", "live" or "vod", but when setting only "live" or "vod" is allowed because ' +
    '"web" is set implicitly based on the URL scheme. The "playMode" controls how setting the "playing" ' +
    'property affects the stream. In "web" mode, setting the "playing" property calls the play() or ' +
    'pause() method of the VideoDisplay object. In the "live" mode, it creates or destroys the NetStream ' +
    'for playing. In the "vod" mode, it calls the pause() or resume() method of the NetStream object ' +
    'for playing. Thus, if you do not want to destroy/re-create the NetStream on pause/play, you should set ' +
    'the "playMode" to "vod", instead of the default "live". Setting this property affects only subsequent ' +
    'change in the "playing" property.\n';
    
    [Bindable('propertyChange')]
    /**
     * The playMode property along with playing controls how the streams are affected when playing is changed.
     */
    public function get playMode():String
    {
        return _playMode;
    }
    public function set playMode(value:String):void
    {
        if (value == "vod" || value == "live") {
            var oldValue:String = _playMode;
            _playMode = value;
            if (oldValue != value) {
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "playMode", oldValue, value));
            }
        }
        else {
            trace("setting invalid value to playMode=" + value);
        }
    }
    
    public const __doc__live:String =
    'The "live" read-write boolean property refers to whether the current display mode is live ' + 
    'camera view or not. This is implicitly set when "publish" property is set. This can be ' + 
    'explicitly set by the "live" parameter in the "src" property. For example, when the ' + 
    '"src" property is set to "?live=true" then "live" property is set to true. This property ' + 
    'controls the "camera" property, but not the reverse. This property when set also attaches ' + 
    'the camera to the local video display.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether local video is displayed as preview.
     */
    public function get live():Boolean
    {
        return _live;
    }
    public function set live(value:Boolean):void
    {
        var oldValue:Boolean = _live;
        _live = value;
        if (oldValue != value) {
            camera = value;
            if (value && display)
                attachVideo();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "live", oldValue, value));
        }
    }
    
    public const __doc__mirrored:String =
    'The "mirrored" read-write boolean property controls whether the local video view is mirrored ' + 
    'either for live or when publish is set. This property does not effect the remote view when ' + 
    'play is set. Default is "true".\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether local video is mirrored or not?
     */
    public function get mirrored():Boolean
    {
        return _mirrored;
    }
    public function set mirrored(value:Boolean):void
    {
        var oldValue:Boolean = _mirrored;
        _mirrored = value;
        if (oldValue != value) {
            resizeVideoHandler(null);
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "mirrored", oldValue, value));
        }
    }
    
    public const __doc__bidirection:String =
    'The "bidirection" read-write boolean property controls whether to support both play and publish ' + 
    'streams at the same time or not. Default is false. Setting this to true will allow you to set both ' + 
    '"play" and "publish" properties for bi-directional stream. The stream which is set later ' + 
    'is used to attach to the video display. This is useful for SIP call using the SIP-RTMP gateway ' + 
    'which requires both "publish=local" and "play=remote".\n';
            
    [Bindable('propertyChange')]
    /**
     * Whether the streams are created for both directions is possible.
     */
    public function get bidirection():Boolean
    {
        return _bidirection;
    }
    public function set bidirection(value:Boolean):void
    {
        var oldValue:Boolean = _bidirection;
        _bidirection = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "bidirection", oldValue, value));
        }
    }
    
    public const __doc__playing:String =
    'The "playing" read-write boolean property refers to the play or pause state of the stream. ' + 
    'If "playMode" is "web", i.e., the "src" is a "http" or "https" URL, then the "playing" ' + 
    'property calls the play() or pause() method of the VideoDisplay object. Otherwise, ' + 
    'if "playMode" is "live", then it creates or destroys the NetStream for playing. ' +
    'Otherwise, if "playMode" is "vod", then it calls the resume() and pause() method on the ' +
    'NetStream for playing. This property is implicitly set when ' + 
    '"autoplay" is set and the "src" property is set to indicate a play mode. This property ' + 
    'is also attached to the state of play/pause button in the control panel if visible.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the video is playing or not (if play is valid).
     */
    public function get playing():Boolean
    {
        return _playing;
    }
    public function set playing(value:Boolean):void
    {
        var oldValue:Boolean = _playing;
        _playing = value;
        
        trace("playing " + oldValue + "=>" + value);
        if (oldValue != value) {
            if (value) 
                detachPoster();
                
            if (_playMode == "web") {
                if (oldValue)
                    _videoDisplay.pause();
                if (value)
                    _videoDisplay.play();
            }
            else if (_play != null && _playMode == "vod") {
                if (_remote == null)
                    createStream();
                
                if (_remote != null) {
                    if (oldValue)
                        _remote.pause();
                    if (value)
                        _remote.resume();
                }
            }
            else { // default is "live"
                if (oldValue)
                    destroyStream();
                if (value)
                    createStream();
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "playing", oldValue, value));
        }
    }
    
    public const __doc__recording:String =
    'The "recording" read-write boolean property refers to the publish or stop state of the ' + 
    'stream. This is useful only for a publish stream of "rtmp" or "rtmfp" URL. The property ' + 
    'creates or destroys the NetStream for publish. This is implicitly set when "src" points to ' + 
    'a publish stream, e.g., "rtmp://server/path?publish=live1, and the stream is created. ' + 
    'This property is also attached to the state of the record/stop button in the control ' + 
    'panel if visible.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the camera/microphone is capturing or not (if publish or live is valid).
     */
    public function get recording():Boolean
    {
        return _recording;
    }
    public function set recording(value:Boolean):void
    {
        var oldValue:Boolean = _recording;
        _recording = value;
        
        if (oldValue != value) {
            if (oldValue)
                destroyStream();
            if (value)
                createStream();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "recording", oldValue, value));
        }
    }
    
    public const __doc__camera:String =
    'The "camera" read-write boolean property refers to whether the camera is on and ' + 
    'capturing video or not. This is implicitly set when "live" or "publish" property is ' + 
    'set. This can be explicitly set to control the camera on/off. This property is also ' + 
    'attached to the camera on/off icon on the control panel if visible. On Mac OS, when ' + 
    'set, it tries to use the "USB Video Class Video" camera, whereas on other platforms it' + 
    'tries to use the default camera. When the camera is set or reset or when the ' + 
    'cameraObject is changed a "cameraChange" event is dispatched.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether camera is active or not?
     */
    public function get camera():Boolean
    {
        return _camera;
    }
    public function set camera(value:Boolean):void
    {
        var oldValue:Boolean = _camera;
        if (value != oldValue) {
            if (!value) {
                _camera = value;
                if (_cameraObject != null) {
                    _cameraObject = null;
                    dispatchEvent(new Event("cameraChange"));
                }
            }
            else {
                if (_cameraObject == null) {
                    if (_cameraName != "default") {
                        var index3:int = Camera.names.indexOf(_cameraName);
                        if (index3 < 0) {
                            cameraName = "default"; // will dispatch the property change event.
                        }
                        else {
                            _cameraObject = Camera.getCamera(index3.toString());
                        }
                    }
                    if (_cameraName == "default") {
                        var index1:int = Camera.names.indexOf("USB Video Class Video");
                        var index2:int = Camera.names.indexOf("Built-in iSight");
                        if (index1 >= 0 && Capabilities.os.indexOf("Mac") >= 0) 
                            _cameraObject = Camera.getCamera(index1.toString());
                        else if (index2 >= 0 && Capabilities.os.indexOf("Mac") >= 0) 
                            _cameraObject = Camera.getCamera(index2.toString());
                        else
                            _cameraObject = Camera.getCamera();
                    }
                    if (_cameraObject != null) {
                        _cameraObject.setLoopback(_cameraLoopback);
                        _cameraObject.setMode(_cameraWidth, _cameraHeight, _cameraFPS);
                        _cameraObject.setQuality(_cameraBandwidth, _cameraQuality);
                        dispatchEvent(new Event("cameraChange"));
                    }
                }
                if (_cameraObject != null) {
                    _camera = value;
                    
                    if (!deviceAllowed) {
                        setProperty("deviceAllowed", false);
                        showSettings();
                        _cameraObject.addEventListener(StatusEvent.STATUS, cameraStatusHandler, false, 0, true);
                    }
                    else {
                        setProperty("deviceAllowed", true);
                    }
                }
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "camera", oldValue, value));
        }
    }
    
    public const __doc__microphone:String =
    'The "microphone" read-write boolean property refers to whether the microphone is on and ' + 
    'capturing audio or not. This is implicitly set when "publish" property is ' + 
    'set. This can be explicitly set to control the microphone on/off. This property is also ' + 
    'attached to the microphone on/off icon on the control panel if visible. ' + 
    'When the microphone is set or reset or when the ' + 
    'microphoneObject is changed a "microphoneChange" event is dispatched.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether microphone is active or not?
     */
    public function get microphone():Boolean
    {
        return _microphone;
    }
    public function set microphone(value:Boolean):void
    {
        var oldValue:Boolean = _microphone;
        if (value != oldValue) {
            if (!value) {
                stopMicLevelTimer();
                _microphone = value;
                if (_microphoneObject != null) {
                    _microphoneObject = null;
                    if (CONFIG::sdk4) { 
                        // use getMicrophone to remove the enhance microphone which takes unnecessary CPU 
                        if (Microphone['getEnhancedMicrophone'] != undefined) {
                            var mic:Microphone = Microphone.getMicrophone(-1);
                        }
                    }
                    dispatchEvent(new Event("micChange"));
                }
            }
            else {
                if (_microphoneObject == null) {
                    var index:int = -1;
                    if (_microphoneName != "default") {
                        index = Microphone.names.indexOf(_microphoneName);
                        if (index < 0) {
                            index = -1;
                            microphoneName = "default";
                        }
                    }
                    if (CONFIG::sdk4) {
                        if(Microphone['getEnhancedMicrophone'] == undefined) {
                            _microphoneObject = Microphone.getMicrophone(index);
                        }
                        else {
                            if (!this.echoCancel) {
                                trace('enhanced mic available but not used');
                                _microphoneObject = Microphone.getMicrophone(index);
                            }
                            else {
                                trace('enhanced mic available and used');
                                _microphoneObject = Microphone['getEnhancedMicrophone'](index);
                                var options:Object = new flash.media.MicrophoneEnhancedOptions();
                                options.mode = flash.media.MicrophoneEnhancedMode.FULL_DUPLEX;
                                options.autoGain = false;
                                options.echoPath = 128;
                                options.nonLinearProcessing = true;
                                _microphoneObject['enhancedOptions'] = options;
                            }
                        } 
                    } 
                    else {
                        _microphoneObject = Microphone.getMicrophone(index);
                    }
                    if (_microphoneObject != null) {
                        _microphoneObject.codec = codec;
                        _microphoneObject.rate = rate;
                        _microphoneObject.encodeQuality = encodeQuality;
                        _microphoneObject.framesPerPacket = framesPerPacket;
                        _microphoneObject.setSilenceLevel(silenceLevel);
                        _microphoneObject.setUseEchoSuppression(echoSuppression);
                        gain = _microphoneObject.gain / 100;
                        dispatchEvent(new Event("micChange"));
                    }
                }
                if (_microphoneObject != null) {
                    _microphone = value;
                    startMicLevelTimer();
                        
                    if (!deviceAllowed) {
                        setProperty("deviceAllowed", false);
                        showSettings();
                        _microphoneObject.addEventListener(StatusEvent.STATUS, micStatusHandler, false, 0, true);
                    }
                    else {
                        setProperty("deviceAllowed", true);
                    }
                }
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "microphone", oldValue, value));
        }
    }
    
    public const __doc__cameraNames:String =
    'The "cameraNames" read-only property refers to the list of cameras available in the system. ' +
    'It returns the Camera.names property of Flash Player. The Flash Player settings also displays ' +
    'this list under the camera tab.\n';
     
    /**
     * The camera names.
     */
    public function get cameraNames():Array
    {
        return Camera.names;
    }
    
    public const __doc__microphoneNames:String =
    'The "microphoneNames" read-only property refers to the list of microphones available in the system. ' +
    'It returns the Microphone.names property of Flash Player. The Flash Player settings also displays ' +
    'this list under the microphone tab.\n';
     
    /**
     * The microphone names.
     */
    public function get microphoneNames():Array
    {
        return Microphone.names;
    }
    
    public const __doc__cameraName:String =
    'The "cameraName" read-write property controls the selected camera. Default is "default" indicating ' +
    'use of default system camera. The application can set it to "default" to indicate that the ' +
    'Flash Player settings and system settings are used to determine the default device, or to any ' +
    'of the value listed in "cameraNames" property to pick a specific device. If the application ' +
    'picks a specific device, then changing the device from Flash Player settings does not affect ' +
    'the device selection of this instance. Setting the property to an invalid name will cause reset to ' +
    '"default" next time a camera object is created. Changing the device in the middle of a session is not ' +
    'supported. Changing this property will not affect ongoing publish session. You should set this ' +
    'property before setting any of "publish", "live" or "camera" property.\n';
     
    [Bindable("propertyChange")]
    /**
     * The camera name.
     */
    public function get cameraName():String
    {
        return _cameraName;
    }
    
    public function set cameraName(value:String):void
    {
        var oldValue:String = _cameraName;
        _cameraName = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraName", oldValue, value));
        }
    }
    
    public const __doc__microphoneName:String =
    'The "microphoneName" read-write property controls the selected microphone. Default is "default" indicating ' +
    'use of default system microphone. The application can set it to "default" to indicate that the ' +
    'Flash Player settings and system settings are used to determine the default device, or to any ' +
    'of the value listed in "microphoneNames" property to pick a specific device. If the application ' +
    'picks a specific device, then changing the device from Flash Player settings does not affect ' +
    'the device selection of this instance. Setting the property to an invalid name will cause reset to ' +
    '"default" next time a microphone object is created. Changing the device in the middle of a session is not ' +
    'supported. Changing this property will not affect ongoing publish session. You should set this ' +
    'property before setting any of "publish", "live" or "microphone" property.\n';
     
    [Bindable("propertyChange")]
    /**
     * The microphone name.
     */
    public function get microphoneName():String
    {
        return _microphoneName;
    }
    
    public function set microphoneName(value:String):void
    {
        var oldValue:String = _microphoneName;
        _microphoneName = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "microphoneName", oldValue, value));
        }
    }
    
    public const __doc__privacyEvent:String =
    '(Deprecated) The "privacyEvent" read-write boolean property controls whether an event callback is ' + 
    'invoked when the Flash Player demands privacy settings display or not? It also allows ' + 
    'using the deviceAllowed property to know if the user has changed his device access ' + 
    'permissions. Default is "false".\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether to dispatch event callback when Flash Player settings is shown?
     */
    public function get privacyEvent():Boolean
    {
        return _privacyEvent;
    }
    public function set privacyEvent(value:Boolean):void
    {
        var oldValue:Boolean = _privacyEvent;
        _privacyEvent = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "privacyEvent", oldValue, value));
        }
    }
    
    public const __doc__deviceAllowed:String =
    'The "deviceAllowed" read-only boolean property indicates whether the microphone and/or ' + 
    'camera device is allowed (or denied) by the end user in the Flash Player security' + 
    'settings. The value is not valid until a device access is needed.\n';
    
    /**
     * Whether access to camera and microphone is allowed?
     */
    public function get deviceAllowed():Boolean
    {
        var mic:Microphone = _microphoneObject != null ? _microphoneObject : Microphone.getMicrophone();
        var cam:Camera = _cameraObject != null ? _cameraObject : Camera.getCamera();
        return (mic && !mic.muted || cam && !cam.muted);
    }
    
    public const __doc__display:String =
    'The "display" read-write boolean property refers to whether the video display is enabled ' + 
    'or not. Default is "true". This property applies to both publish and play mode. ' + 
    'If you do not want to display the video, e.g., for audio-only user interface or ' + 
    'for second publish stream, you can set this to "false".\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether display is enabled or not?
     */
    public function get display():Boolean
    {
        return _display;
    }
    public function set display(value:Boolean):void
    {
        var oldValue:Boolean = _display;
        if (value != oldValue) {
            _display = value;
            
            if (!value) {
                if (video != null) {
                    detachVideo();
                }
            }
            else {
                if (video == null) {
                    if (play != null || publish != null || live) {
                        attachVideo();
                    }
                }
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "display", oldValue, value));
        }
    }
    
    public const __doc__videoDisplay:String =
    'The "videoDisplay" read-only property refers to the currently displayed VideoDisplay ' + 
    'object when "playMode" is "web", or none if there is no VideoDisplay. Either the ' + 
    'VideoDisplay or Video object is available when a video is displaying.\n';
     
    /**
     * The video display object.
     */
    public function get videoDisplay():VideoDisplay
    {
        return _videoDisplay;
    }
    
    public const __doc__video:String =
    'The "video" read-only property refers to the currently displayed Video ' + 
    'object when "live" or "playing" is true, or none if there is no Video. Either the ' + 
    'VideoDisplay or Video object is available when a video is displaying.\n';
     
    /**
     * The video object.
     */
    public function get video():Video
    {
        return _video;
    }
    
    public const __doc__videoWidth:String =
    'The "videoWidth" read-only number property refers to the width in pixels of the active ' + 
    'video display if any, or NaN if none. This is the original unscaled width.\n';
     
    /**
     * The width of video display.
     */
    public function get videoWidth():Number
    {
        return _videoWidth;
    }
    
    public const __doc__videoHeight:String =
    'The "videoHeight" read-only number property refers to the height in pixels of the active ' + 
    'video display if any, or NaN if none. This is the original unscaled height.\n';
     
    /**
     * The height of video display.
     */
    public function get videoHeight():Number
    {
        return _videoHeight;
    }
    
    public const __doc__zoom:String =
    'The "zoom" read-write string property controls the zoom mode when the video aspect ratio is ' +
    'different than the display aspect ratio. The video aspect ratio is set by the capture device such as Camera, ' +
    'sent by the publishing stream to the playing stream or present in the video file itself. The display ' +
    'aspect ratio is determined by the dimension of the VideoIO.swf object and is based on the ' +
    'dimensions specified in embedding object/embed tags in HTML, SWFLoader component in parent SWF or ' +
    'monitor size in full screen mode. When this property is set to null, there is no zoom adjustiment made ' +
    'and the displayed video may be distorted, e.g., if 320x240 video is displayed in 1280x720 it will ' +
    'appear to be horizontally stretched. When this property is set to "in", the video is zoomed in without ' +
    'distortion so as to fill the full display size, e.g., if 320x240 video is displayed in 1280x720 it will ' +
    'zoom in central 320x180 of the video and fill the full display area. When this property is set to "out", ' +
    'the video is zoomed out without distortion so as to show the full video, e.g., is 320x240 video is displayed ' +
    'in 1280x720 it will expand full 320x240 to central 960x720 region on the display area, but leave blank spaces ' +
    'on left and right side of the video. Default is "in" which is well suited for video conferencing. If you have ' +
    'sub-titles or log to be displayed on top or bottom, use null or "out" to display the full video because "in" ' +
    'can hide parts of the video on the sides.\n';
    
    [Bindable('propertyChange')]
    /**
     * The zoom mode when aspect ratio of video is different than the display aspect ratio?
     */
    public function get zoom():String
    {
        return _zoom;
    }
    public function set zoom(value:String):void
    {
        var oldValue:String = _zoom;
        if (value != oldValue) {
            _zoom = value;
            resizeVideoHandler(null);
            sendVideoSize();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "zoom", oldValue, value));
        }
    }
    
    public const __doc__liveDelay:String =
    'The "liveDelay" read-only number property refers to the play streams live delay ' + 
    'If unavailable, this is NaN.\n';
     
    /**
     * The liveDelay of playing stream.
     */
    public function get liveDelay():Number
    {
        return _liveDelay;
    }
    
    public const __doc__currentFPS:String =
    'The "currentFPS" read-only number property refers to the current frames per second of video ' + 
    'display in live streaming mode. If unavailable, this is NaN.\n';
     
    /**
     * The current FPS of playing stream.
     */
    public function get currentFPS():Number
    {
        return _currentFPS;
    }
    
    public const __doc__bandwidth:String =
    'The "bandwidth" read-only number property indicates the stream bandwidth in bytes per second for live ' + 
    'play or publish mode. It is calculated using the currentBytesPerSecond property of the NetStream ' + 
    'object. This property gives the rough idea of the bandwidth utilization. It does not depend on ' + 
    'playbackBackBytesPerSecond of NetStream. If unavailable, this field is NaN.\n';
    
    /**
     * The bandwidth property the live NetStream.
     */
    public function get bandwidth():Number
    {
        return _bandwidth;
    }
    
    public const __doc__gain:String =
    'The "gain" read-write number property refers to the microphone gain, and is between ' + 
    '0 and 1. When set, it is scaled to 0-100 and applied to the gain property of the ' + 
    '"microphoneObject".\n';
    
    [Bindable('propertyChange')]
    /**
     * The microphone volume is a number between 0.0 and 1.0. When set, it updates the microphone
     * gain.
     */
    public function get gain():Number
    {
        return _gain;
    }
    public function set gain(value:Number):void
    {
        var oldValue:Number = _gain;
        _gain = value;
        if (_microphoneObject != null)
            _microphoneObject.gain = value * 100;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "gain", oldValue, value));
        }
    }
    
    public const __doc__level:String =
    'The "level" read-only number property refers to the microphone level, and is between ' + 
    '0 and 1. It is not valid only when "microphone" is true, indicating that the ' + 
    'microphone is actively capturing the audio.\n';
    
    [Bindable('propertyChange')]
    /**
     * The microphone level is the current activity level of the microphone.
     */
    public function get level():Number
    {
        return _level;
    }
    public function set level(value:Number):void
    {
        var oldValue:Number = _level;
        _level = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "level", oldValue, value));
        }
    }
    
    public const __doc__rate:String =
    'The "rate" read-write number property refers to the microphone sampling rate, and is ' + 
    '8 or 16. This is useful only when "codec" is "Speex" for the "microphoneObject". Default' + 
    'is 16. It represents the sampling rate of the codec in kHz. ' + 
    'It is not recommended to change this value unless you really need to use ' + 
    'a different sampling rate, e.g., for interoperating with telephony network you may need ' + 
    '8 kHz sampling.\n';
    
    [Bindable('propertyChange')]
    /**
     * The sampling rate property of the microphone.
     */
    public function get rate():Number
    {
        return _rate;
    }
    public function set rate(value:Number):void
    {
        var oldValue:Number = _rate;
        _rate = value;
        if (_microphoneObject != null)
            _microphoneObject.rate = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "rate", oldValue, value));
        }
    }
    
    public const __doc__codec:String =
    'The "codec" read-write string property refers to the microphone codec, and is ' + 
    '"Speex" or "NellyMoser". In Flash Player 11+, "PCMU" and "PCMA" are also supported. ' +
    'Default is "Speex".\n';
    
    [Bindable('propertyChange')]
    /**
     * The codec property of the microphone.
     */
    public function get codec():String
    {
        return _codec;
    }
    public function set codec(value:String):void
    {
        var oldValue:String = _codec;
        _codec = value;
        if (_microphoneObject != null)
            _microphoneObject.codec = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "codec", oldValue, value));
        }
    }
    
    public const __doc__encodeQuality:String =
    'The "encodeQuality" read-write number property refers to the microphone encodeQuality, and is ' + 
    'a number between 0 and 10, with 10 indicating highest quality. This is useful only when ' + 
    '"codec" is "Speex" and controls the bandwidth of the captured audio stream. ' + 
    'Default is 6.\n';
    
    [Bindable('propertyChange')]
    /**
     * The encodeQuality property of the microphone.
     */
    public function get encodeQuality():Number
    {
        return _encodeQuality;
    }
    public function set encodeQuality(value:Number):void
    {
        var oldValue:Number = _encodeQuality;
        _encodeQuality = value;
        if (_microphoneObject != null)
            _microphoneObject.encodeQuality = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "encodeQuality", oldValue, value));
        }
    }
    
    public const __doc__framesPerPacket:String =
    'The "framesPerPacket" read-write number property refers to the microphone framesPerPacket, ' + 
    'and is typically a small number like 1 or 2. Default is 1. It is not recommended to change ' + 
    'this value. Some versions of Flash Player are known to work well when "framesPerPacket" is ' + 
    '1 if the "codec" is "Speex".\n';
    
    [Bindable('propertyChange')]
    /**
     * The framesPerPacket property of the microphone.
     */
    public function get framesPerPacket():Number
    {
        return _framesPerPacket;
    }
    public function set framesPerPacket(value:Number):void
    {
        var oldValue:Number = _framesPerPacket;
        _framesPerPacket = value;
        if (_microphoneObject != null)
            _microphoneObject.framesPerPacket = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "framesPerPacket", oldValue, value));
        }
    }
    
    public const __doc__silenceLevel:String =
    'The "silenceLevel" read-write number property refers to the microphone silenece level, and is between ' + 
    '0 and 100. When set to 0, it disables silence suppression, and when set to 100, it disables any audio.' + 
    'Default is 0. Recommended value is 2.\n';
    
    [Bindable('propertyChange')]
    /**
     * The silenceLevel is between 0 and 100 for silence suppression: 0 means disable silence suppression,
     * and 100 means suppress all audio. Recommended value is 2 for video conference and 0 for video message
     * recording.
     */
    public function get silenceLevel():Number
    {
        return _silenceLevel;
    }
    public function set silenceLevel(value:Number):void
    {
        var oldValue:Number = _silenceLevel;
        _silenceLevel = value;
        if (_microphoneObject != null)
            _microphoneObject.setSilenceLevel(value);
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "silenceLevel", oldValue, value));
        }
    }
    
    public const __doc__echoSuppression:String =
    'The "echoSuppression" read-write boolean property controls whether echo suppression is enabled in ' + 
    'Flash Player or not. Default is true.\n';
    
    [Bindable('propertyChange')]
    /**
     * Controls the Microphone's setUseEchoSuppression().
     */
    public function get echoSuppression():Boolean
    {
        return _echoSuppression;
    }
    public function set echoSuppression(value:Boolean):void
    {
        var oldValue:Boolean = _echoSuppression;
        _echoSuppression = value;
        if (_microphoneObject != null)
            _microphoneObject.setUseEchoSuppression(value);
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "echoSuppression", oldValue, value));
        }
    }
    
    public const __doc__echoCancel:Object =
    'The "echoCancel" read-write Object property controls the echo cancellation mode when used with Flash Player 10.3 or ' +
    'later. For earlier versions, it does not have any effect. If true it uses getEnhancedMicrophone with the default echo ' +
    'cancellation options, and if false it uses the getMicrophone without echo cancellation options. Changing the property ' +
    'does not have any effect after "microphone" is set to true, which happens implicitly in publish mode. Default is true.\n';
    
    [Bindable('propertyChange')]
    /**
     * Controls the Microphone's echo cancellation mode.
     */
    public function get echoCancel():Boolean
    {
        return _echoCancel;
    }
    public function set echoCancel(value:Boolean):void
    {
        var oldValue:Boolean = _echoCancel;
        _echoCancel = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "echoCancel", oldValue, value));
        }
    }
    
    public const __doc__sound:String =
    'The "sound" read-write boolean property refers to whether the play sound is on or off. ' + 
    'Default is true. This property is also attached to the speaker on/off icon on the ' + 
    'control panel if visible. This controls the global sound volume of the application.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the speaker is active or not (muted)?
     */
    public function get sound():Boolean
    {
        return _sound;
    }
    public function set sound(value:Boolean):void
    {
        var oldValue:Boolean = _sound;
        _sound = value;
        updateMixer();
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "sound", oldValue, value));
        }
    }
    
    public const __doc__volume:String =
    'The "volume" read-write number property refers to play sound volume between 0 and 1 ' + 
    'with 1 indicating full volume. Default is 0.5. ' + 
    'This property is also attached to the speaker volume slider on the ' + 
    'control panel if visible. This controls the global sound volume of the application.\n';
    
    [Bindable('propertyChange')]
    /**
     * The speaker volume property controls the volume level of the global SoundMixer.
     */
    public function get volume():Number
    {
        return _volume;
    }
    public function set volume(value:Number):void
    {
        var oldValue:Number = _volume;
        _volume = value;
        updateMixer();
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "volume", oldValue, value));
        }
    }
    
    public const __doc__smoothing:String =
    'The "smoothing" read-write boolean property refers to whether smoothing is applied to ' + 
    'the "video" display or not. Default is true. Since smoothing algorithm take CPU cycles, ' + 
    'applications may want to reset this property to reduce CPU utilization during playback.\n';
    
    [Bindable('propertyChange')]
    /**
     * The smoothing property controls the smoothing of various Video objects in the application.
     */
    public function get smoothing():Boolean
    {
        return _smoothing;
    }
    public function set smoothing(value:Boolean):void
    {
        var oldValue:Boolean = _smoothing;
        _smoothing = value;
        if (video != null)
            video.smoothing = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "smoothing", oldValue, value));
        }
    }
    
    //public const __doc__cameraAspectRatio:String =
    //'The "cameraAspectRatio" read-write number property controls to the camera capture aspect ratio. ' + 
    //'Default is 4/3 which is 1.333333... For using non-standard aspect ratio such as HD, first set "cameraAspectRatio" ' + 
    //'and then set either "cameraWidth" or "cameraHeight". When using non-default aspect ratio, it is the ' +
    //'application\'s responsibility to set "cameraAspectRatio" of both publish and play side to be the same. ' +
    //'The play side also needs the correct value for correct display even though the value is not applied to a camera. ' +
    //' When this property is changed, the "cameraWidth" or "cameraHeight" are not automatically adjusted ' +
    //'immediately, but it waits until either "cameraWidth" or "cameraHeight" is set, and then both the properties ' +
    //'are adjusted. You can also set this property to a string, e.g., "4:3" or "16:9" but reading it will always ' +
    //'return the number representation.\n';
    //
    //[Bindable('propertyChange')]
    ///**
    // * The camera aspect ratio.
    // */
    //public function get cameraAspectRatio():Object
    //{
    //    return _cameraAspectRatio;
    //}
    //public function set cameraAspectRatio(value:Object):void
    //{
    //    var oldValue:Number = _cameraAspectRatio as Number;
    //    if (value is Number) {
    //        _cameraAspectRatio = value as Number;
    //    }
    //    else if (value is String) {
    //        try {
    //            var parts:Array = value.split(":");
    //            _cameraAspectRatio = parseInt(parts[0])/parseInt(parts[1]);
    //        } catch (e:Error) {
    //            trace("error parsing the cameraAspectRatio " + value);
    //        }
    //    }
    //    if (oldValue != _cameraAspectRatio) {
    //        dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraAspectRatio", oldValue, _cameraAspectRatio));
    //    }
    //}
    
    public const __doc__cameraDimension:String =
    'The "cameraDimension" read-write string property controls to the camera capture dimension. ' + 
    'Default is "320x240". This property should be used instead of "cameraWidth" or "cameraHeight" ' +
    'and is required when setting to a non-standard aspect ratio such as HD camera capture. ' +
    'When this property is changed, the "cameraWidth" and "cameraHeight" are also changed as needed. ' +
    'When the display size is not 320x240, it periodically transmits the publish dimension to the player ' +
    'using NetStream\'s setVideoSize function, which in turn is used by the play side to adjust the ' +
    'aspect ratio of the display.\n';
    
    [Bindable('propertyChange')]
    /**
     * The camera capture dimension.
     */
    public function get cameraDimension():String
    {
        return "" + _cameraWidth + "x" + _cameraHeight;
    }
    public function set cameraDimension(value:String):void
    {
        var oldWidth:Number = _cameraWidth;
        var oldHeight:Number = _cameraHeight;
        var oldDimension:String = "" + _cameraWidth + "x" + _cameraHeight;
        if (oldDimension != value) {
            try {
                var parts:Array = value.split("x");
                var width:Number = parseInt(parts[0]);
                var height:Number = parseInt(parts[1]);
                // after both width and height are parsed.
                _cameraWidth = width;
                _cameraHeight = height;
                trace("changed from " + oldWidth + "x" + oldHeight + " to " + _cameraWidth + "x" + _cameraHeight);
            } catch (e:Error) {
                trace("error parsing the cameraDimension " + value);
                return;
            }
            setCameraMode();
            resizeVideoHandler(null);
            if (oldWidth != _cameraWidth)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraWidth", oldWidth, _cameraWidth));
            if (oldHeight != _cameraHeight)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraHeight", oldHeight, _cameraHeight));
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraDimension", oldDimension, value));
        }
    }
    
    public const __doc__cameraWidth:String =
    'The "cameraWidth" (deprecated) read-write number property refers to the camera capture width in pixels. ' + 
    'Default is 320. Higher values give better quality picture but consume higher bandwidth. The ' + 
    'application may want to increase the resolution of capture for higher quality recording. ' + 
    'When setting "cameraWidth" it also changes "cameraHeight" and "cameraDimension" to keep the ' +
    'aspect ratio of 4:3. To change the aspect ratio you must set "cameraDimension" instead of ' +
    '"cameraWidth" or "cameraHeight".\n';
    
    [Deprecated]
    [Bindable('propertyChange')]
    /**
     * The camera capture width.
     */
    public function get cameraWidth():Number
    {
        return _cameraWidth;
    }
    public function set cameraWidth(value:Number):void
    {
        var oldWidth:Number = _cameraWidth;
        var oldDimension:String = "" + _cameraWidth + "x" + _cameraHeight;
        _cameraWidth = value;
        if (oldWidth != value) {
            var oldHeight:Number = _cameraHeight;
            _cameraHeight = Math.round(value*(3/4));
            var dimension:String = "" + _cameraWidth + "x" + _cameraHeight;
            setCameraMode();
            resizeVideoHandler(null);
            if (oldWidth != _cameraWidth)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraWidth", oldWidth, _cameraWidth));
            if (oldHeight != _cameraHeight)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraHeight", oldHeight, _cameraHeight));
            if (oldDimension != dimension)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraDimension", oldDimension, dimension));
        }
    }
    
    public const __doc__cameraHeight:String =
    'The "cameraHeight" (deprecated) read-write number property refers to the camera capture height in pixels. ' + 
    'Default is 240. Higher values give better quality picture but consume higher bandwidth. The ' + 
    'application may want to increase the resolution of capture for higher quality recording. ' + 
    'When setting "cameraHeight" it also changes "cameraWidth" and "cameraDimension" to keep the ' +
    'aspect ratio of 4:3. To change the aspect ratio you must set "cameraDimension" instead of ' +
    '"cameraWidth" or "cameraHeight".\n';
    
    [Deprecated]
    [Bindable('propertyChange')]
    /**
     * The camera capture height.
     */
    public function get cameraHeight():Number
    {
        return _cameraHeight;
    }
    public function set cameraHeight(value:Number):void
    {
        var oldHeight:Number = _cameraHeight;
        var oldDimension:String = "" + _cameraWidth + "x" + _cameraHeight;
        _cameraHeight = value;
        if (oldHeight != value) {
            var oldWidth:Number = _cameraWidth;
            _cameraWidth = Math.round(value*(4/3));
            var dimension:String = "" + _cameraWidth + "x" + _cameraHeight;
            setCameraMode();
            resizeVideoHandler(null);
            if (oldWidth != _cameraWidth)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraWidth", oldWidth, _cameraWidth));
            if (oldHeight != _cameraHeight)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraHeight", oldHeight, _cameraHeight));
            if (oldDimension != dimension)
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraDimension", oldDimension, dimension));
        }
    }
    
    public const __doc__cameraFPS:String =
    'The "cameraFPS" read-write number property refers to the camera frames per second. ' + 
    'Default is 12. Higher values give better quality picture but consume higher bandwidth. The ' + 
    'application may want to increase the frames per second for higher quality recording.\n';
    
    [Bindable('propertyChange')]
    /**
     * The fps (frames-per-second) property of the camera.
     */
    public function get cameraFPS():Number
    {
        return _cameraFPS;
    }
    public function set cameraFPS(value:Number):void
    {
        var oldValue:Number = _cameraFPS;
        _cameraFPS = value;
        if (oldValue != value) {
            setCameraMode();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraFPS", oldValue, value));
        }
    }
    
    public const __doc__cameraBandwidth:String =
    'The "cameraBandwidth" read-write number property controls the maximum bandwidth in ' + 
    'bytes per second of the camera. A value of 0 means the camera can use as much ' + 
    'bandwidth as needed to maintain the desired "quality". Ideally only "cameraBandwidth" or ' + 
    '"cameraQuality" but not both should be set. Default is 0.\n';
    
    [Bindable('propertyChange')]
    /**
     * The bandwidth property of the camera.
     */
    public function get cameraBandwidth():Number
    {
        return _cameraBandwidth;
    }
    public function set cameraBandwidth(value:Number):void
    {
        var oldValue:Number = _cameraBandwidth;
        _cameraBandwidth = value;
        if (_cameraObject != null)
            _cameraObject.setQuality(_cameraBandwidth, _cameraQuality);
        if (CONFIG::player11) {
            if (_local != null && _local.videoStreamSettings != null) {
                _local.videoStreamSettings.setQuality(_cameraBandwidth, _cameraQuality);
            }
        }
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraBandwidth", oldValue, value));
        }
    }
    
    public const __doc__cameraQuality:String =
    'The "cameraQuality" read-write number property controls the desired frame quality ' + 
    'of the camera and is between 0 and 100. A value of 0 means the quality can vary as needed ' + 
    'to avoid exceeding the available "bandwidth". A value of 1 means lowest quality with ' + 
    'maximum compressions and 100 means highest quality without compression. ' + 
    'Ideally only "cameraBandwidth" or "cameraQuality" but not both should be set. ' + 
    'Default is 0.\n';
    
    [Bindable('propertyChange')]
    /**
     * The quality property of the camera.
     */
    public function get cameraQuality():Number
    {
        return _cameraQuality;
    }
    public function set cameraQuality(value:Number):void
    {
        var oldValue:Number = _cameraQuality;
        _cameraQuality = value;
        if (_cameraObject != null)
            _cameraObject.setQuality(_cameraBandwidth, _cameraQuality);
        if (CONFIG::player11) {
            if (_local != null && _local.videoStreamSettings != null) {
                _local.videoStreamSettings.setQuality(_cameraBandwidth, _cameraQuality);
            }
        }
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraQuality", oldValue, value));
        }
    }
    
    public const __doc__keyFrameInterval:String =
    'The "keyFrameInterval" read-write number property controls the desired key frame interval ' + 
    'of the camera. Default value is 15, which means every 15th frame is a key frame. ' + 
    'Allowed values are 1 to 28.\n';
    
    [Bindable('propertyChange')]
    /**
     * The keyFrameInterval property of the camera.
     */
    public function get keyFrameInterval():Number
    {
        return _keyFrameInterval;
    }
    public function set keyFrameInterval(value:Number):void
    {
        var oldValue:Number = _keyFrameInterval;
        _keyFrameInterval = value;
        if (_cameraObject != null)
            _cameraObject.setKeyFrameInterval(_keyFrameInterval);
        if (CONFIG::player11) {
            if (_local != null && _local.videoStreamSettings != null) {
                _local.videoStreamSettings.setKeyFrameInterval(_keyFrameInterval);
            }
        }
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "keyFrameInterval", oldValue, value));
        }
    }
    
    public const __doc__cameraLoopback:String =
    'The "cameraLoopback" read-write number property controls whether a local view of what the ' + 
    'camera is capturing is compressed and decompressed (true), as it would be for live ' + 
    'transmission, or whether the local view is uncompressed (false). The default value is false.\n';
    
    [Bindable('propertyChange')]
    /**
     * The loopback property of the camera.
     */
    public function get cameraLoopback():Boolean
    {
        return _cameraLoopback;
    }
    public function set cameraLoopback(value:Boolean):void
    {
        var oldValue:Boolean = _cameraLoopback;
        _cameraLoopback = value;
        if (_cameraObject != null)
            _cameraObject.setLoopback(value);
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "cameraLoopback", oldValue, value));
        }
    }
    
    public const __doc__videoCodec:String =
    'The "videoCodec" read-write string property refers to the publishing NetStream video codec, and is ' + 
    '"Sorenson" or "H264Avc". This property is only available when using Flash Player 11+, otherwise setting ' +
    'this property is ignored. Additionally for H264Avc, profile and level are supplied, e.g., "H264Avc/baseline/2". ' +
    'Default is "Sorenson".\n';
    
    [Bindable('propertyChange')]
    /**
     * The video codec property of the publish stream.
     */
    public function get videoCodec():String
    {
        return _videoCodec;
    }
    public function set videoCodec(value:String):void
    {
        if (CONFIG::player11) {
            var oldValue:String = _videoCodec;
            _videoCodec = value;
            if (_local != null) {
                _local.videoStreamSettings = createVideoStreamSettings();
            }
            if (oldValue != value) {
                dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "videoCodec", oldValue, value));
            }
        }
        else {
            trace("ignoring videoCodec property in this version of Flash Player");
        }
    }
    
    CONFIG::player11
    private function createVideoStreamSettings():flash.media.VideoStreamSettings {
        var s:flash.media.VideoStreamSettings;
        if (_videoCodec.substr(0, 7) == "H264Avc") {
            var parts:Array = _videoCodec.split("/");
            var profile:String = parts.length >= 2 ? parts[1] : flash.media.H264Profile.BASELINE;
            var level:String = parts.length >= 3 ? parts[2] : flash.media.H264Level.LEVEL_2;
            s = new flash.media.H264VideoStreamSettings();
            flash.media.H264VideoStreamSettings(s).setProfileLevel(profile, level);
            trace("using H264Avc/" + profile + "/" + level);
        } else if (_videoCodec == "Sorenson") {
            s = new flash.media.VideoStreamSettings();
        } else {
            trace("ignoring invalid videoCodec property: " + _videoCodec + ". using Sorenson");
            s = new flash.media.VideoStreamSettings();
        }
        // copy Camera settings to VideoStreamSettings.
        s.setKeyFrameInterval(_keyFrameInterval);
        s.setMode(_cameraWidth, _cameraHeight, _cameraFPS);
        s.setQuality(_cameraBandwidth, _cameraQuality);
        return s;
    }
    
    public const __doc__currentTime:String =
    'The "currentTime" read-write number property refers to the current play head position, ' + 
    'in seconds, since the video started to play or record. For playback of live stream, the ' + 
    'playhead position may start from some positive value if the player joins the stream after ' + 
    'some time. If the current time is not known, the value is NaN. This property is also ' + 
    'attached to the numeric display (as formatted duration in hh:mm:ss) or current position ' + 
    'in the playhead bar on the control panel, if available.\n';
    
    [Bindable('propertyChange')]
    /**
     * The current time in play mode.
     */
    public function get currentTime():Number
    {
        return _currentTime;
    }
    public function set currentTime(value:Number):void
    {
        var oldValue:Number = _currentTime;
        _currentTime = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "currentTime", oldValue, value));
        }
    }
    
    /**
     * Use setCurrentTime instead of setting currentTime directly when seeking to a new
     * position. The currentTime is automatically updated based on the playheadTime of
     * videoDisplay or time of playing NetStream.
     */
    public function setCurrentTime(value:Number):void
    {
        this.currentTime = value;
        if (videoDisplay != null)
            videoDisplay.playheadTime = value;
        if (play != null && _remote != null)
            _remote.seek(value);
    }
    
    public const __doc__duration:String =
    'The "duration" read-write number property refers to the total length of the media, ' + 
    'in seconds. For live or recorded streams, this value is unavailable or NaN. For real-time ' + 
    'playback of the stream or downloaded video file, this value may not be available, unless ' + 
    'set by metadata or has finished playback once. This property is also attached to the ' + 
    'length of the playhead bar on the control panel, if available.\n';
    
    [Bindable('propertyChange')]
    /**
     * The duration in play mode.
     */
    public function get duration():Number
    {
        return _duration;
    }
    public function set duration(value:Number):void
    {
        var oldValue:Number = _duration;
        _duration = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "duration", oldValue, value));
        }
    }

    public const __doc__bytesLoaded:String =
    'The "bytesLoaded" read-only number property indicates the number of bytes of data that ' + 
    'has been loaded so far in the system. If unavailable, this is NaN.\n';
    
    /**
     * The total bytes loaded so far in play mode.
     */
    public function get bytesLoaded():Number
    {
        return _bytesLoaded;
    }

    public const __doc__bytesTotal:String =
    'The "bytesTotal" read-only number property indicates the total size of the file being ' + 
    'loaded in the system. If unavailable, such as for live streams, this is NaN.\n';
    
    /**
     * The total bytes of media file in play mode.
     */
    public function get bytesTotal():Number
    {
        return _bytesTotal;
    }

    public const __doc__playerState:String =
    'The "playerState" read-only string property indicates the player state when playing an "http" or "https" ' +
    'URL. For other play or publish modes, the value is null. ' +
    'Please see the "VideoDisplay.state" property in Flex documentation for details.\n';
    
    /**
     * Player's state (VideoDisplay)
     */
    public function get playerState():String
    {
        return _playerState;
    }

    public const __doc__quality:String =
    'The "quality" read-only number property indicates the quality of the play stream as ' + 
    'number between 0 (very low) and 1 (very high). The quality is calculated based on ' + 
    'some combination of other metrics such as number of packet losses and delay of ' + 
    'the stream. This property is also attached to the quality bars displayed in the ' + 
    'control panel in stream play mode displaying 4 bars indicating quality values in ' + 
    'range of (0, 0.25], (0.25, 0.5], (0.5, 0.75], (0.75, 1.0]), respectively.\n';
    
    [Bindable('propertyChange')]
    /**
     * The quality of stream in play mode.
     */
    public function get quality():Number
    {
        return _quality;
    }
    public function set quality(value:Number):void
    {
        var oldValue:Number = _quality;
        _quality = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "quality", oldValue, value));
        }
    }

    public const __doc__bufferTime:String =
    'The "bufferTime" read-write number property controls the playing stream bufferTime in seconds. ' +
    'Default is negative (-1.0) which indicates do not set. If you are experiencing choppiness, ' +
    'set it to a higher value.\n';
    
    [Bindable('propertyChange')]
    /**
     * The bufferTime of stream in play mode.
     */
    public function get bufferTime():Number
    {
        return _bufferTime;
    }
    public function set bufferTime(value:Number):void
    {
        var oldValue:Number = _bufferTime;
        _bufferTime = value;
        if (value != oldValue) {
            if (_remote != null) {
                _remote.bufferTime = value >= 0 ? value : 0;
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "bufferTime", oldValue, value));
        }
    }
                
    public const __doc__bufferTimeMax:String =
    'The "bufferTimeMax" read-write number property controls the playing stream bufferTimeMax in seconds. ' +
    'Default is negative (-1.0) which indicates do not set. This property puts an upper limit on bufferTime.\n';
    
    [Bindable('propertyChange')]
    /**
     * The bufferTimeMax of stream in play mode.
     */
    public function get bufferTimeMax():Number
    {
        return _bufferTimeMax;
    }
    public function set bufferTimeMax(value:Number):void
    {
        var oldValue:Number = _bufferTimeMax;
        _bufferTimeMax = value;
        if (value != oldValue) {
            if (_remote != null && _remote.hasOwnProperty("bufferTimeMax")) {
                _remote["bufferTimeMax"] = value >= 0 ? value : 0;
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "bufferTimeMax", oldValue, value));
        }
    }
                
    public const __doc__fullscreen:String =
    'The "fullscreen" read-only property controls the full screen mode of the application.' + 
    'Due to restriction in Flash Player, this property can only be set in response to ' + 
    'a mouse click or button press within Flash Player, hence setting this property by ' + 
    'the external application has no effect. This property is also attached to the fullscreen ' + 
    'button state in control panel, and the clicking the button triggers or comes out of ' + 
    'full screen mode.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the current mode is full screen or not?
     */
    public function get fullscreen():Boolean
    {
        return _fullscreen;
    }
    public function set fullscreen(value:Boolean):void
    {
        var oldValue:Boolean = _fullscreen;
        _fullscreen = value;
        if (oldValue != value) {
            try {
                stage.displayState = value ? StageDisplayState.FULL_SCREEN: StageDisplayState.NORMAL;
            } catch (e:SecurityError) {
                _fullscreen = oldValue;
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "fullScreen", oldValue, value));
        }
    }

    public const __doc__enableFullscreen:String =
    'The "enableFullscreen" read-write property controls whether full screen mode is enabled ' + 
    'or not. Default is true. If the full screen mode is disabled, then the user interface ' + 
    'elements needed to do full screen are hidden. Resetting this property to false is ' + 
    'useful when you know that full screen is not available, e.g., in Facebook FBML application.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the full screen button is enabled or not?
     */
    public function get enableFullscreen():Boolean
    {
        return _enableFullscreen;
    }
    public function set enableFullscreen(value:Boolean):void
    {
        var oldValue:Boolean = _enableFullscreen;
        _enableFullscreen = value;
        if (oldValue != value) {
            if (value) {
                var fullscreen:ContextMenuItem = new ContextMenuItem("Toggle full-screen");
                menu.customItems.push(fullscreen);
                fullscreen.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, fullScreenMenuHandler);
            }
            else {
                var menu:ContextMenu;
                if (CONFIG::sdk4) {
                    menu = mx.core.FlexGlobals.topLevelApplication.contextMenu;
                }
                else {
                    menu = Application.application.contextMenu;
                }
                var found:Boolean = false;
                for (var i:int=0; i<menu.customItems.length; ++i) {
                    if (menu.customItems[i].caption == "Toggle full-screen") {
                        menu.customItems.splice(i, 1);
                        break;
                    }
                }
                
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "enableFullscreen", oldValue, value));
        }
    }
    
    public const __doc__enableFullscreenOnDoubleClick:String =
    'The "enableFullscreenOnDoubleClick" read-write property controls whether full screen mode is toggled ' + 
    'on double-click or not. Default is false. This is useful because a bug in Flash Player prevents the ' +
    'right-click context-menu to toggle the full screen when the video is displayed on top. This property ' +
    'provides an alternative or work-around to that problem. This property is independent of the ' +
    '"enableFullscreen" property.\n';
    
    [Bindable('propertyChange')]
    /**
     * Whether the full screen is enabled on double-click or not?
     */
    public function get enableFullscreenOnDoubleClick():Boolean
    {
        return _enableFullscreenOnDoubleClick;
    }
    public function set enableFullscreenOnDoubleClick(value:Boolean):void
    {
        var oldValue:Boolean = _enableFullscreenOnDoubleClick;
        _enableFullscreenOnDoubleClick = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "enableFullscreenOnDoubleClick", oldValue, value));
        }
    }
    
//        public const __doc__sip:String =
//        'The "sip" read-write property controls and indicates the SIP call state as follows:\n' + 
//        ' Setting to "invite:sip:user@domain" will initiate a SIP call to the target destination.\n' +
//        ' Value of "inviting:sip:user@domain" indicates that outgoing call invitation is in progress.\n' + 
//        ' Setting to "idle" will terminate any active or pending call.\n' + 
//        ' Value of "invited:sip:user@domain" indicates that the target user is inviting you.\n' + 
//        ' Setting to "active" in "inviting" state will accept an incoming call.\n' +
//        ' Setting to "idle:reason" in "inviting" state will reject an incoming call with supplied reason.\n' + 
//        'This property assumes that "src" property was already set correctly to connect to a "siprtmp"\n' + 
//        'gateway and to register using additional arguments such as local SIP URI, auth name and password.\n' +
//        'When you are in a SIP call, the "play" and "publish" property are automatically set to "local" and "remote" ' +
//        'as required by the "siprtmp" software, and previously set values of "play" and "publish" properties are ' +
//        'ignored.\n';
//        
//        [Bindable('propertyChange')]
//        /**
//         * The SIP call state property.
//         */
//        public function get sip():String
//        {
//            return _sip;
//        }
//        public function set sip(value:String):void
//        {
//            _sip = value;
//        }
    
    public const __doc__group:String =
    'The "group" read-write string property refers to the group name for application ' + 
    'level multicast in both publish and play mode. Although this is a read-write property ' + 
    'the application should use the "group" parameter of the "src" property to set this. ' + 
    'For example, if the "src" is set to "rtmp://server/path?group=my/group1&publish=live1" ' + 
    'then the "group" property is automatically set to "my/group1". This "group" property ' + 
    'must be set before publish or play, so that it takes effect before publishing or playing.\n';
    
    [Bindable('propertyChange')]
    /**
     * The group-name for publish or play.
     */
    public function get group():String
    {
        return _group;
    }
    public function set group(value:String):void
    {
        var oldValue:String = _group;
        _group = value;
        // TODO: cannot be set in active call.
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "group", oldValue, value));
        }
    }
    
    public const __doc__snapshot:String = 
    'The "snapshot" read-write string property represents a one-time snapshot of the video stream. ' + 
    'It represents the base64 encoded JPEG image data. The application can get the snapshot and ' + 
    'publish it to the backend. The application can set the snapshot to display the image.\n';
    
    [Bindable('propertyChange')]
    /**
     * The snapshot property to represent instantaneous base64 of JPEG image data.
     */
    public function get snapshot():String
    {
        return _snapshot;
    }
    public function set snapshot(value:String):void
    {
        var oldValue:String = _snapshot;
        _snapshot = value;
        if (oldValue != value) {
            if (oldValue != null) 
                detachPoster();
            if (value != null)
                attachPoster();
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "snapshot", oldValue, value));
        }
    }
    
    public const __doc__objectEncoding:String = 
    'The "objectEncoding" read-write number property represents the NetConnection objectEncoding and defaults to 3. ' +
    'Possible values are 3 and 0 for AMF3 and AMF0 respectively. ' +
    'This property must be set before url or src is set so that the value is applied before invoking the connect method on ' +
    'NetConnection. Setting it after the connection is initiated has no effect on the current connection.\n';
    
    [Bindable('propertyChange')]
    /**
     * The objectEncoding of the NetConnection.
     */
    public function get objectEncoding():Number
    {
        return _objectEncoding;
    }
    public function set objectEncoding(value:Number):void
    {
        var oldValue:Number = _objectEncoding;
        _objectEncoding = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "objectEncoding", oldValue, value));
        }
    }
    
    public const __doc__proxyType:String = 
    'The "proxyType" read-write string property determines which fallback methods are tried if an initial connection attempt ' +
    'fails. Possible values are "none", "best", "HTTP", and "CONNECT", and default is "none". The value of "best" ' +
    'is particularly useful when using "rtmps" with native SSL instead of HTTPS tunneling. '
    'This property must be set before url or src is set so that the value is applied before invoking the connect method on ' +
    'NetConnection. Setting it after the connection is initiated has no effect on the current connection.\n';
    
    [Bindable('propertyChange')]
    /**
     * The proxyType of the NetConnection.
     */
    public function get proxyType():String
    {
        return _proxyType;
    }
    public function set proxyType(value:String):void
    {
        var oldValue:String = _proxyType;
        _proxyType = value;
        if (oldValue != value) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "proxyType", oldValue, value));
        }
    }
    
    
    public const __doc__multicastWindowDuration:String =
    'Mirror of NetStream\'s multicastWindowDuration property.\n';
    
    private var _multicastWindowDuration:Number
    
    [Bindable('propertyChange')]
    public function get multicastWindowDuration():Number
    {
        return _multicastWindowDuration;
    }
    public function set multicastWindowDuration(value:Number):void
    {
        var oldValue:Number = _multicastWindowDuration;
        _multicastWindowDuration = value;
        if (value != oldValue) {
            var ns:NetStream = (_local != null ? _local : _remote);
            if (ns != null && ns.hasOwnProperty("multicastWindowDuration")) {
                ns["multicastWindowDuration"] = value;
            }
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "multicastWindowDuration", oldValue, value));
        }
        
    }
    
    //--------------------------------------
    // PUBLIC METHODS
    //--------------------------------------
    
    public const __doc__call:String =
    'The "call" method can be used to call some method on the server using the connected ' +
    'NetConnection object. It invokes the call method on the NetConnection object. ' +
    'In the reverse direction, the callback event is posted with method and args when ' +
    'the server calls a method on the client using the NetConnection object. The callback ' + 
    'event eventually invokes the onCallback Javascript function. ' + 
    'The call() method and callback event are useful for interacting with intelligent ' + 
    'server such as SIP-RTMP gateway (siprtmp) to perform PC-to-phone or phone-to-PC calls.\n' +
    'For example, if you are running the gateway on localhost, then ' + 
    'to register as "sip:kundan@localhost" with authname "kundan", password "mypass", ' + 
    'display-name "Kundan Singh", rate "narrowband", you can use the following "src" property.\n' + 
    ' rtmp://localhost/sip/kundan@localhost?arg=kundan&arg=kundan&arg=Kundan Singh&arg=narrowband\n';
    
    /**
     * Method to call something on the server using the connected NetConnection object's call() method.
     */
    public function call(methodName:String, ...args):void
    {
        if (nc != null && nc.connected) {
            var func:Function = nc.call;
            args.splice(0, 0, methodName, null);
            func.apply(nc, args);
        }
        else {
            throw new Error("not connected to server");
        }
    }
    
    public const __doc__post:String =
    'The "post" method can be used to send some message to the net group associated with the ' +
    'application level multicast group. It invokes the post method on the NetGroup object. ' +
    'In the reverse direction, the postingNotify event is posted with user and text when ' +
    'an incoming message is received on the NetGroup object. The postingNotify ' + 
    'event eventually invokes the onPostingEvent Javascript function. ' + 
    'The post() method and postingNotify event are useful for implementing multicast group ' + 
    'communication, e.g., multiparty text chat. Note that for the "post" method to work, ' + 
    'you must set the "group" property to join a net group.\n';
    
    /**
     * Method to post something to the net group.
     */
    public function post(user:String, text:String):void
    {
        if (nc != null && nc.connected && _netGroup != null) {
            var message:Object = new Object();
            message.sender = _netGroup.convertPeerIDToGroupAddress(nc.nearID);
            message.user = user;
            message.text = text;
            _netGroup.post(message);
        }
        else {
            throw new Error("not connected to net group");
        }
    }
    
    public const __doc__sendData:String =
    'The "sendData" method can be used to send some text in a publishing stream to all the other subscribed ' +
    'playing streams. This is particularly useful for P2P RTMFP mode using Stratus service where you cannot use ' +
    'the call() method and implement a server side feature, but you need an end-to-end text/data transfer mechanism.' +
    'Unlike the post() method that works in a group, the sendData() method is useful for client-server and ' +
    'peer-to-peer streams. The other end receives a onReceiveData(data) callback method invoked when it receives ' +
    'the sendData command in the stream. For a one-to-many stream, the publishers calls sendData and all the ' +
    'players receive onReceiveData callback. You can call sendData only if VideoIO is publishing, and will get ' +
    'onReceiveData only if VideoIO is playing. It is up to the application to define the meaning of the text data ' +
    'send and received via this method.\n';
    
    /**
     * Method to sendData in a publish stream which is received by all play streams.
     */
    public function sendData(data:String):void
    {
        if (_local != null) {
            trace("sending sendData(" + data + ")");
            _local.send("sendData", data);
        }
        else {
            trace("sendData() ignored with no publish stream");
        }
    }
    
    public const __doc__showSettings:String =
    'The "showSettings" method shows the Flash Player security settings. The application ' + 
    'may detect on launch that "deviceAllowed" is false, and invoke this prompt to get ' + 
    'device permissions. If "privacyEvent" is true then "hidingSettings" is dispatched when ' +
    'this application receives focus again.\n';
    
    /**
     * Method to show Flash Player security settings.
     */
    public function showSettings():void
    {
        if (privacyEvent && settingsTimer == null) {
            dispatchEvent(new Event("showingSettings"));
            
            if (CONFIG::sdk4) {
                var stage:Stage = mx.core.FlexGlobals.topLevelApplication.stage;
            }
            else {
                var stage:Stage = Application.application.stage;
            }
            if (settingsTimer == null) {
                stageChildren = stage.numChildren;
                settingsTimer = new Timer(100, 0);
                settingsTimer.addEventListener(TimerEvent.TIMER, settingsTimerHandler, false, 0, true);
                settingsTimer.start();
            }
        }
        Security.showSettings(SecurityPanel.PRIVACY);
    }
    
    //--------------------------------------
    // PRIVATE METHODS
    //--------------------------------------
    
    // Private setter to dispatch propertyChange event.
    private function setProperty(property:String, value:Object):void
    {
        var oldValue:Object = this["_" + property];
        this["_" + property] = value;
        if (value != oldValue) {
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, property, oldValue, value));
        }
    }
    
    // set the camera's setMode and if player11 then video stream's setMode as well.
    private function setCameraMode():void
    {
        if (_cameraObject != null)
            _cameraObject.setMode(_cameraWidth, _cameraHeight, _cameraFPS);
        if (CONFIG::player11) {
            if (_local != null && _local.videoStreamSettings != null) {
                _local.videoStreamSettings.setMode(_cameraWidth, _cameraHeight, _cameraFPS);
            }
        }
    }
    
    // when settings timer fires, check if the stage.numChildren is same as before
    // showSettings, and if yes, that means the security panel is closed.
    // In that case dispatch the hidingSettings event.
    private function settingsTimerHandler(event:TimerEvent):void
    {
        if (CONFIG::sdk4) {
            var stage:Stage = mx.core.FlexGlobals.topLevelApplication.stage;
        }
        else {
            var stage:Stage = Application.application.stage;
        }
        if (settingsTimer != null && (stage.numChildren == stageChildren)) {
            settingsTimer.removeEventListener(TimerEvent.TIMER, settingsTimerHandler);
            settingsTimer.stop();
            settingsTimer = null;
            if (privacyEvent) {
                dispatchEvent(new Event("hidingSettings"));
            }
        }
    }
    
    // update the global soundmixer based on the current speaker volume.
    private function updateMixer():void
    {
        var transform:SoundTransform = new SoundTransform();
        transform.volume = (_sound ? _volume : 0);
        SoundMixer.soundTransform = transform;
    }
    
    private function cameraStatusHandler(event:StatusEvent):void
    {
        var oldValue:Boolean = _camera;
        if (deviceAllowed) {
            setProperty("deviceAllowed", true);
        }
        else {
            setProperty("deviceAllowed", false);
        }
    }
    
    private function micStatusHandler(event:StatusEvent):void
    {
        var oldValue:Boolean = _microphone;
        if (deviceAllowed) {
            setProperty("deviceAllowed", true);
        }
        else {
            setProperty("deviceAllowed", false);
        }
    }
    
    private function startMicLevelTimer():void
    {
        if (_micLevelTimer == null) {
            _micLevelTimer = new Timer(100, 0);
            _micLevelTimer.addEventListener(TimerEvent.TIMER, micLevelTimerHandler, false, 0, true);
            _micLevelTimer.start();
        }
    }
    
    private function stopMicLevelTimer():void
    {
        if (_micLevelTimer != null) {
            _micLevelTimer.stop();
            _micLevelTimer = null;
        }
    }
    
    private function micLevelTimerHandler(event:TimerEvent):void
    {
        level = new Number((_microphoneObject != null && !_microphoneObject.muted && _microphoneObject.activityLevel >= 0) ? _microphoneObject.activityLevel / 100 : 0);
    }
    
    private function connect():void
    {
        if (nc == null && url != '') {
            nc = new NetConnection();
            nc.proxyType = proxyType;
            nc.objectEncoding = objectEncoding;

            // The CallProxy approach now works.
            nc.client = new CallProxy(this);

            nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler, false, 0, true);
            nc.addEventListener(IOErrorEvent.IO_ERROR, errorHandler, false, 0, true);
            nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler, false, 0, true);
            nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler, false, 0, true);
            
            var func:Function = nc.connect;
            var args:Array = [url];
            for each (var arg:Object in this.args) 
                args.push(arg);
                
            _dispatchDisconnect = true;
            trace('connect() ' + args);
            func.apply(nc, args);
        }
    }

    private function called(method:String, ...args):void 
    {
        var event:DynamicEvent = new DynamicEvent("callback");
        event.method = method;
        event.args = args;
        trace("CallProxy dispatchEvent type=callback method=" + method);
        dispatchEvent(event);
    }
    
    private function called2(method:String, args:Array):void 
    {
        var event:DynamicEvent = new DynamicEvent("callback");
        event.method = method;
        event.args = args;
        trace("CallProxy dispatchEvent type=callback method=" + method);
        dispatchEvent(event);
    }
    
    private function disconnect():void
    {
        if (nc != null) {
            trace("disconnect()");
            _dispatchDisconnect = false;
            nc.close();
            nc.removeEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            nc.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
            nc.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, errorHandler);
            nc.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
            nc.client = {};
            nc = null;
            destroyStream();
        }
        detachVideo();
    }
    
    private function netStatusHandler(event:NetStatusEvent):void 
    {
        trace('netStatusHandler() ' + event.type + ' ' + event.info.code);
        
        switch (event.info.code) {
        case 'NetConnection.Connect.Success':
            setProperty("nearID", nc.nearID);
            if (CONFIG::sdk4) {
                if (group != null) 
                    createGroup();
                else if (autoplay && (publish != null || play != null))
                    createStream();
            } 
            else {
                if (autoplay && (publish != null || play != null))
                    createStream();
            }
            break;
        case 'NetConnection.Connect.Failed':
        case 'NetConnection.Connect.Rejected':
        case 'NetConnection.Connect.Closed':
            errorHandler(event);
            break;
            
        case 'NetStream.Play.Stop':
            if (_remote != null) {
                currentTime = _remote.time;
                if (!isNaN(duration) && currentTime < duration)
                    currentTime = duration;
            }
            playing = false;
            if (loop)
                playing = true;
            break;
            
        case 'NetGroup.Connect.Success':
            if (autoplay && (publish != null || play != null))
                createStream();
            break;
            
        case 'NetGroup.Posting.Notify':
            try {
                var event1:DynamicEvent = new DynamicEvent("postingNotify");
                event1.user = event.info.message.user;
                event1.text = event.info.message.text;
                trace("CallProxy dispatchEvent type=postingNotify");
                dispatchEvent(event1);
            }
            catch (e:Error) {
                trace("CallProxy.callProperty(" + name + ") exception\n" + e.getStackTrace());
            }
            break;
        }
    }
    
    private function errorHandler(event:Event):void 
    {
        if (_dispatchDisconnect) {
            if (event is ErrorEvent) {
                trace('errorHandler() ' + ErrorEvent(event).type + ' ' + ErrorEvent(event).text);
            }
            else if (event is NetStatusEvent) {
                if ('description' in NetStatusEvent(event).info)
                    trace("reason: " + NetStatusEvent(event).info.description);
            }
            
            if (nc != null)
                nc.close();
            nc = null;
            playing = recording = false;
            
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "src", src, null));
            src = null;
        }
    }
    
    private function cameraChangeHandler(event:Event):void
    {
        if (_video != null)
            _video.attachCamera(_cameraObject);
        if (_local != null)
            _local.attachCamera(_cameraObject);
    }
    private function micChangeHandler(event:Event):void
    {
        if (_local != null)
            _local.attachAudio(_microphoneObject);
    }
    
    private function onMetaData(obj:Object):void
    {
        if (obj.duration != undefined)
            duration = obj.duration;
        if (obj.width != undefined)
            setProperty("videoWidth", obj.width);
        if (obj.height != undefined)
            setProperty("videoHeight", obj.height);
    }
    
    private function onSendData(data:String):void
    {
        dispatchEvent(new DataEvent("receiveData", false, false, data));
    }
    
    CONFIG::sdk4
    private function createGroup():void
    {
        try {
            _groupspec = new flash.net.GroupSpecifier(group);
            _groupspec.serverChannelEnabled = true;
            _groupspec.postingEnabled = true;
            _groupspec.multicastEnabled = true;
//            _groupspec.ipMulticastMemberUpdatesEnabled = true;
//            _groupspec.addIPMulticastAddress("224.1.2.3", 8082);
            
            _netGroup = new flash.net.NetGroup(nc, _groupspec.groupspecWithAuthorizations());
            _netGroup.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler, false, 0, true);
        } catch (e:Error) {
            // ignore
            trace("error in creating GroupSpecifier or NetGroup, disabled group: " + e.message);
            Alert.show("You need Flash Player 10.1 or\nhigher for group communication", "Disabled group!");
            
            var oldValue:String = _group;
            _group = null;
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "group", oldValue, null));
            
            if (autoplay && (publish != null || play != null))
                createStream();
        }
    }
    
    private function createStream():void
    {
        var oldPlaying:Boolean = _playing, oldRecording:Boolean = _recording;
        var createLocal:Boolean = _local == null && publish != null;
        var createRemote:Boolean = (_bidirection || !createLocal) && _remote == null && play != null;
        
        if ((createLocal || createRemote) && nc != null && nc.connected) {
            if (scheme == 'rtmfp') {
                if (group == null) {
                    if (createLocal) {
                        trace("creating rtmfp publish stream: nearID=" + nc.nearID);
                        _local = new NetStream(nc, NetStream.DIRECT_CONNECTIONS);
                    }
                    if (createRemote) {
                        trace("creating rtmfp play stream: farID=" + farID);
                        _remote = new NetStream(nc, farID);
                    }
                } else {
                    if (createLocal) {
                        trace("creating rtmfp publish stream: groupspec=" + _groupspec.groupspecWithoutAuthorizations());
                        _local = new NetStream(nc, _groupspec.groupspecWithAuthorizations());
                    }
                    if (createRemote) {
                        trace("creating rtmfp play stream: groupspec=" + _groupspec.groupspecWithoutAuthorizations());
                        _remote = new NetStream(nc, _groupspec.groupspecWithAuthorizations());
                    }
                }
            }
            else {
                if (createLocal)
                    _local = new NetStream(nc);
                if (createRemote)
                    _remote = new NetStream(nc);
            }
            
            if (createLocal) {
                if (CONFIG::player11) {
                    _local.videoStreamSettings = createVideoStreamSettings();
                }
                _local.client = { onMetaData: onMetaData, setVideoSize: setVideoSize, sendData: onSendData };
                _local.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler, false, 0, true);
            }
            
            if (createRemote) {
                _remote.client = { onMetaData: onMetaData, setVideoSize: setVideoSize, sendData: onSendData };
                _remote.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler, false, 0, true);
                
                if (this.bufferTime >= 0) {
                    _remote.bufferTime = this.bufferTime;
                }
                if (this.bufferTimeMax >= 0 && _remote.hasOwnProperty("bufferTimeMax")) {
                    _remote["bufferTimeMax"] = this.bufferTimeMax;
                }
            }
            
            
            if (createLocal) {
                trace("createStream() publish=" + publish);
                if (_cameraObject != null)
                    _local.attachCamera(_cameraObject);
                if (_microphoneObject != null)
                    _local.attachAudio(_microphoneObject);
                _recording = true;
            }
            if (createRemote) {
                trace("createStream() play=" + play);
                if (_video != null)
                    _video.attachNetStream(_remote);
                _playing = true;
            }
            
            if (createLocal)
                _local.publish(publish, (record && scheme == 'rtmp' ? recordMode : null));
            if (createRemote)
                _remote.play(play);
                
            startPlayheadTimer();
        }
        
        if (oldRecording != _recording)
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "recording", oldRecording, _recording));
        if (oldPlaying != _playing)
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "playing", oldPlaying, _playing));
    }
    
    private function destroyNetStream(ns:NetStream):void
    {
        trace("destroyStream()");
        try {
            ns.client = {};
            ns.close();
        } catch (e:Error) {
            // ignore
        }
        try {
//            ns.attachAudio(null);
//            ns.attachCamera(null);
        } catch (e:Error) {
            // ignore
        }
    }
    
    private function destroyStream():void
    {
        var oldPlaying:Boolean = _playing, oldRecording:Boolean = _recording;
        
        stopPlayheadTimer();
        
        if (_local != null) {
            destroyNetStream(_local);
            _local = null;
            _recording = false;
        }
        
        if (_remote != null) {
            destroyNetStream(_remote);
            if (oldPlaying && _video != null)
                _video.attachNetStream(null);
            _remote = null;
            _playing = false;
        }
        
        if (oldRecording != _recording)
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "recording", oldRecording, _recording));
        if (oldPlaying != _playing)
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "playing", oldPlaying, _playing));
    }
    
    private function startPlayheadTimer():void
    {
        if (_currentTimer == null) {
            _currentTimer = new Timer(1000, 0);
            _currentTimer.addEventListener(TimerEvent.TIMER, currentTimerHandler, false, 0, true);
            _currentTimer.start();
        }
    }
    
    private function stopPlayheadTimer():void
    {
        if (_currentTimer != null) {
            _currentTimer.stop();
            _currentTimer = null;
        }
    }
    
    private function currentTimerHandler(event:Event):void
    {
        if (_local != null || _remote != null) {
            var ns:NetStream = _local != null ? _local : _remote;
            currentTime = ns.time;
            setProperty("currentFPS", ns.currentFPS);
            setProperty("liveDelay", ns.liveDelay);
            quality = delayToQuality(ns.liveDelay);
            if (ns.info != null)
                setProperty("bandwidth", new Number(int(ns.info.videoBytesPerSecond + ns.info.audioBytesPerSecond)));
        }
                
        if (currentTime > (_lastSendTime + 5)) {
            sendVideoSize();
        }
    }
    
    private function delayToQuality(delay:Number):Number
    {
        var result:Number;
        if (isNaN(delay) || delay < 0)
            result = NaN;
        else if (delay < 0.05)
            result = 1.0;
        else if (delay < 0.15)
            result = 0.8;
        else if (delay < 0.5)
            result = 0.6;
        else if (delay < 1.0)
            result = 0.4;
        else if (delay < 3.0)
            result = 0.2;
        else 
            result = 0.5;
        return result;
    }
    
    private function attachVideo():void
    {
        trace("attachVideo() video=" + _video);
        unload();
        if (_video == null) {
            _video = new Video();
            _video.smoothing = this.smoothing;
            
            var parent:UIComponent = new UIComponent();
            parent.addChild(_video);
            parent.percentWidth = parent.percentHeight = 100;
            resizeVideoHandler(null);
            parent.addEventListener(ResizeEvent.RESIZE, resizeVideoHandler, false, 0, true);
            this.addChildAt(parent, 0);
        }
        
        if (_remote != null)
            _video.attachNetStream(_remote);
        else if (_cameraObject != null)
            _video.attachCamera(_cameraObject);
    }
    
    private function detachVideo():void
    {
        trace("detachVideo() video=" + _video);
        if (_video != null) {
            this.removeChild(_video.parent);
            _video.clear();
            _video.attachCamera(null);
            _video = null;
        }
    }
    
    private function sendVideoSize():void
    {
        if (recording && _local != null && (this.width != 320 || this.height != 240)) {
            _lastSendTime = currentTime;
            trace("sending setVideoSize(" + this.width + ", " + this.height + ")");
            if (_zoom == "in")
                _local.send("setVideoSize", this.width, this.height);
            else
                _local.send("setVideoSize", _cameraWidth, _cameraHeight);
        }
    }
    
    private function setVideoSize(width:Number, height:Number):void
    {
        trace("received setVideoSize(" + width + "," + height + ")");
        
        if (width != publishWidth || height != publishHeight) {
            publishWidth = width;
            publishHeight = height;
            setProperty("videoWidth", width);
            setProperty("videoHeight", height);
            
            if (!live)
                resizeVideoHandler(null);
        }
        
    }
    
    private function localViewMatrix(zoom:String, mirrored:Boolean):Matrix
    {
        var ratio:Number = _cameraWidth/_cameraHeight;
        var m:Matrix = new Matrix();
        var parent:DisplayObject = _video.parent;
        trace("setting video transform parent=" + parent.width + "x" + parent.height + " camera=" + _cameraWidth + "x" + _cameraHeight + " mirrored=" + mirrored + " zoom=" + zoom);
        if (mirrored) {
            if (zoom == null) {
                m.scale(-parent.width/320, parent.height/240);
                m.translate(parent.width, 0);
            }
            else if (zoom == "out") {
                m.scale(-Math.min(parent.width, ratio*parent.height)/320, Math.min(parent.height, (1/ratio)*parent.width)/240);
                m.translate(Math.min(parent.width, ratio*parent.height)-(Math.min(parent.width, ratio*parent.height)-parent.width)/2, -(Math.min(parent.height, (1/ratio)*parent.width)-parent.height)/2);
            }
            else if (zoom == "in") {
                m.scale(-Math.max(parent.width, ratio*parent.height)/320, Math.max(parent.height, (1/ratio)*parent.width)/240);
                m.translate(Math.max(parent.width, ratio*parent.height)-(Math.max(parent.width, ratio*parent.height)-parent.width)/2, -(Math.max(parent.height, (1/ratio)*parent.width)-parent.height)/2);
            }
        } 
        else {
            if (zoom == null) {
                m.scale(parent.width/320, parent.height/240);
                m.translate(0, 0);
            }
            else if (zoom == "out") {
                m.scale(Math.min(parent.width, ratio*parent.height)/320, Math.min(parent.height, (1/ratio)*parent.width)/240);
                m.translate(-(Math.min(parent.width, ratio*parent.height)-parent.width)/2, -(Math.min(parent.height, (1/ratio)*parent.width)-parent.height)/2);
            }
            else if (zoom == "in") {
                m.scale(Math.max(parent.width, ratio*parent.height)/320, Math.max(parent.height, (1/ratio)*parent.width)/240);
                m.translate(-(Math.max(parent.width, ratio*parent.height)-parent.width)/2, -(Math.max(parent.height, (1/ratio)*parent.width)-parent.height)/2);
            }
        }
        return m;
    }
    
    private function resizeVideoHandler(event:ResizeEvent):void
    {
        var ratio:Number = _cameraWidth/_cameraHeight;

        if (_video != null) {
            if (_live) {
                _video.transform.matrix = localViewMatrix(_zoom, _mirrored);
            }
            else {
                if (_zoom == null || isNaN(publishWidth) || isNaN(publishHeight)) {
                    _video.width = _video.parent.width;
                    _video.height = _video.parent.height;
                }
                else {
                    var ratio1:Number = this.width/this.height;
                    var originalWidth:Number = _zoom == "in" ? Math.max(publishWidth, publishHeight*ratio) : Math.min(publishWidth, publishHeight*ratio);
                    var originalHeight:Number = _zoom == "in" ? Math.max(publishWidth/ratio, publishHeight) : Math.min(publishWidth/ratio, publishHeight);
                    var extractedWidth:Number = _zoom == "in" ? Math.min(publishWidth, publishHeight*ratio1) : Math.max(publishWidth, publishHeight*ratio1);
                    var extractedHeight:Number = _zoom == "in" ? Math.min(publishWidth/ratio1, publishHeight) : Math.max(publishWidth/ratio1, publishHeight);
                    var scaled:Number = this.width / extractedWidth;
                    var scaledWidth:Number = originalWidth*scaled;
                    var scaledHeight:Number = originalHeight*scaled;
                    _video.width = scaledWidth;
                    _video.height = scaledHeight;
                    _video.x = this.width/2 - scaledWidth/2;
                    _video.y = this.height/2 - scaledHeight/2;
                }                    
            }
        }
    }

    public function takeSnapshot(quality:Number=100):void
    {
        if (_video != null || _videoDisplay != null) {
            var snap:BitmapData = new BitmapData(this.width, this.height, true);
            var matrix:Matrix = new Matrix(1, 0, 0, 1, 0, 0);
            if (_live && (this.width != 320 || this.height != 240)) {
                // since the live display scales from 320x240, need to use the similar matrix
                matrix = localViewMatrix(_zoom, false); // do not mirror, but use the same zoom.
            }
            snap.draw(_video != null ? _video : _videoDisplay, matrix);
            var bm:Bitmap = new Bitmap(snap);
            var encoder:JPEGEncoder = new JPEGEncoder(quality);
            var jpeg:ByteArray = encoder.encode(snap);
            var b64:Base64Encoder = new Base64Encoder();
            b64.insertNewLines = false;
            b64.encodeBytes(jpeg);
            _snapshot = b64.toString();
        }
        else {
            _snapshot = null;
        }
    }
    
    private function snapshotToData():ByteArray
    {
        var b64:Base64Decoder = new Base64Decoder();
        b64.decode(_snapshot);
        return b64.drain();
    }
    
    private function attachPoster():void
    {
        if ((_poster != null || _snapshot != null) && _image == null) {
            _image = new Image();
            _image.maintainAspectRatio = true;
            _image.source = (_snapshot != null ? snapshotToData() : _poster);
            _image.addEventListener(Event.COMPLETE, posterCompleteHandler, false, 0, true);

            var parent:UIComponent = new Canvas();
            if (!isNaN(posterBackgroundAlpha)) {
                attachPosterBackground();
            }
            
            parent.addChild(_image);
            parent.percentWidth = parent.percentHeight = 100;
            resizePosterHandler(null);
            parent.addEventListener(ResizeEvent.RESIZE, resizePosterHandler, false, 0, true);
            this.addChildAt(parent, 0);
        }
    }
    
    private function detachPoster():void
    {
        if (_image != null) {
            this.removeChild(_image.parent);
            _image = null;
        }
    }
    
    private function attachPosterBackground():void
    {
        if (_image != null && _poster != null) {
            var bg:Image = new Image();
            bg.percentWidth = bg.percentHeight = 100;
            bg.maintainAspectRatio = false;
            bg.alpha = posterBackgroundAlpha;
            bg.source = _poster;
            _image.parent.addChildAt(bg, 0);
        }
    }
    
    private function detachPosterBackground():void
    {
        if (_image != null) {
            if (_image.parent.numChildren >= 2) {
                _image.parent.removeChildAt(0);
            }
        }
    }
    
    private function posterCompleteHandler(event:Event):void
    {
        resizePosterHandler(null);
    }
    
    private function resizePosterHandler(event:ResizeEvent):void
    {
        try {
            if (_image != null && _image.content != null && 
                !isNaN(_image.content.width) && !isNaN(_image.content.height) && _image.content.height > 0) {
                var ratio:Number = _image.content.width / _image.content.height;
                var width:Number = Math.min(_image.parent.width, _image.parent.height*ratio);
                var height:Number = Math.min(_image.parent.width/ratio, _image.parent.height);
                _image.width = Math.ceil(width);
                _image.height = Math.ceil(height);
                _image.x = int(_image.parent.width/2 - _image.width/2);
                _image.y = int(_image.parent.height/2 - _image.height/2);
                trace("poster size=" + _image.width + "x" + _image.height + " position=" + _image.x + "," + _image.y); 
            }
        } catch (e:SecurityError) {
            _image.percentWidth = _image.percentHeight = 100;
            if (_image.parent != null)
                _image.parent.removeEventListener(ResizeEvent.RESIZE, resizePosterHandler);
        }
    }
    
    private function load():void
    {
        trace("load() _videoDisplay=" + _videoDisplay);
        detachVideo();
        if (_videoDisplay == null) {
            _videoDisplay = new VideoDisplay();
            setProperty("playerState", VideoEvent.DISCONNECTED);
            _videoDisplay.percentWidth = _videoDisplay.percentHeight = 100;
            _videoDisplay.maintainAspectRatio = true;
            _videoDisplay.autoRewind = loop;
            _videoDisplay.autoPlay = autoplay;
            _videoDisplay.source = url;
            _videoDisplay.addEventListener(VideoEvent.CLOSE, videoCloseHandler, false, 0, true);
            _videoDisplay.addEventListener(VideoEvent.COMPLETE, videoCompleteHandler, false, 0, true);
            _videoDisplay.addEventListener(VideoEvent.READY, videoReadyHandler, false, 0, true);
            _videoDisplay.addEventListener(VideoEvent.PLAYHEAD_UPDATE, videoPlayheadUpdateHandler, false, 0, true);
            _videoDisplay.addEventListener(VideoEvent.STATE_CHANGE, videoStateChangeHandler, false, 0, true);
            _videoDisplay.addEventListener(MetadataEvent.METADATA_RECEIVED, metadataHandler, false, 0, true);
            _videoDisplay.addEventListener(ProgressEvent.PROGRESS, videoProgressHandler, false, 0, true);
            
            trace("load() videoDisplay with url=" + url + " autoplay=" + autoplay);
            this.addChildAt(_videoDisplay, 0);
            
            if (autoplay)
                playing = true;
            else
                attachPoster();
        }
    }
    
    private function unload(poster:Boolean=false):void
    {
        trace("unload() videoDisplay=" + _videoDisplay + " poster=" + poster);
        if (_videoDisplay != null) {
            _videoDisplay.close();
            this.removeChild(_videoDisplay);
            _videoDisplay = null;
            setProperty("playerState", null);
            if (poster)
                attachPoster();
        }
    }

    private function videoCompleteHandler(event:Event):void
    {
        trace("videoCompleteHandler " + event.type);
        playing = false;
        if (loop)
            playing = true;
    }

    private function videoReadyHandler(event:VideoEvent):void
    {
        trace("videoReadyHandler " + event.type);
    }

    private function videoCloseHandler(event:Event):void
    {
        trace("videoCloseHandler " + event.type);
    }

    private function videoStateChangeHandler(event:VideoEvent):void
    {
        trace("videoStateChangeHandler " + event.type + ", " + event.state + ", " + event.stateResponsive);
        setProperty("playerState", event.state);
    }

    private function videoProgressHandler(event:ProgressEvent):void
    {
        trace("videoProgressHandler " + event.type + ", " + event.bytesLoaded + "/" + event.bytesTotal);
        
        if (event.bytesLoaded != bytesTotal)
            setProperty("bytesTotal", new Number(event.bytesTotal));
        if (event.bytesLoaded != bytesLoaded)
            setProperty("bytesLoaded", new Number(event.bytesLoaded));
    }

    private function videoPlayheadUpdateHandler(event:VideoEvent):void
    {
        trace("videoPlayheadUpdateHandler " + event.type + ", " + event.playheadTime + "/" + _videoDisplay.totalTime);
        
        if (_videoDisplay.totalTime != duration && _videoDisplay.totalTime > 0)
            duration = _videoDisplay.totalTime;
        if (event.playheadTime != currentTime)
            currentTime = event.playheadTime;
    }
    
    private function metadataHandler(event:MetadataEvent):void
    {
        if (event.info != null) {
            onMetaData(event.info);
        }
    }

    private function addHandler(event:Event):void
    {
        if (stage != null) 
            stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler, false, 0, true);
    }
    
    private function removeHandler(event:Event):void
    {
        if (stage != null)
            stage.removeEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler);
    }
    
    private function fullScreenHandler(event:FullScreenEvent):void
    {
        if (fullscreen != event.fullScreen) 
            fullscreen = event.fullScreen;
        if (!CONFIG::player11) { // it gives ambigous reference to hasOwnProperty
            if (!event.fullScreen && stage != null && this.hasOwnProperty("fullScreenSourceRect"))
                stage.fullScreenSourceRect = null;
        }
    }
    
    private function fullScreenMenuHandler(event:Event):void
    {
        fullscreen = !fullscreen;
    }

    private function doubleClickHandler(event:Event):void
    {
        if (_enableFullscreenOnDoubleClick) {
            trace("double-click detected");
            var oldValue:Boolean = fullscreen;
            fullscreen = !fullscreen;
            // for some reason the property change is not dispatched here. So explicitly do that.
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "fullscreen", oldValue, fullscreen));
        }
    }
    
    private function productMenuHandler(event:Event):void
    {
        try {
            navigateToURL(new URLRequest(VideoIOInternal.COMPONENT_URL), "_blank");
        } catch (e:Error) {
            trace("failed to navigate to " + VideoIOInternal.COMPONENT_URL);
        }
    }
    
    private function installContextMenu():void
    {
        var menu:ContextMenu;
        if (CONFIG::sdk4) {
            menu = mx.core.FlexGlobals.topLevelApplication.contextMenu;
        }
        else {
            menu = Application.application.contextMenu;
        }
        menu.hideBuiltInItems();
        
        var product:ContextMenuItem = new ContextMenuItem(VideoIOInternal.COMPONENT_VERSION);
        menu.customItems.push(product);
        product.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, productMenuHandler);

        var fullscreen:ContextMenuItem = new ContextMenuItem("Toggle full-screen");
        menu.customItems.push(fullscreen);
        fullscreen.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, fullScreenMenuHandler);
        
        trace("context menu installed");
    }
};

import flash.events.IEventDispatcher;
import flash.utils.Proxy;
import flash.utils.flash_proxy;

dynamic class CallProxy extends Proxy 
{
    private var obj:IEventDispatcher;
    
    public function CallProxy(value:IEventDispatcher) 
    {
        this.obj = value;
    }
    
    override flash_proxy function hasProperty(name:*):Boolean
    {
        trace("hasProperty " + name);
        return true;
    }
    
    override flash_proxy function isAttribute(name:*):Boolean
    {
        trace("isAttribute " + name);
        return false;
    }
    
    // getProperty is invoked instead of callProperty
    override flash_proxy function getProperty(name:*):*
    {
        trace("getProperty " + name);
        var func:Function = function(...rest):void {
            try {
                trace("CallProxy.callback(" + name + ") invoked");
                var event:DynamicEvent = new DynamicEvent("callback");
                event.method = name.toString();
                event.args = rest;
                trace("CallProxy dispatchEvent type=callback method=" + name);
                obj.dispatchEvent(event);
            }
            catch (e:Error) {
                trace("CallProxy.callback(" + name + ") exception\n" + e.getStackTrace());
            }
        };
        return func as Function;
    }
    
    // when a property is called, just dispatch "callback" event on associated object.
    override flash_proxy function callProperty(name:*, ...rest):* 
    {
        try {
            trace("callProperty(" + name + ") invoked");
            var event:DynamicEvent = new DynamicEvent("callback");
            event.method = name;
            event.args = rest;
            trace("CallProxy dispatchEvent type=callback method=" + name);
            obj.dispatchEvent(event);
        }
        catch (e:Error) {
            trace("CallProxy.callProperty(" + name + ") exception\n" + e.getStackTrace());
        }
    }       
};

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Matrix;
import flash.media.Video;
import flash.net.FileReference;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.ByteArray;
import flash.utils.Timer;

import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;
import mx.containers.Canvas;
import mx.containers.HBox;
import mx.controls.Alert;
import mx.controls.Button;
import mx.controls.HSlider;
import mx.controls.LinkButton;
import mx.controls.Spacer;
import mx.controls.Text;
import mx.core.Container;
import mx.core.UIComponent;
import mx.effects.Move;
import mx.events.DynamicEvent;
import mx.events.FlexEvent;
import mx.events.PropertyChangeEvent;
import mx.events.PropertyChangeEventKind;
import mx.events.ResizeEvent;
import mx.graphics.codec.JPEGEncoder;
import mx.resources.ResourceManager;
import mx.skins.ProgrammaticSkin;


class VideoControl extends HBox
{
    private static const HIDE_DELAY:uint = 3000;
    
    private var _parent:VideoIOInternal;
    private var _help:LinkButton;
    
    private var _playButton:Button;
    private var _recordButton:Button;
    private var _camButton:Button;
    private var _micButton:Button;
    private var _micSlider:VolumeSlider;
    private var _level:VolumeLevel;
    private var _speakerButton:Button;
    private var _speakerSlider:VolumeSlider;
    private var _qualityButton:Button;
    private var _fullscreenButton:Button;
    private var _snapButton:Button;
    private var _spacer:Button;
    private var _position:PlayPositionSlider;
    
    private var _hideTimer:Timer;
    
    private var _watchers:Array = [];
    private var _hover:Boolean = false;
    
    public function VideoControl() 
    {
        percentWidth = 100;
        height = 20;
        
        setStyle("paddingLeft", 0);
        setStyle("paddingRight", 0);
        setStyle("paddingTop", 0);
        setStyle("paddingBottom", 0);
        setStyle("horizontalGap", 0);
        setStyle("backgroundColor", 0xb7b8b9);
        setStyle("borderStyle", "solid");
        setStyle("borderThickness", 0);

        addEventListener(Event.ADDED_TO_STAGE, addedHandler);
    }
    
    private function _(format:String, ...args):String 
    {
        var result:String = ResourceManager.getInstance().getString("main", format.split(" ").join("_"), args);
        if (result == null) {
            result = format;
            for (var i:int=0; i<args.length; ++i) // use regex to replace all occurances
                result = result.replace("{" + i.toString() + "}", args[i] != null ? args[i].toString() : 'null');
        }
        return result;
    }
    
    private function createButton(skin:Class=null, toggleOn:String=null, 
        visibleOn:*=null, visibleWhen:Function=null):Button
    {
        trace("addButton(" + skin + "," + toggleOn + "," + visibleOn + ")");
        
        var button:Button = new Button();
        button.width = button.height = 20;
        button.buttonMode = true;
        button.setStyle("borderThickness", 0);
        button.setStyle("fillColors", [0xcacaca, 0xb8b8b8]);
        button.setStyle("fillOverColors", skin != null ? [0xa0a0a0, 0xa0a0a0] : [0xcacaca, 0xb8b8b8]);
        button.setStyle("skin", skin != null ? skin : ShinyButtonSkin);

        button.toggle = (toggleOn != null);
        button.includeInLayout = button.visible = (visibleOn == null);
        
        addChild(button);
        
        if (visibleOn != null) {
            if (visibleOn is String)
                _watchers.push(BindingUtils.bindProperty(button, "includeInLayout", _parent, String(visibleOn)));
            else
                for each (var property:String in visibleOn)
                    _watchers.push(BindingUtils.bindProperty(button, "includeInLayout", _parent,
                        { name: property, getter: visibleWhen}));
            _watchers.push(BindingUtils.bindProperty(button, "visible", button, "includeInLayout"));
        }
        
        if (toggleOn != null) {
            _watchers.push(BindingUtils.bindProperty(button, "selected", _parent, toggleOn));
            _watchers.push(BindingUtils.bindProperty(_parent, toggleOn, button, "selected"));
        }
        
        return button;
    }
    
    private function createSpacer(button:UIComponent, position:String):Spacer
    {
        var spacer:Spacer = new Spacer();
        spacer.width = 1;
        _watchers.push(BindingUtils.bindProperty(spacer, "includeInLayout", button, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(spacer, "visible", button, "visible"));
        if (position == "after")
            addChild(spacer);
        else if (position == "before")
            addChildAt(spacer, getChildIndex(button));
        return spacer;
    }
    
    private function addedHandler(event:Event):void
    {
        removeEventListener(Event.ADDED_TO_STAGE, addedHandler);
        addEventListener(Event.REMOVED_FROM_STAGE, removedHandler);
        
        trace('addedHandler');
        _parent = VideoIOInternal(this.parent);
        _parent.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler, false, 0, true);
        _parent.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false, 0, true);
        _parent.addEventListener(MouseEvent.MOUSE_DOWN, mouseMoveHandler, false, 0, true);
        _parent.addEventListener(ResizeEvent.RESIZE, resizeHandler, false, 0, true);
        addEventListener(MouseEvent.ROLL_OVER, rollOverHandler, false, 0, true);
        addEventListener(MouseEvent.ROLL_OUT, rollOutHandler, false, 0, true);

        var isPlaying:Function = function(host:Object):Boolean { return host['play'] != null || host['url'] != null && host['url'].substr(0, 4) == 'http'; };
        _playButton = createButton(PlayButtonSkin, "playing", ["play", "url"], isPlaying);
        _playButton.name = _("Play or pause");
        createSpacer(_playButton, "after");
        
        var isRecording:Function = function(host:Object):Boolean { return host['publish'] != null; }; 
        _recordButton = createButton(RecordButtonSkin, "recording", "publish", isRecording);
        _recordButton.name = _("Start/stop recording");
        createSpacer(_recordButton, "after");
            
        _camButton = createButton(CamButtonSkin, "camera", "live");
        _camButton.name = _("On/off camera");
        createSpacer(_camButton, "after");
        
        _micButton = createButton(MicButtonSkin, "microphone", "publish", isRecording);
        _micButton.name = _("On/off microphone");
        
        _micSlider = new VolumeSlider();
        _micSlider.name = _("Adjust microphone volume");
        _micSlider.width = 40;
        _micSlider.height = 20;
        _micSlider.includeInLayout = false;
        _watchers.push(BindingUtils.bindProperty(_micSlider, "visible", _micSlider, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_micSlider, "level", _parent, "gain"));
        _watchers.push(BindingUtils.bindProperty(_micSlider, "enabled", _parent, "microphone"));
        _micSlider.addEventListener(Event.CHANGE, sliderChangeHandler, false, 0, true);
        addChild(_micSlider);
        createSpacer(_micSlider, "after");
        
        _level = new VolumeLevel();
        _level.width = 40;
        _level.height = 20;
        _level.includeInLayout = false;
        _watchers.push(BindingUtils.bindProperty(_level, "includeInLayout", _micButton, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_level, "visible", _micButton, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_level, "level", _parent, "level"));
        _watchers.push(BindingUtils.bindProperty(_level, "enabled", _parent, "microphone"));
        addChild(_level);
        createSpacer(_level, "after");
        
        _micSlider.addEventListener(MouseEvent.ROLL_OUT, function(event:Event):void {
            if (_recordButton.includeInLayout) {
                _micSlider.includeInLayout = false;
                _level.includeInLayout = true;
            }
        });
        _level.addEventListener(MouseEvent.ROLL_OVER, function(event:Event):void {
            if (_recordButton.includeInLayout) {
                _level.includeInLayout = false;
                _micSlider.includeInLayout = true;
            }
        });
        
        _speakerButton = createButton(SpeakerButtonSkin, "sound", ["url", "play"], isPlaying);
        _speakerButton.name = _("On/off speaker sound");
        
        _speakerSlider = new VolumeSlider();
        _speakerSlider.name = _("Adjust speaker volume");
        _speakerSlider.width = 40;
        _speakerSlider.height = 20;
        _speakerSlider.includeInLayout = false;
        _watchers.push(BindingUtils.bindProperty(_speakerSlider, "includeInLayout", _speakerButton, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_speakerSlider, "visible", _speakerSlider, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_speakerSlider, "level", _parent, "volume"));
        _watchers.push(BindingUtils.bindProperty(_speakerSlider, "enabled", _parent, "sound"));
        _speakerSlider.addEventListener(Event.CHANGE, sliderChangeHandler, false, 0, true);
        //_watchers.push(BindingUtils.bindProperty(_parent, "volume", _speakerSlider, "level"));
        addChild(_speakerSlider);
        
        createSpacer(_speakerSlider, "after");

        var isStreaming:Function = function(host:Object):Boolean { 
            return host['play'] != null || host['publish'] != null || host['url'] != null && host['url'].substr(0, 4) == 'http'; 
        };
        
        var isNotStreaming:Function = function(host:Object):Boolean { 
            return !isStreaming(host); 
        };
        
        _spacer = createButton();
        _spacer.percentWidth = 100;
        _spacer.setStyle("fillDownColors", _spacer.getStyle("fillColors"));
        _spacer.buttonMode = false;
        _watchers.push(BindingUtils.bindProperty(_spacer, "includeInLayout", _parent, {name: "play", getter: isNotStreaming}));
        _watchers.push(BindingUtils.bindProperty(_spacer, "includeInLayout", _parent, {name: "publish", getter: isNotStreaming}));
        _watchers.push(BindingUtils.bindProperty(_spacer, "includeInLayout", _parent, {name: "url", getter: isNotStreaming}));
        _watchers.push(BindingUtils.bindProperty(_spacer, "visible", _spacer, "includeInLayout"));
        
        _position = new PlayPositionSlider();
        _position.percentWidth = 100;
        _position.height = 20;
        _position.buttonMode = false;
        _watchers.push(BindingUtils.bindProperty(_position, "includeInLayout", _parent, {name: "play", getter: isStreaming}));
        _watchers.push(BindingUtils.bindProperty(_position, "includeInLayout", _parent, {name: "publish", getter: isStreaming}));
        _watchers.push(BindingUtils.bindProperty(_position, "includeInLayout", _parent, {name: "url", getter: isStreaming}));
        _watchers.push(BindingUtils.bindProperty(_position, "visible", _position, "includeInLayout"));
        _watchers.push(BindingUtils.bindProperty(_position, "total", _parent, "duration"));
        _watchers.push(BindingUtils.bindProperty(_position, "position", _parent, "currentTime"));
        _position.addEventListener(Event.CHANGE, positionChangeHandler, false, 0, true);
        addChild(_position);
        
        var isAllowed:Function = function(host:Object):Boolean { return host['camera'] && !host['fullscreen']; };
        _snapButton = createButton(SnapButtonSkin, null, ["camera", "fullscreen"], isAllowed);
        _snapButton.name = _("Take camera snapshot");
        createSpacer(_snapButton, "before");
        _snapButton.addEventListener(MouseEvent.CLICK, snapshotHandler, false, 0, true);
        
        _qualityButton = createButton(QualityButtonSkin, null, ["playing", "url"], function(host:Object):Boolean { 
                return host['playing'] && host['url'] != null && host['url'].substr(0, 4) != 'http'; 
            });
        _qualityButton.name = _("Quality of play stream");
        createSpacer(_qualityButton, "before");
        _watchers.push(BindingUtils.bindSetter(function(value:Number):void {
                trace("setting level to " + value);
                _qualityButton.setStyle("level", value);
                _qualityButton.validateNow();
            }, _parent, "quality"));
        
        _fullscreenButton = createButton(FullScreenButtonSkin, "fullscreen", "enableFullscreen");
        _fullscreenButton.name = _("Toggle fullscreen mode");
        createSpacer(_fullscreenButton, "before");

        show();
    }
    
    private function removedHandler(event:Event):void
    {
        trace('removedHandler');
        removeEventListener(Event.REMOVED_FROM_STAGE, removedHandler);
        addEventListener(Event.ADDED_TO_STAGE, addedHandler);
        
        _parent.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, propertyChangeHandler);
        _parent.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        _parent.removeEventListener(MouseEvent.MOUSE_DOWN, mouseMoveHandler);
        _parent.removeEventListener(ResizeEvent.RESIZE, resizeHandler);
        _parent = null;
        
        removeEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
        removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
        
        for each (var w:ChangeWatcher in _watchers)
            w.unwatch();
        _watchers.splice(0, _watchers.length);
        
        removeAllChildren();
    }
    
    private var _file:FileReference = null;
    
    private function snapshotHandler(event:Event):void
    {
        var video:Video = _parent.video;
        if (video != null && _file == null && !_parent.fullscreen) {
            var snap:BitmapData = new BitmapData(_parent.width, _parent.height, true);
            var matrix:Matrix = new Matrix(1, 0, 0, 1, 0, 0);
            snap.draw(video.parent, matrix);
            var x0:Number = _parent.width, y0:Number = 0, scaleX:Number = -1;
            var bm:Bitmap = new Bitmap(snap);
            var encoder:JPEGEncoder = new JPEGEncoder(100);
            var jpeg:ByteArray = encoder.encode(snap);
            
            _file = new FileReference();
            _file.save(jpeg, "snap.jpg");
            _file.addEventListener(Event.COMPLETE, saveCompleteHandler, false, 0, true);
            _file.addEventListener(Event.CANCEL, saveCompleteHandler, false, 0, true);
        }
    }
    
    private function saveCompleteHandler(event:Event):void
    {
        _file = null;
    }
    
    private function sliderChangeHandler(event:Event):void
    {
        var obj:VolumeSlider = event.currentTarget as VolumeSlider;
        if (obj != null) {
            if (obj == _micSlider)
                _parent.gain = _micSlider.level;
            else
                _parent.volume = _speakerSlider.level;
        }
    }
    
    private function positionChangeHandler(event:Event):void
    {
        var obj:PlayPositionSlider = event.currentTarget as PlayPositionSlider;
        if (obj != null)
            _parent.setCurrentTime(obj.position);
    }
    
    private function propertyChangeHandler(event:PropertyChangeEvent):void
    {
        if (event.property != "level")
            trace("propertyChange " + event.property + " " + event.oldValue + "=>" + event.newValue);
    }
    
    private function mouseMoveHandler(event:Event):void
    {
        show();
    }
    
    private function resizeHandler(event:Event):void
    {
        y = _parent.height - this.height;
    }
    
    private function rollOverHandler(event:Event):void
    {
        _hover = true;
        stopHideTimer();
    }
    
    private function rollOutHandler(event:Event):void
    {
        startHideTimer();
        _hover = false;
    }
    
    private var _last_showing:Number = 0;
    private var _last_to_y:Number = 0;
    
    private function show():void
    {
        if (y != (_parent.height - this.height)) {
            if (_last_showing < ((new Date()).getTime() - 200) || _last_to_y != (_parent.height - this.height)) {
                _last_showing = (new Date()).getTime();
                _last_to_y = _parent.height - this.height;
                var effect:Move = new Move();
                effect.duration = 200;
                setStyle("moveEffect", effect);
                y = _parent.height - this.height;
            }
        }
        
        if (!_hover)
            startHideTimer();
    }
    
    private function hide():void
    {
        if (y != _parent.height) {
            var effect:Move = new Move();
            effect.duration = 500;
            setStyle("moveEffect", effect);
            
            y = _parent.height;
        }
    }

    public function startHideTimer():void
    {
        stopHideTimer();
        if (_parent.detectActivity) {
            _hideTimer = new Timer(HIDE_DELAY, 1);
            _hideTimer.addEventListener(TimerEvent.TIMER, hideTimerHandler, false, 0, true);
            _hideTimer.start();
        }
    }
    
    public function stopHideTimer():void
    {
        if (_hideTimer != null) {
            _hideTimer.stop();
            _hideTimer = null;
        }
    }
    
    private function hideTimerHandler(event:Event):void
    {
        _hideTimer = null;
        if (_parent != null && y == (_parent.height - this.height))
            hide();
    }
    
    private function setToolTip(str:String, child:DisplayObject=null):void
    {
        if (_help == null) {
            _help = new LinkButton();
            _help.setStyle("color", 0xffffff);
            _help.setStyle("fontWeight", "normal");
            _help.setStyle("bottom", 20);
            if (parent != null)
                parent.addChild(_help);
        }
        if (str != null && str != '') {
            _help.visible = true;
            _help.label = str;
            if (child != null) {
                _help.x = child.x - _help.width/2;
                if (_help.x < 0) _help.x = 0;
                else if (_help.x + _help.width > this.width) _help.x = this.width - _help.width;
            }
        }
        else {
            _help.visible = false; // hide it
        }
    }
};


/*
 * NOTE: Following classes are borrowed from the videocity project.
 * http://code.google.com/p/videocity
 */
 
class ShinyButtonSkin extends ProgrammaticSkin
{
    /**
     * update the display list based on the style.
     */
    protected override function updateDisplayList(w:Number, h:Number):void
    {
        var borderThickness:uint = getDefaultStyle("borderThickness", 0) as uint;
        var borderColor:uint     = getDefaultStyle("borderColor", 0xb7babc) as uint;
        var fillColors:Array     = getDefaultStyle("fillColors", [0xcacaca, 0xb0b0b0]) as Array;
        var backgroundAlpha:Number = getDefaultStyle("backgroundAlpha", 1.0) as Number;
        var temp:uint;
        
        switch (name) {
            case "upSkin":
            case "selectedUpSkin":
                // no change
                break;
                
            case "overSkin":
            case "selectedOverSkin":
                fillColors = getDefaultStyle("fillOverColors", [fillColors[1], fillColors[1]]) as Array;
                break;
                
            case "downSkin":
            case "selectedDownSkin":
                fillColors = getDefaultStyle("fillDownColors", [fillColors[1], fillColors[0]]) as Array;
                break;
                
            case "disabledSkin":
            case "disabledDownSkin":
            case "selectedDisabledSkin":
                fillColors = getDefaultStyle("fillDownColors", [fillColors[0], fillColors[0]]) as Array;
                break;
        }
        
        drawShinySkin(graphics, fillColors, w, h, borderThickness, borderColor, backgroundAlpha);
    }    

    public static function drawShinySkin(graphics:Graphics, fillColors:Array, w:Number, h:Number, borderThickness:int, borderColor:uint, backgroundAlpha:Number):void
    {
        var lightColor:uint      = fillColors[0];
        var darkColor:uint       = fillColors[1];
        
        graphics.clear();
        
        if (borderThickness > 0) {
            graphics.beginFill(borderColor);
            graphics.drawRect(0, 0, w, h);
            graphics.endFill();
        }
        
        var half:int = Math.ceil((h-2)/2);
        graphics.beginFill(lightColor, backgroundAlpha);
        graphics.drawRect(borderThickness, borderThickness, w-2*borderThickness, half - borderThickness);
        graphics.endFill();
        
        graphics.beginFill(darkColor, backgroundAlpha);
        graphics.drawRect(borderThickness, half, w-2*borderThickness, h - half - 2*borderThickness);
        graphics.endFill();
    }
    
    protected function getDefaultStyle(prop:String, def:Object):Object
    {
        var result:Object = getStyle(prop);
        return (result != null ? result : def);
    }
};

class PlayButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        
        if (name != null && name.substr(0, 8) != "selected") {
            graphics.lineStyle(1, color);
            graphics.beginFill(color);
            graphics.moveTo(w/4, h/4);
            graphics.lineTo(w*3/4, h/2);
            graphics.lineTo(w/4, h*3/4);
            graphics.lineTo(w/4, h/4);
            graphics.endFill();
        }
        else {
            graphics.lineStyle(1, color);
            graphics.beginFill(color);
            graphics.moveTo(w/4, h/4);
            graphics.lineTo(w*4/10, h/4);
            graphics.lineTo(w*4/10, h*3/4);
            graphics.lineTo(w/4, h*3/4);
            graphics.lineTo(w/4, h/4);
            graphics.endFill();
            
            graphics.beginFill(color);
            graphics.moveTo(w*5/8, h/4);
            graphics.lineTo(w*3/4, h/4);
            graphics.lineTo(w*3/4, h*3/4);
            graphics.lineTo(w*6/10, h*3/4);
            graphics.lineTo(w*6/10, h/4);
            graphics.endFill();
        }
    }
};

class RecordButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = (name != null && name.substr(0, 8) == "selected") ?
            getDefaultStyle("selectedColor", 0xff0000) as uint:
            getDefaultStyle("color", 0x000000) as uint;
        graphics.lineStyle(1, color);
        graphics.beginFill(color);
        graphics.drawCircle(w/2, h/2, Math.min(w, h)/4);
        graphics.endFill();
    }
};

class CamButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        
        var g:Graphics = graphics;
        g.lineStyle(1, color);
        
        g.beginFill(color);
        g.moveTo(w/4, h/3);
        g.lineTo(w*2/3, h/3);
        g.lineTo(w*2/3, h/2);
        g.lineTo(w*5/6, h/3);
        g.lineTo(w*5/6, h*2/3);
        g.lineTo(w*2/3, h/2);
        g.lineTo(w*2/3, h*2/3);
        g.lineTo(w/4, h*2/3);
        g.lineTo(w/4, h/3);
        g.endFill();
        
        if (name != null && name.substr(0, 8) != "selected") {
            g.lineStyle(1, 0xff0000);
            var r:Number = Math.min(w, h)/3;
            g.drawCircle(w/2, h/2, r);
            g.moveTo(w/2+r/Math.SQRT2, r/Math.SQRT2);
            g.lineTo(r/Math.SQRT2, h/2+r/Math.SQRT2);
        }
    }
};

class MicButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        
        graphics.lineStyle(1, color);
        
        graphics.beginFill(color);
        graphics.drawEllipse(w*5/12, h/4, w/6, h*3/8);
        graphics.endFill();
        
        graphics.moveTo(w/3, h*3/8);
        graphics.lineTo(w/3, h*2/3);
        graphics.curveTo(w/2, h*5/6, w*2/3, h*2/3);
        graphics.lineTo(w*2/3, h*3/8);
        
        if (name != null && name.substr(0, 8) != "selected") {
            graphics.lineStyle(1, 0xff0000);
            var r:Number = Math.min(w, h)/3;
            graphics.drawCircle(w/2, h/2, r);
            graphics.moveTo(w/2+r/Math.SQRT2, r/Math.SQRT2);
            graphics.lineTo(r/Math.SQRT2, h/2+r/Math.SQRT2);
        }
    }
}

class SpeakerButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        
        graphics.lineStyle(1, color);
        
        graphics.beginFill(color);
        graphics.moveTo(w*3/20, h*8/20);
        graphics.lineTo(w*5/20, h*8/20);
        graphics.lineTo(w*10/20, h*4/20);
        graphics.lineTo(w*10/20, h*16/20);
        graphics.lineTo(w*5/20, h*12/20);
        graphics.lineTo(w*3/20, h*12/20);
        graphics.lineTo(w*3/20, h*8/20);
        graphics.endFill();
        
        if (name != null && name.substr(0, 8) != "selected") {
            graphics.lineStyle(1, 0xff0000);
            var r:Number = Math.min(w, h)/3;
            graphics.drawCircle(w/2, h/2, r);
            graphics.moveTo(w/2+r/Math.SQRT2, r/Math.SQRT2);
            graphics.lineTo(r/Math.SQRT2, h/2+r/Math.SQRT2);
        }
        else {
            var level:int = Math.round((getDefaultStyle("level", 0.5) as Number) * 3);
            if (level >= 1) {
                graphics.moveTo(w*6/10, h*4/10);
                graphics.curveTo(w*6/10+3, h/2, w*6/10, h*6/10);
            }
            if (level >= 2) {
                graphics.moveTo(w*7/10, h*3/10);
                graphics.curveTo(w*7/10+5, h/2, w*7/10, h*7/10);
            }
            if (level >= 3) {
                graphics.moveTo(w*8/10, h*2/10);
                graphics.curveTo(w*8/10+6, h/2, w*8/10, h*8/10);
            }
        }
    }
}
    
class FullScreenButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        graphics.lineStyle(1, color);
        
        graphics.moveTo(w*3/4-3, h/4+1);
        graphics.lineTo(w/4, h/4+1);
        graphics.lineTo(w/4, h*3/4);
        graphics.lineTo(w*3/4-1, h*3/4);
        graphics.lineTo(w*3/4-1, h/4+3);
        graphics.moveTo(w*3/4-5, h/4+5);
        graphics.lineTo(w*3/4+1, h/4-1);
        
        if (name != null && name.substr(0, 8) != "selected") {
            graphics.moveTo(w*3/4-3, h/4-1);
            graphics.lineTo(w*3/4+1, h/4-1);
            graphics.lineTo(w*3/4+1, h/4+3);
        }
        else {
            graphics.moveTo(w*3/4-5, h/4+2);
            graphics.lineTo(w*3/4-5, h/4+5);
            graphics.lineTo(w*3/4-2, h/4+5);
        }
    }
}

class SnapButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x000000) as uint;
        var fillColors:Array = getDefaultStyle("fillColors", [0xcacaca, 0xb8b8b8]) as Array;
        
        graphics.lineStyle(1, color);
        graphics.beginFill(color);
        graphics.moveTo(3, h/3);
        graphics.lineTo(w-4, h/3);
        graphics.lineTo(w-4, h*2/3);
        graphics.lineTo(3, h*2/3);
        graphics.lineTo(3, h/3);
        graphics.endFill();
        
        graphics.beginFill(fillColors[0]);
        graphics.drawCircle(w/2, h/2, Math.min(w, h)/4);
        graphics.drawCircle(w/2, h/2, Math.min(w, h)/4-2);
        graphics.endFill();
    }
}

class QualityButtonSkin extends ShinyButtonSkin
{
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        super.updateDisplayList(w, h);
        
        var color:uint = getDefaultStyle("color", 0x404040) as uint;
        var fillColor:uint = getDefaultStyle("levelColor", 0x808080) as uint;
        var level:Number = getDefaultStyle("level", 0.0) as Number;
        
        var bars:Array = [{x:2, y:13, w:4, h:3}, {x:6, y:10, w:4, h:6}, 
                          {x:10, y:7, w:4, h:9}, {x:14, y:4, w:4, h:12}];
        
        for (var i:Number = 0; i<bars.length; ++i) {
            var o:Object = bars[i];
            var active:Boolean = (level > i/4);
            graphics.lineStyle(1, color);
            if (active)
                graphics.beginFill(fillColor);
            graphics.drawRect(o.x, o.y, o.w, o.h);
            if (active)
                graphics.endFill();
        }    

    }
};

    
[Event(name="change", type="flash.events.Event")]

class VolumeSlider extends UIComponent
{
    private var _level:Number = 0.5;
    
    public function VolumeSlider()
    {
        super();
        buttonMode = true;
        setStyle("borderThickness", 0);
        setStyle("fillColors", [0xcacaca, 0xb8b8b8]);
        setStyle("fillOverColors", [0xcacaca, 0xb8b8b8]);
        setStyle("fillDownColors", [0xcacaca, 0xb8b8b8]);
        setStyle("disabledColor", 0xa0a0a0);
        setStyle("showTrackHighlight", true);
        setStyle("skin", ShinyButtonSkin);
        addEventListener(MouseEvent.CLICK, mouseClickHandler, false, 0, true);
    }
    
    public function get level():Number
    {
        return _level;
    }
    public function set level(value:Number):void
    {
        var oldValue:Number = _level;
        if (value >= 0 && value <= 1.0)
            _level = value;
        if (oldValue != value) 
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "level", oldValue, value));
    }
    
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        var g:Graphics = graphics;
        g.clear();
        
        var borderThickness:uint = getStyle("borderThickness") != null ? getStyle("borderThickness") : 0;
        var borderColor:uint     = getStyle("borderColor") != null ? getStyle("borderColor") : 0xb7babc;
        var fillColors:Array     = getStyle("fillColors") != null ? getStyle("fillColors") : [0xcacaca, 0x989898];
        var backgroundAlpha:Number = getStyle("backgroundAlpha") != null ? getStyle("backgroundAlpha") : 1.0;
        var color:uint = getStyle("color") != null ? getStyle("color") : 0x000000;
        if (!enabled) 
            color = getStyle("disabledColor") != null ? getStyle("disabledColor") : 0x202020;
            
        ShinyButtonSkin.drawShinySkin(g, fillColors, w, h, borderThickness, borderColor, backgroundAlpha);
        
        g.lineStyle(1, color);
        g.moveTo(2, h*3/4);
        g.lineTo(w-2, h/4);
        g.lineTo(w-2, h*3/4);
        g.lineTo(2, h*3/4);
        
        g.beginFill(color);
        g.moveTo(2, h*3/4);
        g.lineTo(2+(w-4)*level, h*3/4-h/2*level);
        g.lineTo(2+(w-4)*level, h*3/4);
        g.lineTo(2, h*3/4);
        g.endFill();
    }
    
    private function mouseClickHandler(event:MouseEvent):void
    {
        if (this.enabled && this.width > 0) {
            level = this.mouseX / this.width;
            updateDisplayList(this.unscaledWidth, this.unscaledHeight);
            dispatchEvent(new Event(Event.CHANGE));
        }
    }
};


class VolumeLevel extends UIComponent
{
    private var _level:Number = 0.5;
    
    public function VolumeLevel()
    {
        super();
        setStyle("borderThickness", 0);
        setStyle("fillColors", [0xcacaca, 0xb8b8b8]);
        setStyle("fillOverColors", getStyle("fillColors"));
        setStyle("fillDownColors", getStyle("fillColors"));
        setStyle("skin", ShinyButtonSkin);
    }
    
    public function get level():Number
    {
        return _level;
    }
    public function set level(value:Number):void
    {
        var oldValue:Number = _level;
        if (value >= 0 && value <= 1.0) {
            _level = value;
            updateDisplayList(unscaledWidth, unscaledHeight);
        }
        if (oldValue != value) 
            dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, "level", oldValue, value));
    }
    
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        var g:Graphics = graphics;
        g.clear();
        
        var borderThickness:uint = getStyle("borderThickness") != null ? getStyle("borderThickness") : 0;
        var borderColor:uint     = getStyle("borderColor") != null ? getStyle("borderColor") : 0xb7babc;
        var fillColors:Array     = getStyle("fillColors") != null ? getStyle("fillColors") : [0xcacaca, 0xc0c0c0];
        var backgroundAlpha:Number = getStyle("backgroundAlpha") != null ? getStyle("backgroundAlpha") : 1.0;
        var color:uint = getStyle("color") != null ? getStyle("color") : 0x000000;
        var inactiveColor:uint   = getStyle("inactiveColor") != null ? getStyle("inactiveColor") : Math.max(fillColors[1] - 0x050505, 0x000000);
        
        ShinyButtonSkin.drawShinySkin(g, fillColors, w, h, borderThickness, borderColor, backgroundAlpha);
    
        g.lineStyle(1, color);
        for (var i:int = 0; i<(w-5); i+= 6) {
            var active:Boolean = enabled && (level > i/w);
            g.lineStyle(1, active ? color : inactiveColor);
            if (active)
                g.beginFill(level > i/w ? color : fillColors[1]);
            g.drawRect(i, h/4, 4, h/2);
            if (active)
                g.endFill();
        }    
    }
};

[Event(name="change", type="flash.events.Event")]

class PlayPositionSlider extends UIComponent
{
    private var _position:Number = 0;
    private var _total:Number = 0;
    private var _text:TextField;
    
    public function PlayPositionSlider()
    {
        super();
        buttonMode = true;
        setStyle("borderThickness", 0);
        setStyle("fillColors", [0xcacaca, 0xb8b8b8]);
        setStyle("fillOverColors", [0xcacaca, 0xb8b8b8]);
        setStyle("fillDownColors", [0xcacaca, 0xb8b8b8]);
        setStyle("disabledColor", 0xa0a0a0);
        setStyle("showTrackHighlight", true);
        setStyle("skin", ShinyButtonSkin);
        addEventListener(MouseEvent.CLICK, mouseClickHandler, false, 0, true);
        
        _text = new TextField();
        _text.x = 6;
        _text.y = 4;
        var format:TextFormat = new TextFormat();
        format.font = "Arial";
        format.color = 0x606060;
        format.size = 9;
        _text.mouseEnabled = false;
        _text.defaultTextFormat = format;
        addChild(_text);
    }
    
    public function get total():Number
    {
        return _total;
    }
    public function set total(value:Number):void
    {
        _total = value;
        invalidateDisplayList();
    }
    
    public function get position():Number
    {
        return _position;
    }
    public function set position(value:Number):void
    {
        _position = value;
        invalidateDisplayList(); 
    }
    
    override protected function updateDisplayList(w:Number, h:Number):void
    {
        var g:Graphics = graphics;
        g.clear();
        
        super.updateDisplayList(w, h);
        
        var borderThickness:uint = getStyle("borderThickness") != null ? getStyle("borderThickness") : 0;
        var borderColor:uint     = getStyle("borderColor") != null ? getStyle("borderColor") : 0xb7babc;
        var fillColors:Array     = getStyle("fillColors") != null ? getStyle("fillColors") : [0xcacaca, 0x989898];
        var backgroundAlpha:Number = getStyle("backgroundAlpha") != null ? getStyle("backgroundAlpha") : 1.0;
        var color:uint = getStyle("color") != null ? getStyle("color") : 0x000000;
        var color1:uint = Math.min(color+0x202020, 0xffffff);
        if (!enabled) 
            color = getStyle("disabledColor") != null ? getStyle("disabledColor") : 0x202020;
            
        ShinyButtonSkin.drawShinySkin(g, fillColors, w, h, borderThickness, borderColor, backgroundAlpha);
        
        if (total > 0) {
            g.lineStyle(1, color);
            g.drawRect(5, h/4, w-10, h/2);
            
            if (!isNaN(position)) {
                var pos:Number = position <= total ? position : total;
                g.beginFill(color1);
                g.drawRect(5, h/4, (w-10)*pos/total, h/4);
                g.endFill();
                g.beginFill(color);
                g.drawRect(5, h/2, (w-10)*pos/total, h/4);
                g.endFill();
            }
        }
            
        _text.text = formatDuration(position);
            
    }
    
    private function formatDuration(duration:int):String
    {
        var hh:int = Math.floor(duration / 3600);
        var mm:int = Math.floor((duration % 3600) / 60);
        var ss:int = duration % 60;
        var value:String = (mm < 10 ? '0' + mm.toString() : mm.toString()) + ":" + (ss < 10 ? '0' + ss.toString() : ss.toString());;
        if (hh > 0)
            value = hh.toString() + ":" + value;
        return value;
    }
    
    private function mouseClickHandler(event:MouseEvent):void
    {
        if (this.enabled && this.width > 0) {
            if (total > 0) {
                var value:Number = (this.mouseX - 5) / (this.width - 10) * total;
                this.position = (value < 0 ? 0 : (value > total ? total : value));
                invalidateDisplayList();
                dispatchEvent(new Event(Event.CHANGE));
            }
        }
    }
};