mediastream.lzx
<library>
<class name="mediastream" extends="node">
    <passthrough when="$as3">
        import flash.media.*;
        import flash.events.*;
        import flash.net.NetConnection;
        import flash.net.NetStream;                
        import flash.utils.setTimeout;
    </passthrough>
    <doc>
        <tag name="shortdesc">
        <text>Allows video playback, live broadcast or recording .</text></tag>
        <text>
        <p><tagname>mediastream</tagname> allows applications to stream video from HTTP or RTMP servers.  Classes like <sgmltag class="element" role="lz.videoview"><videoview></sgmltag> use mediastreams to handle the connection to the server.</p> 
            
        <example><programlisting class="code">
        <canvas>
            <mediastream name="ms" autoplay="true" type="http" url="http://www.archive.org/download//JudgeMediaTestVideoFile_0/video.flv"/>
            <videoview type="http" autoplay="true" width="320" height="240" stream="canvas.ms"/>
        </canvas>
        </programlisting></example>
        </text>
    </doc>
  
    
    <attribute name="url" value="" type="string"/>
    
    
    <attribute name="type" value="http" type="string"/>
    
    <attribute name="autoplay" value="false" type="boolean"/>
    
    <attribute name="autorewind" value="true" type="boolean"/>
    
    <attribute name="autoplayStart" value="'either'"/>
    
    <attribute name="autoplayPause" value="false" type="boolean"/>
    
    <attribute name="autoplayLength" value="'end'"/>
    
    <attribute name="autoplayReset" value="true"/>
    
    <attribute name="totaltime" value="0" type="number"/>
    
    <attribute name="time" value="0" type="number"/>
    
    <attribute name="buffertime" value="0.1" type="number"/>
    
    <attribute name="progress" value="0" type="number"/>
    
    <attribute name="fps" type="number" value="0"/>
        
    
    <attribute name="paused" value="false" type="boolean"/>
        
    
    <attribute name="playing" value="${(this.mode == 'playing') && (!this.paused)}"/>
    
    <attribute name="recording" value="${this.mode == 'recording'}"/>
    
    <attribute name="broadcasting" value="${(this.mode == 'broadcasting') || (this.mode == 'recording')}"/>
    
    <attribute name="mode" value="" type="string"/>
        
    
    <attribute name="debug" value="false"/>
    
    <attribute name="cam" value="null"/>
    
    <attribute name="mic" value="null"/>
    
    <attribute name="streamname" value="" type="string"/>
        
    
    <attribute name="rtmp" value="null"/>
    
    <attribute name="muteaudio" value="false"/>
    
    <attribute name="mutevideo" value="false"/>
    
    <attribute name="_nc" value="null"/>
    
    <attribute name="_nullnc" value="null"/>
    
    
    <attribute name="_flashstream" value="null"/>
    
    <attribute name="_timedel" value="null"/>
    
    <attribute name="_pendingstreamname" value="" type="string"/>
    
    <attribute name="_basetime" value="0" type="number"/>
    
    <attribute name="_creationCallback" value="null"/>
    
    <event name="onurl"/>
    
    <event name="ontype"/>
    
    <event name="oncuepoint"/>
    
    <event name="onstart"/>
    
    <event name="onstop"/>
    
    <event name="oninsufficientbandwidth"/>
    
    <event name="onmetadata"/>
     
    
    <event name="onmode"/>
 
    
    <event name="onbuffertime"/>
 
    
    <method name="_resetStream">
        if (this._flashstream != null) {
            this._flashstream.play(false);
            this._flashstream.close();
            this._flashstream = null;
        }
    </method> 
    
    
    <method name="play" args="start='either', pause=false,                                     length='end', reset=true"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w play url=%w" +
                    "start=%w pause=%w length=%w reset=%w",
                    this, this.url, start, pause, length, reset);
            }
        }
        // Map symbolic start values to Flash integers.
        switch (start) {
            case "recorded":
                start = 0;
                break;
            case "live":
                start = -1;
                break;
            case "either":
                start = -2;
                break;
            default:
                if (start < 0) {
                    if ($debug) {
                        Debug.warn("mediastream.play %w called with Flash-dependent start param: %w", this, start);
                    }
                }
                break;
        }
        // Map symbolic length values to Flash integers.
        switch (length) {
            case "end":
                length = -1;
                break;
            default:
                if (length < 0) {
                    if ($debug) {
                        Debug.warn("mediastream.play %w called with Flash-dependent length param: %w", this, length);
                    }
                }
                break;
        }
        // Map symbolic reset values to Flash integers. 
        switch (reset) {
            case true: 
            case false: {
                // The same.
                break;
            }
            case 'queueWithImmediateMessages':
                reset = 2;
                break;
            case 'resetWithImmediateMessages':
                reset = 3;
                break;
            default:
                if ($debug) {
                    Debug.warn("mediastream.play %w called with Flash-dependent length param: %w", length);
                }
                break;
        }
        if (this.debug) {
            if ($debug) {
                Debug.write("mediastream play", this, "_flashstream", this._flashstream, "type", this.type, "url", this.url);
            }
        }
        // unload 
        if (this._flashstream) {
            if (this.paused) {
                // unpause
                this.setAttribute('paused', false);
                return;
            } else {
                // clear, reset
                this._resetStream();
            }
        }
        // Start must be undefined for a live mediastream.
        this._basetime = ((start == undefined) || (start < 0)) ? 0 : start;
        this.setAttribute("time", 0);
        this.setAttribute("progress", 0.0);
        
        // call internal play method after start, length, and reset params 
        // have been translated into confusing flash player values
        this._play(pause, start, length, reset);
    
    </method>
    
    <method name="_play" args="pause, start, length, reset"> 
        var isnew = false;
        if (!this._flashstream) {
            isnew = true;
            this._createStream("_play", [pause, start, length, reset]);
            if (!this._flashstream) {
                return;
            }
        }                            
        // note the mode 'playing' means playing, as opposed to recording or
        // broadcasting.  It also includes the 'paused' state
        this.setAttribute("mode", "playing");
        if ($debug) {
            if (!this._flashstream) {
                Debug.warn('mediastream %w unexpected _flashstream=%w', 
                    this, this._flashstream); 
            }
        }
        // set up delegate to update the time, progress and fps attributes.
        this._activateTimeDel();
        //var sname = this._namefromurl(this.url);
        var sname = this.url;
        //Debug.write("start=", start);
        //Debug.write("pause=", pause);
        
        if (pause) {
            //Debug.write("mediastream play PAUSE",  this, "start", start, "pause", pause, "length", length, "reset", reset);
            //seems like overkill, but all of the following seemed to be
            //required
            this._flashstream.play(sname, start, length, reset);
            this.setAttribute("paused", true);
            this._flashstream.seek(0);
        } else {
            //Debug.write("mediastream play PLAY %w sname=%w start=%w length=%w reset=%w ",  
            //    this, sname, start, length, reset);
            //this._flashstream.play(sname);
            this._flashstream.play(sname, start, length, reset);
            this.setAttribute("paused", false);
        }
        if (this.type == "rtmp") {
            this._pendingstreamname = sname;
        }
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w play initiated sname=%w"
                    +" mode=%w", this, sname, this.mode);
            }
        }
        
    </method> 
    
    <method name="_handleAutoplay" args="ignore=null"> 
        //Debug.write("mediastream._handleAutoplay()");
        if ((this.url == "") ||
            (!this.autoplay)) {
            return;
        }
        this.play(
            this.autoplayStart, 
            this.autoplayPause, 
            this.autoplayLength, 
            this.autoplayReset);
    </method> 
        
    
    <method name="_activateTimeDel">
        //Debug.write("mediastream._activeTimeDel()", this);
        if (!this._timedel) {
            this._timedel = new LzDelegate(this, "_updateTime");
        }
        this._timedel.unregisterAll();
        this._timedel.register(lz.Idle, "onidle");
    </method>
    
    <method name="_deactivateTimeDel">
        //Debug.write("mediastream._deactiveTimeDel()", this);
        if (this._timedel) {
            this._timedel.unregisterAll();
        }
    </method>  
    
    <method name="_updateTime" args="ignore">      
        //Debug.write("mediastream._updateTime time=%w mode=%w paused=%w", 
        //   this.time, this.mode, this.paused);
        /* seems to work better without this
        // don't update if new lz.mediastream about to start
        if (this._pendingstreamname != "") {
            if ($as3) {
                // TODO: why doesn't _pendingstreamname get reset?
            } else {
                return;
            }
        }
        */
        if (!this._flashstream) return;
        var time = 0;
        var progress = 1.0;
        if ($as3) {
            var fps = this._flashstream.currentFPS;
        } else {
            var fps = this._flashstream.currentFps;
        }
        switch (this.mode) {
            case "playing": {
                time = 
                    this._flashstream.time + this._basetime;
                var bytesTotal = this._flashstream.bytesTotal;
                var bytesLoaded = this._flashstream.bytesLoaded;
                if ((bytesTotal != null) && (bytesTotal > 0)) {
                    progress = bytesLoaded / bytesTotal;
                }
                break;
            }
            case "recording": {
                var time = 
                    ((new Date()).getTime() - this._basetime) / 1000;
                if (time != this.time) {
                    this.setAttribute("time", time);
                }
                break;
            }
            default: {
                break;
            }
        }
        if (time != this.time) {
            this.setAttribute("time", time);
        }
        if (this.progress != progress) {
            this.setAttribute("progress", progress);
        }
        //Debug.write("_updateTime", this, "_flashstream", this._flashstream, "_pendingstreamname", _pendingstreamname);
        // Only update in playback mode.
        if (fps != this.fps) {
            this.setAttribute("fps", fps);
        }
        // Push totaltime up if time past end
        // (because we got the wrong totaltime somehow).
        if ((this.totaltime == 0) ||
            isNaN(this.totaltime) ||
            (this.totaltime < this.time)) {
            this.setAttribute("totaltime", this.time);
        }
        // Keep playing while we're recording, 
        // or while we're playing, 
        // or while we're still downloading. 
        // (So the download progress bar updates while we pause playing.)
        if ((this.mode != "recording") &&
            (this.mode != "playing") &&
            (progress == 1.0)) {
            this._deactivateTimeDel();
        }
    
    </method>  
   
    
    <method name="_publishSetup">     
        //Debug.write("_publishSetup", this, this.streamname);
        if (this.type != "rtmp") {
            if ($debug) {
                Debug.warn("in order to record, type must be rtmp %w", this);
            }
            return;
        }
        if (this.cam) {
            this.setAttribute("mutevideo", false);
        }
        if (this.mic) {
            this.setAttribute("muteaudio", false);
        }
        if (this.streamname != "") {
            // cool; already has a name
        } else if (this.url != "") {
            this.streamname = this._namefromurl(this.url);
        } else {
            // TODO? make up temporary name
        }
        
    </method>
    
    <method name="broadcast">
        if (!this._flashstream) {
            this._createStream("broadcast");
        } 
        // rely on callback for as3
        if (! this._flashstream) return;
        this._publishSetup();
        //Debug.write("publish", this.streamname);
        this._flashstream.publish(this.streamname);
        this.setAttribute("mode", "broadcasting");
        
    </method>
    
    <method name="record">
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w record: "+
                    "fs=%w streamname=%w url=%w", 
                    this, this._flashstream, this.streamname, this.url);
            }
        }
        if (!this._flashstream) {
            this._createStream("record");
        }
        // rely on callback for as3
        if (! this._flashstream) return;
        this._publishSetup();
        //Debug.write("mediastream record", this, "done with setup, streamname", this.streamname);
        this._flashstream.publish(this.streamname, "record");
        //Debug.write("publish", this.streamname);
        this._basetime = (new Date).getTime();
        //Debug.write("basetime = ", this._basetime);
        this.setAttribute("time", 0);
        this.setAttribute("progress", 0.0);
        this.setAttribute("mode", "recording");
        this._activateTimeDel();
         
    </method>
    
    <method name="pause" args="p=null">
        //Debug.write("pause", this, "p", p, "p == null", p == null, "this['paused']", this['paused']);
        var val = (p == null) ? !this['paused'] : p;
        if (val != this.paused) this.setAttribute("paused", val)
    </method>            
    
    <method name="stop"> 
        if ($debug) {
            if (this.debug) {
                Debug.write('mediastream %w stop fs=%w mode=%w', 
                        this, this._flashstream, this.mode);
            }
        }
            
        if (this.paused) {
            // can't be paused and stopped at the same time...
            setAttribute('paused', false);
        }
    
        if (this._flashstream == null) {
            return;
        }
        switch (this.mode) {
            case "recording":
                this._deactivateTimeDel();
                // fall through to broadcasting
            case "broadcasting":
                if ($as3) {
                    this._flashstream.close();
                    //See this doc for info: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#close()
                    //See this doc for info: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#publish()
                } else {
                    this._flashstream.publish(false);
                }
                break;
            case "playing":
                this._flashstream.play(false);
                this._deactivateTimeDel();
                break;
            case "":
                // Already stopped. 
                break;
            default:
                if ($debug) Debug.warn("bogus mode: " + this.mode);
                break;
        }
        this.setAttribute("time", 0);
        this.setAttribute("progress", 0);
        this.setAttribute("mode", "");
        
    </method> 
    <method name="close">
        //Debug.write("mediastream.close()");
        //Debug.write("FLASHSTREAM CLOSE", this, this._flashstream);
        if (this._flashstream == null) {
            return;
        }
        this.stop();
        if ($as2) {
            this._flashstream.attachVideo(null);
        }
        this._flashstream.attachAudio(null);
        this._flashstream.close();
        this._flashstream = null;
        //this._flushnc()
    </method>
    
    <method name="_findnc"> 
        //Debug.write("_findnc", this, this._nc, this.type, this.url, "isinited", this.isinited);
        if (this.type == undefined) return;  // called too early 
        if (this._nc) {
            return;
        }
        //Debug.write('_findnc', this.type);
        switch (this.type) {
            case "http": {
                // keep one of these around for any http mediastream
                // we need (weird flash thing that we need a 
                // null NetConnection)
                lz.mediastream._nullnc = new NetConnection();
                lz.mediastream._nullnc.connect(null);
                this._nc = lz.mediastream._nullnc;
                this._srcurl = null;
                //Debug.write("_findnc made new _nullnc", this._nc, this._srcurl);
                break;
            }
            case "rtmp": {
                //Debug.write("lz.rtmpconnection", lz.rtmpconnection);
                if (lz.rtmpconnection == undefined) {
                    if ($debug) Debug.warn("rtmpconnection must be included for %w", this);
                } 
                // If we don't define our own connection, then use
                // the default one (the first rtmpconnection created).
                //Debug.write("_findnc type rtmp, rtmpconnection", this.rtmpconnection, "_nc", lz.rtmpconnection.connections._default._nc);
                if (! this.rtmp && lz.rtmpconnection.connections) {
                    this.rtmp = lz.rtmpconnection.connections._default;
                }
                
                if (! this.rtmp) {
                    if ($debug) Debug.warn("No rtmpconnection found for %w.", this);
                    return
                }
                // register for connection
                new LzDelegate(this, '_handleAutoplay', this.rtmp, 'onconnect');
                // register for reconnection
                new LzDelegate(this, '_recreateStream', this.rtmp, 'on_nc');
                this._nc = this.rtmp._nc;
                this._srcurl = this.rtmp.src;
                //Debug.write('found rtmp', this._nc, this._srcurl);    
                break;
            }
            default: {
                if (this.debug) {
                    if ($debug) Debug.warn(
                        "unexpected protocol for url: %w %w",
                        this.url, 
                        this);
                }
                break;
            }
        }
    
    </method> 
    
    <method name="_recreateStream" args="ignore=null">
        this._nc = this.rtmp._nc;
        this._flashstream = null;
    
    </method>
    
    <method name="_flushnc">
        //Debug.write("mediastream._flushnc()");
        if (this._nc == null) {
            return;
        }
        //Debug.write('_flushnc', this, this.type, this._nc);
        this._nc = null;
    </method>  
    
    <method name="_createStream" args="callbackName='', callbackArgs=null">      
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w type=%w creating internal flash "
                    + "stream, _nc=%w _flashstream=%w url=%w callback=%w", 
                    this, this.type, this._nc, this._flashstream, this.url,
                    callbackName);
            }
        }
        if (!this.url || this.url == '') {
            if ($debug) {
                if (this.debug) {
                    Debug.warn( "Can't create mediastream w/ no url: %w %w", 
                    this, arguments.caller);
                }
            }
            return;
        }
        if (this._flashstream) {
            if ($debug) Debug.warn("_flashstream already defined: %w", this._flashstream);
            return;
        }
        if (! this._nc) {
            if ($debug) {
                // note: null nc is creted when setting the type for 'http'
                if (this.type && this.type != 'http') {
                    Debug.warn("No netconnection defined for %w.", this, 
                        this._nc);
                }
            }
            return;
        }
        if ($as3) {
            if(this._nc.connected) {
                _AS3createStream();
            } else {
                // TODO: why aren't we already connected?
                //Debug.warn('Must connect before creating stream', this._nc, this._srcurl, this._nc.connected);
                this._nc.addEventListener(NetStatusEvent.NET_STATUS, this._onNetStatusAS3);
                this._nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this._onSecurityError);
                this._creationCallback = {methodName: callbackName, args: callbackArgs};
                setTimeout(this.rtmp._doconnect, 1);
            }
        } else {
            this._flashstream = new NetStream(this._nc);
            this._flashstreamSetup( this._flashstream  );
        }
        
    </method> 
    
    <method name="_onSecurityError">
        //the following does not compile in swf9 since error is undefined
        //not sure where that used to come from
        //if ($debug) Debug.warn("mediastream _onSecurityError() " + error);
    </method> 
    
    <method name="_onAsyncError">
        //the following does not compile in swf9 since error is undefined
        //not sure where that used to come from
        //if ($debug) Debug.warn("mediastream _onAsyncError() " + error);
    </method> 
    
    <method name="_onNetStatusAS3" args="evt"> 
        if ($debug) {
            if (this.debug) Debug.info("mediastream _onNetStatusAS3() " + evt.info.code);
        }
        if (evt && evt.info && evt.info.code == 'NetConnection.Connect.Success') {
            _AS3createStream();
        } else {
            if (evt && evt.info && evt.info.code) this._onStatus(evt.info);
        }
    
    </method> 
    
    
    <method name="_AS3createStream">
        var fs = new NetStream(this._nc);
        this._flashstreamSetup(fs);
        if (this._creationCallback) {
            this[this._creationCallback.methodName].apply(this, this._creationCallback.args);
        }
    </method>
    
    <method name="_flashstreamSetup" args="netstream">
        //Debug.debug("_flashstreamSetup netstream=%w", netstream);
        this.setAttribute("_flashstream", netstream);
        if ($as3) {
            this._flashstream.bufferTime = this.buffertime;
        } else {
            this._flashstream.setBufferTime(this.buffertime);
        }
        if ($as3) {
            this._flashstream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, this._onAsyncError);
            this._flashstream.addEventListener(NetStatusEvent.NET_STATUS, this._onNetStatusAS3);
            var attachPoint = {};
        } else {
            var attachPoint = this._flashstream;
        }
        var _this = this;
        attachPoint.onStatus = function(info) {
            _this._onStatus(info);
        };
        attachPoint.onCuePoint = function(info) {
            _this._onCuePoint(info);
        };
        attachPoint.onPlayStatus = function(info) {
            _this._onPlayStatus(info);
        };
        attachPoint.onMetaData = function(info) {
            _this._onMetaData(info);
        };
        if ($as3) {
            this._flashstream.client = attachPoint;
        }
        //Debug.debug("_flashstreamSetup end");
    </method>
    
    <method name="_onStatus" args="info"> 
        if (! this._flashstream) return;
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w _onStatus %w %w time=%w",
                this, info.code, info, this._flashstream.time);
            }
        }
        switch (info.code) {
            case "NetStream.Buffer.Empty": {
                //var newbuftime = this._flashstream.bufferTime + 1;
                //this._flashstream.setBufferTime(newbuftime);
                //Debug.write("newbuffertime", newbuftime);
                break;
            }
            case "NetStream.Buffer.Full": {
                if (this._pendingstreamname != "") {
                    this.setAttribute("streamname", this._pendingstreamname);
                    this._pendingstreamname = "";
                }
                break;
            }
            case "NetStream.Play.Start": {
                if (this._pendingstreamname != "") {
                    //v.show(); // FIXME: what is v?
                }
                this._onStart(info);
                break;
            }
            case "NetStream.Play.Stop": {
                // don't call _onStop for rtmp streams because some servers sent this event when they're done streaming (red5, wowza) - see LPP-7708
                if (this.type == 'http') this._onStop();
                break;
            }
            case "NetStream.Play.InsufficientBW": {
                this._onInsufficientBandwidth(info);
                break;
            }
            case "NetStream.Record.Start": {
                this._onStart(info);
                break;
            }
            case "NetStream.Record.Stop": {
                this.stop();
                break;
            }
            case "NetStream.Buffer.Flush": {
                break;
            }
            case "NetStream.Publish.Start": {
                // Sent when starting recording from camera to server,
                // and apparently when we stop as well.
                // info.details = first time: null, subsequently: false
                // No way to figure out if it's starting or stopping.
                break;
            }
            case "NetStream.Play.UnpublishNotify": {
                // Called when stop publishing live video cam. (???)
                break;
            }
            case "NetStream.Unpublish.Success": {
                // Sent after you start playing a stream after recording it. (???)
                break;
            }
            case "NetStream.Pause.Notify": {
                // Sent after you pause.
                break;
            }
            case "NetStream.Unpause.Notify": {
                // Sent after you unpause.
                //Debug.write("NetStream.Unpause.Notify");
                break;
            }
            case "NetStream.Play.Reset": {
                // Sent after you start playing a stream after recording it. (???)
                break;
            }
            case "NetStream.Seek.Notify": {
                // Sent after seeking.
                break;
            }
            default: {
                if ($debug) {
                    if (this.debug) Debug.warn("mediastream _onStatus not handled %w %w %w", this, info.code, info);
                }
                break;
            }
        }
    
    </method> 
    
    <setter name="buffertime" args="time=0">
        if (time < 0) time = 0;
        if (time != this.buffertime) {
            if (this._flashstream) {
                if ($as3) {
                    this._flashstream.bufferTime = time;
                } else {
                    this._flashstream.setBufferTime(time);
                }
            }
        }
        this.buffertime = time;
        if (this.onbuffertime.ready) this.onbuffertime.sendEvent(time);
     
    </setter>
    
    <setter name="type" args="newtype">
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set type=%w called by %w",
                    this, newtype, arguments.caller);
            }
        }
        var old = this.type;
        this.type = newtype;
        //Debug.write("mediastream set type", this, "old", old, "new", newtype, "_nc", this['_nc'], "isinited", this.isinited);
        if ((!this.isinited) || 
            (old == newtype)) {
            //Debug.write("set type returning because !isinited or same url", this, newtype);
            return;
        }
        this._flushnc();
        this._updateUrl();
        if (this.ontype.ready) {
            this.ontype.sendEvent(this.type);
        }
     
    </setter>
    
    <setter name="mode" args="newMode"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set mode=%w playing=%w paused=%w",
                    this, newMode, this.playing, this.paused);
            }
        }
        this.mode = newMode;
        if (this.onmode.ready) {
            this.onmode.sendEvent(newMode);
        }
    
    </setter>
    
    
    <method name="_onCuePoint" args="info">
        //Debug.write("mediastream._onCuePoint()");
        //Debug.write("ms onCuePoint", this, info);
        if (this.oncuepoint) {
            this.oncuepoint.sendEvent(info);
        }
    </method> 
    
    <method name="_onStart" args="info">
        // TODO: should be conditionally compiled for debug
        //       but I forget the syntax for that
        //Debug.write("mediastream._onstart()");
        if (this.onstart) this.onstart.sendEvent(this);
    </method> 
    
    <method name="_onStop" args="info=null"> 
        //Debug.write("mediastream._onstop before mode=%w",this.mode);
        // I don't know why this is sometimes called when
        // seeking while paused, but it is 
        if (this.paused) return; 
        if (!this.paused) this.setAttribute('paused', true);
        if (this.autorewind) {
            // TODO: time != totaltime here, so the test failed - seek anyhow.:
            // Debug.info('times', this.time == this.totaltime, this.time, this.totaltime);
            this.seek(0);
        }
        if (this.onstop) this.onstop.sendEvent(this);
        //Debug.write("mediastream._onstop after mode=%w",this.mode);
        
    </method>
    
    <method name="_onInsufficientBandwidth" args="info">
        Debug.write("mediastream.oninsufficientbandwidth()");
        if (this.oninsufficientbandwidth) {
            this.oninsufficientbandwidth.sendEvent(this);
        }
    </method>
    
    <method name="_onPlayStatus" args="info"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w onPlayStatus %w %w", 
                    this, info.code, info);
            }
        }
        switch (info.code) {
            case "NetStream.Play.Complete": {
                // Correct for valid Buffer.Empty.
                // I don't understand why it's doing this. -dhopkins
                //var newbuftime = 
                //    this._flashstream.bufferTime - 1;
                //this._flashstream.setBufferTime(newbuftime);
                //Debug.write("newbuffertime=",newbuftime);
                this.stop();
                break;
            }
            default: {
                if ($debug) {
                    if (this.debug) {
                        Debug.warn("mediastream _onPlayStatus not handled: %w %w %w", this, info.code, info);
                    }
                }
                break;
            }
        }
    
    </method>
    
    <method name="_onMetaData" args="info"> 
        //Debug.write("mediastream %w _onMetaData", this);
        if ($debug) {
            if (this.debug) {
                for (var propName in info) {
                    Debug.write("MetaData: " + propName + " = " + info[propName]);
                }
            }
        }
        if (('duration' in info) && this.totaltime !== info.duration) {
            //Debug.write("Total play time: ", info.duration);
            this.setAttribute("totaltime", info.duration);
        }
        if (this.onmetadata.ready) this.onmetadata.sendEvent(info);
    
    </method>
    
    <setter name="paused" args="paused"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set paused=%w", 
                    this, paused);
            }
        }
        if ((this.mode != "playing")  || !this._flashstream) {
            if (paused) {
                this.play('either', true); 
            }
            return;
        }
        if ($as3) {
            if (paused) {
                this._flashstream.pause();
            } else {
                this._flashstream.resume();
            }
        } else {
            this._flashstream.pause(paused);
        }
        this.paused = paused;
        if (this['onpaused']) {
                this.onpaused.sendEvent(paused);
        }
        // Stop the timer if we're paused 
        if (this.paused) {
            this._deactivateTimeDel();
        } else {
            this._activateTimeDel();
        }
        //Debug.write("PAUSE", "after playing", this.playing);
    
    
    </setter>
    
    
    <method name="seek" args="t"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w seek %w",
                            this, t);
            }
        }
        if (url == "") return;
        if (!this._flashstream) {
            this._createStream("seek", [t]);
        } 
        
        if (this._flashstream) {
            this._flashstream.seek(t);
            // DON'T call _updateTime since the flashstream's time
            // will be wrong for a while and later it will show the closest
            // keyframe's time, which is arguably correct but screws up
            // the displays of scrubbers and whatnot
            this.setAttribute('time', t);
            if (!this.playing) {
                // just in case we have never displayed the video
                this.pause(true);
            }
        }
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w seek(%w %w) fs=%w", 
                    this, typeof(t), t, this._flashstream);
            }
        }
    
    </method> 
    
    <method name="init"> 
        this.paused = false;
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w init immediateparent=%w, url=%w" +
                    "called by %w",
                    this, this.immediateparent, url, arguments.caller);
            }
        }
        super.init();
        // check for the existence of immediateparent.videoview
        if (lz['videoview'] && immediateparent instanceof lz.videoview) {
            immediateparent.setAttribute('stream', this);
        }
        if (this.url != "") {
            this.setAttribute('url', this.url);
        }
    
    </method>
    
    <method name="_namefromurl" args="fromurl"> 
        var sname = fromurl;
        //Debug.write("sname=", sname, "suffix=", sname.substr(-4));
        // FIXME: Compare file name suffix ignoring case.
        if ((this.type == "rtmp") && 
            (sname.substr(-4) == ".flv")) {
            sname = sname.slice(0,-4);
        }
        //Debug.write("mediastream._namefromurl(%w) %w", fromurl, sname);
        return sname;
    
    </method>  
    
    <setter name="url" args="newurl"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set url=%w called by %w", 
                    this, newurl, arguments.caller);
            }
        }
        var old = this["url"];
        //Debug.write("mediastream set url", this, "old", old, "new", newurl, "_nc", this['_nc'], "isinited", this.isinited);
        this.url = newurl;
        this.setAttribute("streamname", "");
        if ((!this.isinited)) {
            //Debug.write("set url returning because !isinited", this, newurl);
            //TODO: find out why we stop here on construct...
            return;
        }
        this._updateUrl();
        if (this.url != "") { // changing the url
            this._handleAutoplay();
        }
        if (this.onurl.ready) {
            //Debug.write("mediastream.set url() / sending onurl event!");
            this.onurl.sendEvent(this.url);
        }
    
    </setter>
    
    <method name="_updateUrl"> 
        // Debug.write("   _updateUrl", this, this.type, this.url, "isinited", this.isinited);
        
        this._resetStream();
        this._findnc();
        if ((this.type == "rtmp") &&
            (this.url != null) &&
            (this.url != "")) {
            //this breaks red5 and depends on Flash Media Server - see LPP-4785
            /** TODO: when to do this? 
            // rtmp server appends the .flv (so we need to remove it)
            var sname = this._namefromurl(this.url);
            // call function in server-side main.asc
            var t = this; // for use in closure
            this.onResult = function(streamLength) {
                //_root.Debug.write("onresult=", streamLength);
                t.setAttribute("totaltime", streamLength || 0);
            };
            */
        }
    
    </method>
      
    
    <setter name="cam" args="cam">
        this.cam = cam;
        if (this["oncam"]) this.oncam.sendEvent();
    </setter>
    
    <setter name="mic" args="mic">
        this.mic = mic;
        if (this["onmic"]) this.onmic.sendEvent();
    </setter>
        
    
    <setter name="muteaudio" args="muteaudio"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set muteaudio=%w called by %w",
                    this, muteaudio, arguments.caller);
            }
        }
        this.muteaudio = muteaudio;
        if (!this.mic)           return;
        if (!this._flashstream)  return;
        this._setupStreamAudio();
        if (this["onmuteaudio"]) this.onmuteaudio.sendEvent();
    
    </setter> 
    
    
    <setter name="mutevideo" args="mutevideo"> 
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w set mutevideo=%w called by %w",
                    this, mutevideo, arguments.caller);
            }
        }
        
        this.mutevideo = mutevideo;
        if (!this.cam)  return;
        if (!this._flashstream)  return;
        this._setupStreamVideo();
        if (this["onmutevideo"]) this.onmutevideo.sendEvent();
    
    </setter> 
    
    <method name="_setupStreamAudio">
        if ($as2) {
            var noaudioobject = false;
        } else {
            // attachAudio() requires a Microphone object in AS3 - see 
            // http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/net/NetStream.html#attachAudio%28%29
            var noaudioobject = null;
        }
        this._flashstream.attachAudio(this.muteaudio ? noaudioobject : this.mic._dev);
    </method>
    
    <method name="_setupStreamVideo">
        
        if ($debug) {
            if (this.debug) {
                Debug.write("mediastream %w _setupStreamVideo: " +
                        "fs=%w, cam=%w, mutevideo=%w",
                        this, this._flashstream, this.cam, this.mutevideo);
            }
        }
        if ($as3) {
            this._flashstream.attachCamera(this.mutevideo ? null : this.cam._dev);
        } else {
            this._flashstream.attachVideo(this.mutevideo ? false : this.cam._dev);
        }
        
    </method>
</class>        
</library>