//**************************************************************************** //Copyright (C) 2004-2005 Macromedia, Inc. All Rights Reserved. //The following is Sample Code and is subject to all restrictions on //such code as contained in the End User License Agreement accompanying //this product. //**************************************************************************** import mx.events.EventDispatcher; import mx.video.*; /** *

Event dispatched when NetConnection is closed, * whether by being timed out or by calling close() API. * Only dispatched with RTMP streams, never HTTP. Event Object has * properties state and playheadTime.

* */ [Event("close")] /** *

Event dispatched when playing completes by reaching the end of * the FLV. Is not dispatched if the APIs stop() or * pause() are called. Event Object has properties state and * playheadTime.

* *

When using progressive download and not setting totalTime * explicitly and downloading an FLV with no metadata duration, * the totalTime will be set to an approximate total value, now * that we have played the whole file we can make a guess. That * value is set by the time this event is dispatched.

* */ [Event("complete")] /** *

Event dispatched when a cue point is reached. Event Object has an * info property that contains the info object received by the * NetStream.onCuePoint callback for FLV cue points or * the object passed into the AS cue point APIs for AS cue points.

* */ [Event("cuePoint")] /** *

Event dispatched the first time the FLV metadata is reached. * Event Object has an info property that contains the info object * received by the NetStream.onMetaData callback.

* */ [Event("metadataReceived")] /** *

While FLV is playing, this event is dispatched every .25 * seconds. Not dispatched when we are paused or stopped, unless a * seek occurs. Event Object has properties state and playheadTime.

* */ [Event("playheadUpdate")] /** *

Indicates progress made in number of bytes downloaded. User can * use this to check bytes loaded or number of bytes in the buffer. * Fires every .25 seconds, starting when load is called and ending * when all bytes are loaded or if there is a network error. Event Object is * of type mx.events.ProgressEvent.

* */ [Event("progress")] /** *

Event dispatched when FLV is loaded and ready to display. Event * object has properties state and playheadTime.

* *

Fired the first time we enter a responsive state after we * load a new flv with play() or load() API. Only fires once * for each FLV loaded.

* */ [Event("ready")] /** *

Event dispatched when video is autoresized due to * maintainAspectRatio or autoSize properties set to true. Event * Object has properties x, y, width and height.

* */ [Event("resize")] /** *

Event dispatched when video autorewinds. Event Object has properties * state and playheadTime.

* */ [Event("rewind")] /** *

Event dispatched when playback state changes. Event Object has * properties state and playheadTime.

* *

This event can be used to track when playback enters/leaves * unresponsive states (for example in the middle of connecting, * resizing or rewinding) during which times APIs play(), * pause(), stop() and seek() * will queue the requests to be executed when the player enters * a responsive state.

*/ [Event("stateChange")] /** *

VideoPlayer is an easy to use wrapper for Video, NetConnection, * NetStream, etc. that makes playing FLV easy. It supports streaming * from Flash Communication Server (FCS) and http download of FLVs.

* *

VideoPlayer extends MovieClip and wraps a Video object. It also * "extends" EventDispatcher using mixins.

* * @author copyright 2004-2005 Macromedia, Inc. */ class mx.video.VideoPlayer extends MovieClip { #include "ComponentVersion.as" // public state constants /** *

State constant. This is the state when the VideoPlayer is * constructed and when the stream is closed by a call to * close() or timed out on idle.

* *

This is a responsive state.

* * @see #state * @see #stateResponsive * @see #connected * @see #idleTimeout * @see #close() */ public static var DISCONNECTED:String = "disconnected"; /** *

State constant. FLV is loaded and play is stopped. This state * is entered when stop() is called and when the * playhead reaches the end of the stream.

* *

This is a responsive state.

* * @see #state * @see #stateResponsive * @see #stop() */ public static var STOPPED:String = "stopped"; /** *

State constant. FLV is loaded and is playing. * This state is entered when play() * is called.

* *

This is a responsive state.

* * @see #state * @see #stateResponsive * @see #play() */ public static var PLAYING:String = "playing"; /** *

State constant. FLV is loaded, but play is paused. * This state is entered when pause() is * called or when load() is called.

* *

This is a responsive state.

* * @see #state * @see #stateResponsive * @see #pause() * @see #load() */ public static var PAUSED:String = "paused"; /** *

State constant. State entered immediately after * play() or load() is called.

* *

This is a responsive state.

* * @see #state * @see #stateResponsive */ public static var BUFFERING:String = "buffering"; /** *

State constant. State entered immediately after * play() or load() is called.

* *

This is a unresponsive state.

* * @see #state * @see #stateResponsive * @see #load() * @see #play() */ public static var LOADING:String = "loading"; /** *

State constant. Stream attempted to load was unable to load * for some reason. Could be no connection to server, stream not * found, etc.

* *

This is a unresponsive state.

* * @see #state * @see #stateResponsive */ public static var CONNECTION_ERROR:String = "connectionError"; /** *

State constant. State entered during a autorewind triggered * by a stop. After rewind is complete, the state will be * STOPPED.

* *

This is a unresponsive state.

* * @see #autoRewind * @see #state * @see #stateResponsive */ public static var REWINDING:String = "rewinding"; /** *

State constant. State entered after seek() * is called.

* *

This is a unresponsive state.

* * @see #state * @see #stateResponsive * @see #seek() */ public static var SEEKING:String = "seeking"; /** *

State constant. State entered during autoresize.

* *

This is a unresponsive state.

* * @see #autoSize * @see #maintainAspectRatio * @see #state * @see #stateResponsive */ public static var RESIZING:String = "resizing"; /** *

State constant. State during execution of queued command. * There will never get a "stateChange" event notification with * this state; it is internal only.

* *

This is a unresponsive state.

* * @see #state * @see #stateResponsive */ static var EXEC_QUEUED_CMD:String = "execQueuedCmd"; // buffer states private static var BUFFER_EMPTY:String = "bufferEmpty"; private static var BUFFER_FULL:String = "bufferFull"; private static var BUFFER_FULL_SAW_PLAY_STOP:String = "bufferFullSawPlayStop"; // state private var _state:String; private var _cachedState:String; private var _bufferState:String; private var _cachedPlayheadTime:Number; private var _metadata:Object; private var _startingPlay:Boolean; private var _invalidSeekTime:Boolean; private var _invalidSeekRecovery:Boolean; private var _readyDispatched:Boolean; private var _autoResizeDone:Boolean; private var _lastUpdateTime:Number; private var _sawSeekNotify:Boolean; // Video object private var _video:Video; // INCManager private var _ncMgr:INCManager; public var ncMgrClassName:String; /** *

Set this property to the name of your custom class to * make all VideoPlayer objects created use that class as the * default INCManager implementation. The default value is * "mx.video.NCManager".

*/ public static var DEFAULT_INCMANAGER:String = "mx.video.NCManager"; // info about NetStream private var _ns:NetStream; private var _currentPos:Number; private var _atEnd:Boolean; private var _streamLength:Number; // store properties private var _autoSize:Boolean; private var _aspectRatio:Boolean; /** *

If true, then video plays immediately, if false waits for * play to be called. Set to true if stream is * loaded with call to play(), false if loaded * by call to load().

* *

Even if _autoPlay is set to false, we will start * loading the video after initialize() is called. * In the case of FCS, this means creating the stream and loading * the first frame to display (and loading more if * autoSize or aspectRatio is true). In * the case of HTTP download, we will start downloading the stream * and show the first frame.

* * @private */ private var _autoPlay:Boolean; private var _autoRewind:Boolean; private var _contentPath:String; private var _bufferTime:Number; private var _isLive:Boolean; private var _volume:Number; private var _sound:Sound; private var __visible:Boolean; private var _hiddenForResize:Boolean; private var _hiddenForResizeMetadataDelay:Number; private var _hiddenRewindPlayheadTime:Number; private var _videoWidth:Number; private var _videoHeight:Number; private var _prevVideoWidth:Number; private var _prevVideoHeight:Number; // intervals private var _updateTimeIntervalID:Number; private var _updateTimeInterval:Number; private var _updateProgressIntervalID:Number; private var _updateProgressInterval:Number; private var _idleTimeoutIntervalID:Number; private var _idleTimeoutInterval:Number; private var _autoResizeIntervalID:Number; private var _rtmpDoStopAtEndIntervalID:Number; private var _rtmpDoSeekIntervalID:Number; private var _httpDoSeekIntervalID:Number; private var _httpDoSeekCount:Number private var _finishAutoResizeIntervalID:Number; private var _delayedBufferingIntervalID:Number; private var _delayedBufferingInterval:Number // default times for intervals static var DEFAULT_UPDATE_TIME_INTERVAL:Number = 250; // .25 seconds static var DEFAULT_UPDATE_PROGRESS_INTERVAL:Number = 250; // .25 seconds static var DEFAULT_IDLE_TIMEOUT_INTERVAL:Number = 300000; // five minutes private static var AUTO_RESIZE_INTERVAL:Number = 100; // .1 seconds private static var AUTO_RESIZE_PLAYHEAD_TIMEOUT = .5; // .5 seconds private static var AUTO_RESIZE_METADATA_DELAY_MAX:Number = 5; // .5 seconds private static var FINISH_AUTO_RESIZE_INTERVAL:Number = 250; // .25 seconds private static var RTMP_DO_STOP_AT_END_INTERVAL:Number = 500; // .5 seconds private static var RTMP_DO_SEEK_INTERVAL:Number = 100; // .1 seconds private static var HTTP_DO_SEEK_INTERVAL:Number = 250; // .25 seconds private static var HTTP_DO_SEEK_MAX_COUNT:Number = 4; // 4 times * .25 seconds = 1 second private static var CLOSE_NS_INTERVAL:Number = .25; // .25 secconds private static var HTTP_DELAYED_BUFFERING_INTERVAL:Number = 100; // .1 seconds // queues up Objects describing queued commands to be run later private var _cmdQueue:Array; // values for command types for _cmdQueue static var PLAY:Number = 0; static var LOAD:Number = 1; static var PAUSE:Number = 2; static var STOP:Number = 3; static var SEEK:Number = 4; // EventDispatcher mixins public var addEventListener:Function; public var removeEventListener:Function; public var dispatchEvent:Function; public var dispatchQueue:Function; //ifdef DEBUG //private static var _debugSingleton:VideoPlayer; //endif // // public APIs // /** *

Constructor.

* * @see INCManager * @see NCManager */ public function VideoPlayer() { // add EventDispatcher mixins EventDispatcher.initialize(this); // init state variables _state = DISCONNECTED; _cachedState = _state; _bufferState = BUFFER_EMPTY; _cachedPlayheadTime = 0; _metadata = null; _startingPlay = false; _invalidSeekTime = false; _invalidSeekRecovery = false; _currentPos = 0; _atEnd = false; _cmdQueue = new Array(); _readyDispatched = false; _autoResizeDone = false; _lastUpdateTime = -1; _sawSeekNotify = false; // put off creation of INCManager until last minute to // give time to customize DEFAULT_INCMANAGER // setup intervals _updateTimeIntervalID = 0; _updateTimeInterval = DEFAULT_UPDATE_TIME_INTERVAL; _updateProgressIntervalID = 0; _updateProgressInterval = DEFAULT_UPDATE_PROGRESS_INTERVAL; _idleTimeoutIntervalID = 0; _idleTimeoutInterval = DEFAULT_IDLE_TIMEOUT_INTERVAL; _autoResizeIntervalID = 0; _rtmpDoStopAtEndIntervalID = 0; _rtmpDoSeekIntervalID = 0; _httpDoSeekIntervalID = 0; _httpDoSeekCount = 0; _finishAutoResizeIntervalID = 0; _delayedBufferingIntervalID = 0; _delayedBufferingInterval = HTTP_DELAYED_BUFFERING_INTERVAL; // init get/set properties if (_isLive == undefined) _isLive = false; if (_autoSize == undefined) _autoSize = false; if (_aspectRatio == undefined) _aspectRatio = true; if (_autoPlay == undefined) _autoPlay = true; if (_autoRewind == undefined) _autoRewind = true; if (_bufferTime == undefined) _bufferTime = 0.1; if (_volume == undefined) _volume = 100; _sound = new Sound(this); _sound.setVolume(_volume); __visible = true; _hiddenForResize = false; _hiddenForResizeMetadataDelay = 0; _contentPath = ""; //ifdef DEBUG //_debugSingleton = this; //endif } /** *

set width and height simultaneously. Since setting either * one can trigger an autoresize, this can be better than invoking * set width and set height individually.

* *

If autoSize is true then this has no effect, since the player * sets its own dimensions. If maintainAspectRatio is true and * autoSize is false, then changing width or height will trigger * an autoresize.

* * @param width * @param height * @see width * @see height */ public function setSize(w:Number, h:Number):Void { if ( (w == _video._width && h == _video._height) || _autoSize ) return; _video._width = w; _video._height = h; if (_aspectRatio) { startAutoResize(); } } /** *

set scaleX and scaleY simultaneously. Since setting either * one can trigger an autoresize, this can be better than invoking * set width and set height individually.

* *

If autoSize is true then this has no effect, since the player * sets its own dimensions. If maintainAspectRatio is true and * autoSize is false, then changing scaleX or scaleY will trigger an * autoresize.

* * @param scaleX * @param scaleY * @see scaleX * @see scaleY */ public function setScale(xs:Number, ys:Number) { if ( (xs == _video._xscale && ys == _video._yscale) || _autoSize ) return; _video._xscale = xs; _video._yscale = ys; if (_aspectRatio) { startAutoResize(); } } /** *

Causes the video to play. Can be called while the video is * paused, stopped, or while the video is already playing. Call this * method with no arguments to play an already loaded video or pass * in a url to load a new stream.

* *

If player is in an unresponsive state, queues the request.

* *

Throws an exception if called with no args and no stream * is connected. Use "stateChange" event and * connected property to determine when it is * safe to call this method.

* * @param url Pass in a url string if you want to load and play a * new FLV. If you have already loaded an FLV and want to continue * playing it, pass in null. * @param isLive Pass in true if streaming a live feed from FCS. * Defaults to false. * @param totalTime Pass in length of FLV. Pass in 0 or null or * undefined to automatically detect length from metadata, server * or xml. If INCManager.streamLength is not 0 or * null or undefined when ncConnected is called, then * that value will trump this one in any case. Default is * undefined. * @see #connected * @see #stateResponsive * @see #load() */ public function play(url:String, isLive:Boolean, totalTime:Number):Void { //ifdef DEBUG //debugTrace("play(" + url + ")"); //endif // if new url passed, ask the INCManager to reconnect for us if (url != null && url != undefined) { if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(PLAY, url, isLive, totalTime); return; } else { execQueuedCmds(); } _autoPlay = true; _load(url, isLive, totalTime); // playing will start automatically once stream is setup, so return. return; } if (!isXnOK()) { if ( _state == CONNECTION_ERROR || _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { throw new VideoError(VideoError.NO_CONNECTION); } else { //ifdef DEBUG //debugTrace("RECONNECTING!!!"); //endif flushQueuedCmds(); queueCmd(PLAY); setState(LOADING); _cachedState = LOADING; _ncMgr.reconnect(); // playing will start automatically once stream is setup, so return. return; } } else if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(PLAY); return; } else { execQueuedCmds(); } // recreate stream if necessary (this will never happen with // http download, just rtmp) if (_ns == null || _ns == undefined) { _createStream(); _video.attachVideo(_ns); this.attachAudio(_ns); } switch (_state) { case BUFFERING: if (_ncMgr.isRTMP()) { _play(0); if (_atEnd) { _atEnd = false; _currentPos = 0; setState(REWINDING); } else if (_currentPos > 0) { _seek(_currentPos); _currentPos = 0; } } // no break case PLAYING: // already playing return; case STOPPED: if (_ncMgr.isRTMP()) { if (_isLive) { _play(-1); setState(BUFFERING); } else { _play(0); if (_atEnd) { _atEnd = false; _currentPos = 0; _state = BUFFERING; setState(REWINDING); } else if (_currentPos > 0) { _seek(_currentPos); _currentPos = 0; setState(BUFFERING); } else { setState(BUFFERING); } } } else { _pause(false); if (_atEnd) { _atEnd = false; _seek(0); _state = BUFFERING; setState(REWINDING); } else { if (_bufferState == BUFFER_EMPTY) { setState(BUFFERING); } else { setState(PLAYING); } } } break; case PAUSED: _pause(false); if (!_ncMgr.isRTMP()) { if (_bufferState == BUFFER_EMPTY) { setState(BUFFERING); } else { setState(PLAYING); } } else { setState(BUFFERING); } break; } // switch } /** *

Similar to play, but causes the FLV to be loaded without * playing. Autoresizing will occur if appropriate and the first * frame of FLV will be shown (except for maybe not in the live case). * After initial load and autoresize, state will be PAUSED.

* *

Takes same arguments as play(), but unlike that * method it is never acceptable to call load() with * no url. If you do, an Error will be thrown.

* *

If player is in an unresponsive state, queues the request.

* * @param url Pass in a url string for the FLV you want to load. * @param isLive Pass in true if streaming a live feed from FCS. * Defaults to false. * @param totalTime Pass in length of FLV. Pass in 0 or null or * undefined to automatically detect length from metadata, server * or xml. If INCManager.streamLength is not 0 or * null or undefined when ncConnected is called, then * that value will trump this one in any case. Default is * undefined. * @see #connected * @see #play() */ public function load(url:String, isLive:Boolean, totalTime:Number):Void { if (url == null || url == undefined) { throw new Error("null url sent to VideoPlayer.load"); } //ifdef DEBUG //debugTrace("load(" + url + ")"); //endif if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(LOAD, url, isLive, totalTime); return; } else { execQueuedCmds(); } _autoPlay = false; _load(url, isLive, totalTime); } /* * does loading work for play and load */ private function _load(url:String, isLive:Boolean, totalTime:Number):Void { //ifdef DEBUG //debugTrace("_load(" + url + ", " + isLive + ", " + totalTime + ")"); //endif _prevVideoWidth = this.videoWidth; if (_prevVideoWidth == undefined) { _prevVideoWidth = _video.width; if (_prevVideoWidth == undefined) _prevVideoWidth = 0; } _prevVideoHeight = this.videoHeight; if (_prevVideoHeight == undefined) { _prevVideoHeight = _video.height; if (_prevVideoHeight == undefined) _prevVideoHeight = 0; } // reset state _autoResizeDone = false; _cachedPlayheadTime = 0; _bufferState = BUFFER_EMPTY; _metadata = null; _startingPlay = false; _invalidSeekTime = false; _invalidSeekRecovery = false; _isLive = (isLive == undefined) ? false : isLive; _contentPath = url; _currentPos = 0; _streamLength = totalTime; _atEnd = false; _videoWidth = undefined; _videoHeight = undefined; _readyDispatched = false; _lastUpdateTime = -1; _sawSeekNotify = false; // must stop ALL intervals here clearInterval(_updateTimeIntervalID); _updateTimeIntervalID = 0; clearInterval(_updateProgressIntervalID); _updateProgressIntervalID = 0; clearInterval(_idleTimeoutIntervalID); _idleTimeoutIntervalID = 0; clearInterval(_autoResizeIntervalID); _autoResizeIntervalID = 0; clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = 0; clearInterval(_rtmpDoSeekIntervalID); _rtmpDoSeekIntervalID = 0; clearInterval(_httpDoSeekIntervalID); _httpDoSeekIntervalID = 0; clearInterval(_finishAutoResizeIntervalID); _finishAutoResizeIntervalID = 0; clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = 0; // close netstream closeNS(false); // if returns false, wait for a "connected" message and // then do these things if (_ncMgr == null || _ncMgr == undefined) { createINCManager(); } var instantConnect:Boolean = _ncMgr.connectToURL(_contentPath); setState(LOADING); _cachedState = LOADING; if (instantConnect) { _createStream(); _setUpStream(); } if (!_ncMgr.isRTMP()) { clearInterval(_updateProgressIntervalID); _updateProgressIntervalID = setInterval(this, "doUpdateProgress", _updateProgressInterval); } } /** *

Pauses video playback. If video is paused or stopped, has * no effect. To start playback again, call play(). * Takes no parameters

* *

If player is in an unresponsive state, queues the request.

* *

Throws an exception if called when no stream is * connected. Use "stateChange" event and * connected property to determine when it is * safe to call this method.

* *

If state is already stopped, pause is does nothing and state * remains stopped.

* * @see #connected * @see #stateResponsive * @see #play() */ public function pause():Void { //ifdef DEBUG //debugTrace("pause()"); //endif if (!isXnOK()) { if ( _state == CONNECTION_ERROR || _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { throw new VideoError(VideoError.NO_CONNECTION); } else { return; } } else if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(PAUSE); return; } else { execQueuedCmds(); } if (_state == PAUSED || _state == STOPPED || _ns == null || _ns == undefined) return; _pause(true); setState(PAUSED); } /** *

Stops video playback. If autoRewind is set to * true, rewinds to first frame. If video is already * stopped, has no effect. To start playback again, call * play(). Takes no parameters

* *

If player is in an unresponsive state, queues the request.

* *

Throws an exception if called when no stream is * connected. Use "stateChange" event and * connected property to determine when it is * safe to call this method.

* * @see #connected * @see #stateResponsive * @see #autoRewind * @see #play() */ public function stop():Void { //ifdef DEBUG //debugTrace("stop()"); //endif if (!isXnOK()) { if ( _state == CONNECTION_ERROR || _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { throw new VideoError(VideoError.NO_CONNECTION); } else { return; } } else if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(STOP); return; } else { execQueuedCmds(); } if (_state == STOPPED || _ns == null || _ns == undefined) return; if (_ncMgr.isRTMP()) { if (_autoRewind && !_isLive) { _currentPos = 0; _play(0, 0); _state = STOPPED; setState(REWINDING); } else { closeNS(true); setState(STOPPED); } } else { _pause(true); if (_autoRewind) { _seek(0); _state = STOPPED; setState(REWINDING); } else { setState(STOPPED); } } } /** *

Seeks to given second in video. If video is playing, * continues playing from that point. If video is paused, seek to * that point and remain paused. If video is stopped, seek to * that point and enters paused state. Has no effect with live * streams.

* *

If time is less than 0 or NaN, throws exeption. If time * is past the end of the stream, or past the amount of file * downloaded so far, then will attempt seek and when fails * will recover.

* *

If player is in an unresponsive state, queues the request.

* *

Throws an exception if called when no stream is * connected. Use "stateChange" event and * connected property to determine when it is * safe to call this method.

* * @param time seconds * @throws VideoError if time is < 0 * @see #connected * @see #stateResponsive */ public function seek(time:Number):Void { //ifdef DEBUG //debugTrace("seek:"+time); //endif // we do not allow more seeks until we are out of an invalid seek time state if (_invalidSeekTime) return; if (isNaN(time) || time < 0) throw new VideoError(VideoError.INVALID_SEEK); if (!isXnOK()) { if ( _state == CONNECTION_ERROR || _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { throw new VideoError(VideoError.NO_CONNECTION); } else { //ifdef DEBUG //debugTrace("RECONNECTING!!!"); //endif flushQueuedCmds(); queueCmd(SEEK, null, false, time); setState(LOADING); _cachedState = LOADING; _ncMgr.reconnect(); // playing will start automatically once stream is setup, so return. return; } } else if (_state == EXEC_QUEUED_CMD) { _state = _cachedState; } else if (!stateResponsive) { queueCmd(SEEK, null, false, time); return; } else { execQueuedCmds(); } // recreate stream if necessary (this will never happen with // http download, just rtmp) if (_ns == null || _ns == undefined) { _createStream(); _video.attachVideo(_ns); this.attachAudio(_ns); } if (_atEnd && time < playheadTime) { _atEnd = false; } switch (_state) { case PLAYING: _state = BUFFERING; // no break; case BUFFERING: case PAUSED: _seek(time); setState(SEEKING); break; case STOPPED: if (_ncMgr.isRTMP()) { _play(0); _pause(true); } _seek(time); _state = PAUSED; setState(SEEKING); break; } } /** *

Forces close of video stream and FCS connection. Triggers * "close" event. Typically calling this directly is not necessary * because the idle timeout functionality will take care of this.

* * @see idleTimeout */ public function close():Void { //ifdef DEBUG //debugTrace("close()"); //endif closeNS(true); // never makes sense to close an http NetConnection, it doesn't really maintain // any kind of network connection! if (_ncMgr != null && _ncMgr != undefined && _ncMgr.isRTMP()) { _ncMgr.close(); } setState(DISCONNECTED); dispatchEvent({type:"close", state:_state, playheadTime:playheadTime}); } // // public getters, setters // public function get x():Number { return this._x; } public function set x(xpos:Number) { this._x = xpos; } public function get y():Number { return this._y; } public function set y(ypos:Number) { this._y = ypos; } /** * 100 is standard scale * * @see #setScale() */ function get scaleX():Number { return _video._xscale; } function set scaleX(xs:Number):Void { setScale(xs, this.scaleY); } /** * 100 is standard scale * * @see #setScale() */ function get scaleY():Number { return _video._yscale; } function set scaleY(ys:Number):Void { setScale(this.scaleX, ys); } /** *

Width of video instance. Not same as Video.width, that is videoWidth.

* * @see #setSize() * @see #videoWidth */ public function get width():Number { return _video._width; } public function set width(w:Number):Void { setSize(w, _video._height); } /** *

Height of video. Not same as Video.height, that is videoHeight.

* * @see #setSize() * @see #videoHeight */ public function get height():Number { return _video._height; } public function set height(h:Number):Void { setSize(_video._width, h); } /** *

Source width of loaded FLV file. Read only. Returns * undefined if no information available yet.

* * @see #width */ public function get videoWidth() { if (_readyDispatched) { _videoWidth = _video.width; } return _videoWidth; } /** *

Source height of loaded FLV file. Read only. Returns * undefined if no information available yet.

* * @see #height */ public function get videoHeight() { if (_readyDispatched) { _videoHeight = _video.height; } return _videoHeight; } /** *

Use this instead of _visible because we * sometimes do internal visibility management when doing an * autoresize.

*/ public function get visible():Boolean { if (!_hiddenForResize) { __visible = _visible; } return __visible; } public function set visible(v:Boolean):Void { __visible = v; if (!_hiddenForResize) { _visible = __visible; } } /** *

Determines whether the instance is automatically resized to * the source dimensions. If this is set from false to true after * an FLV has been loaded, an automatic resize will start * immediately.

* */ public function get autoSize():Boolean { return _autoSize; } public function set autoSize(flag:Boolean):Void { if (_autoSize != flag) { _autoSize = flag; if (_autoSize) { startAutoResize(); } } } /** *

Determines whether video aspect ratio is maintained. If * this is set from false to true and autoSize * * @see #autoSize */ public function get maintainAspectRatio():Boolean { return _aspectRatio; } public function set maintainAspectRatio(flag:Boolean):Void { if (_aspectRatio != flag) { _aspectRatio = flag; if (_aspectRatio && !_autoSize) { startAutoResize(); } } } /** *

Determines whether the FLV is rewound to the first frame * when play stops, either by calling stop() or by * reaching the end of the stream. Meaningless for live streams.

* */ public function get autoRewind():Boolean { return _autoRewind; } public function set autoRewind(flag:Boolean):Void { _autoRewind = flag; } /** *

The current playhead time in seconds. Setting does a seek * and has all the restrictions of a seek.

* *

The event "playheadUpdate" is dispatched when the playhead * time changes, including every .25 seconds while the FLV is * playing.

* * @return The playhead position, measured in seconds since the start. Will return a fractional value. * @see #seek() */ public function get playheadTime():Number { var nowTime:Number = (_ns == null || _ns == undefined) ? _currentPos : _ns.time; if (_metadata.audiodelay != undefined) { nowTime -= _metadata.audiodelay; if (nowTime < 0) nowTime = 0; } return nowTime; } public function set playheadTime(position:Number):Void { seek(position); } /** *

url of currently loaded (or loading) stream. Will be url * last sent to play() or load(), null * if no stream is loaded.

* */ public function get url():String { return _contentPath; } /** *

Volume control in range from 0 to 100.

* * @return The most recent volume setting * @see #transform */ public function get volume():Number { return _volume; } public function set volume(aVol:Number):Void { _volume = aVol; if (!_hiddenForResize) { _sound.setVolume(_volume); } } /** *

Provides direct access to the * Sound.setTransform() and * Sound.getTransform() APIs. to expose more sound * control. Must set property for changes to take effect, get * property just to get a copy of the current settings to tweak.

* * @see #volume */ public function get transform():Object { return _sound.getTransform(); } public function set transform(s:Object):Void { _sound.setTransform(s); } /** * True if stream is RTMP download (streaming from Flash * Communication Server), read only. */ public function get isRTMP():Boolean { if (_ncMgr == null || _ncMgr == undefined) return undefined; return _ncMgr.isRTMP(); } /** *

True if stream is live, read only. isLive only makes sense when * streaming from FVSS or FCS, value is ignored when doing http * download.

*/ public function get isLive():Boolean { return _isLive; } /** * Get state. Read only. Set with load, * play(), stop(), * pause() and seek(). */ public function get state():String { return _state; } /** * Read only. Gets whether state is responsive. If state is * unresponsive, calls to APIs play(), * load(), stop(), * pause() and seek() will queue the * requests for later, when the state changes to a responsive * one. * * @see #connected * @see #DISCONNECTED * @see #STOPPED * @see #PLAYING * @see #PAUSED * @see #LOADING * @see #RESIZING * @see #CONNECTION_ERROR * @see #REWINDING */ public function get stateResponsive():Boolean { switch (_state) { case DISCONNECTED: case STOPPED: case PLAYING: case PAUSED: case BUFFERING: return true; default: return false; } } /** *

property bytesLoaded, read only. Returns -1 when there * is no stream, when the stream is FCS or if the information * is not yet available. Return value only useful for HTTP * download.

* */ public function get bytesLoaded():Number { if (_ns == null || _ns == undefined || _ncMgr.isRTMP()) return -1; return _ns.bytesLoaded; } /** *

property bytesTotal, read only. Returns -1 when there * is no stream, when the stream is FCS or if the information * is not yet available. Return value only useful for HTTP * download.

* */ public function get bytesTotal():Number { if (_ns == null || _ns == undefined || _ncMgr.isRTMP()) return -1; return _ns.bytesTotal; } /** *

property totalTime. read only. 0 or null or undefined * means that property was not passed into play() or * load() and was unable to detect automatically, or * have not yet.

* * @return The total running time of the FLV in seconds */ public function get totalTime():Number { return _streamLength; } /** *

Sets number of seconds to buffer in memory before playing * back stream. For slow connections streaming over rtmp, it is * important to increase this from the default. Default is * 0.1

*/ public function get bufferTime():Number { return _bufferTime; } public function set bufferTime(aTime:Number):Void { _bufferTime = aTime; if (_ns != null && _ns != undefined) { _ns.setBufferTime(_bufferTime); } } /** *

Property idleTimeout, which is amount of time in * milliseconds before connection is idle (playing is paused * or stopped) before connection to the FCS server is * terminated. Has no effect to HTTP download of FLV.

* *

If set when stream already idle, restarts idle timeout with * new value.

*/ public function get idleTimeout():Number { return _idleTimeoutInterval; } public function set idleTimeout(aTime:Number):Void { _idleTimeoutInterval = aTime; if (_idleTimeoutIntervalID > 0) { clearInterval(_idleTimeoutIntervalID); _idleTimeoutIntervalID = setInterval(this, "doIdleTimeout", _idleTimeoutInterval); } } /** *

Property playheadUpdateInterval, which is amount of time * in milliseconds between each "playheadUpdate" event.

* *

If set when stream is playing, will restart interval.

*/ public function get playheadUpdateInterval():Number { return _updateTimeInterval; } public function set playheadUpdateInterval(aTime:Number):Void { _updateTimeInterval = aTime; if (_updateTimeIntervalID > 0) { clearInterval(_updateTimeIntervalID); _updateTimeIntervalID = setInterval(this, "doUpdateTime", _updateTimeInterval); } } /** *

Property progressInterval, which is amount of time * in milliseconds between each "progress" event.

* *

If set when stream is playing, will restart interval.

*/ public function get progressInterval():Number { return _updateProgressInterval; } public function set progressInterval(aTime:Number):Void { _updateProgressInterval = aTime; if (_updateProgressIntervalID > 0) { clearInterval(_updateProgressIntervalID); _updateProgressIntervalID = setInterval(this, "doUpdateProgress", _updateProgressInterval); } } /** *

Access to instance of the class implementing * INCManager. Read only.

* *

One use case for this is that a custom * INCManager implementation may require custom * initialization.

*/ public function get ncMgr():INCManager { if (_ncMgr == null || _ncMgr == undefined) { createINCManager(); } return _ncMgr; } /** *

Read only. Object received by call to onMetaData callback. * null if onMetaData callback has not been called since the last * load or play call. Always null with FLVs with no onMetaData * packet.

* * @see #load() * @see #play() */ public function get metadata() { return _metadata; }; // // public callbacks, not really APIs // /** *

Called on interval determined by * playheadUpdateInterval to send "playheadUpdate" * events. Events only sent when playhead is moving, sent every * .25 seconds by default.

* * @private */ public function doUpdateTime():Void { //ifdef DEBUG ////debugTrace("doUpdateTime()"); //endif var theTime:Number = playheadTime; // stop interval if we are stopped or paused switch (_state) { case STOPPED: case PAUSED: case DISCONNECTED: case CONNECTION_ERROR: clearInterval(_updateTimeIntervalID); _updateTimeIntervalID = 0; break; } if (_lastUpdateTime != theTime) { dispatchEvent({type:"playheadUpdate", state:_state, playheadTime:theTime}); _lastUpdateTime = theTime; } } /** *

Called at interval determined by * progressInterval to send "progress" events. * Object dispatch starts when _load is called, ends * when all bytes downloaded or a network error of some kind * occurs, dispatched every .25 seconds by default.

* * @private */ public function doUpdateProgress():Void { if (_ns == null || _ns == undefined) return; //ifdef DEBUG ////debugTrace("doUpdateProgress()"); ////debugTrace("_ns.bytesLoaded = " + _ns.bytesLoaded); ////debugTrace("_ns.bytesTotal = " + _ns.bytesTotal); //endif if (_ns.bytesTotal >= 0 && _ns.bytesTotal >= 0) { dispatchEvent({type:"progress", bytesLoaded:_ns.bytesLoaded, bytesTotal:_ns.bytesTotal}); } if ( _state == DISCONNECTED || _state == CONNECTION_ERROR || _ns.bytesLoaded == _ns.bytesTotal ) { clearInterval(_updateProgressIntervalID); _updateProgressIntervalID = 0; } } /** *

NetStream.onStatus callback for rtmp. Handles * automatic resizing, autorewind and buffering messaging.

* * @private */ public function rtmpOnStatus(info:Object):Void { //ifdef DEBUG //debugTrace("rtmpOnStatus:"+info.code); //debugTrace("_state == " + _state); //debugTrace("_cachedState == " + _cachedState); //debugTrace("_bufferState == " + _bufferState); //debugTrace("_cachedPlayheadTime == " + _cachedPlayheadTime); //debugTrace("playheadTime == " + playheadTime); //endif if (_state == CONNECTION_ERROR) { // always do nothing return; } switch (info.code) { case "NetStream.Play.Stop": if (_startingPlay) return; switch (_state) { case RESIZING: if (_hiddenForResize) finishAutoResize(); break; case LOADING: case STOPPED: case PAUSED: // yes we are stopped, we already know this break; default: if (_bufferState == BUFFER_EMPTY || _bufferTime <= 0.1) { // if we did a seek toward the end of the file so that // there is less file left to show than we have // buffer, than we will get a NetStream.Play.Stop when // the buffer loads rest of the file, but never get // a NetStream.Buffer.Full, since it won't fill, so // we check if we are done on a timer _cachedPlayheadTime = playheadTime; clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = setInterval(this, "rtmpDoStopAtEnd", RTMP_DO_STOP_AT_END_INTERVAL); } else if (_bufferState == BUFFER_FULL) { _bufferState = BUFFER_FULL_SAW_PLAY_STOP; } else { //ifdef DEBUG //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); //endif } break; } // switch (_state) break; case "NetStream.Buffer.Empty": switch (_bufferState) { case BUFFER_FULL_SAW_PLAY_STOP: rtmpDoStopAtEnd(true); break; case BUFFER_FULL: if (_state == PLAYING) { setState(BUFFERING); } break; default: //ifdef DEBUG //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); //endif break; } _bufferState = BUFFER_EMPTY; break; case "NetStream.Buffer.Flush": case "NetStream.Buffer.Full": if (_sawSeekNotify && _state == SEEKING) { _bufferState = BUFFER_EMPTY; setStateFromCachedState(); doUpdateTime(); } switch (_bufferState) { case BUFFER_EMPTY: if ( !_hiddenForResize ) { if ((_state == LOADING && _cachedState == PLAYING) || _state == BUFFERING) { setState(PLAYING); } else if (_cachedState == BUFFERING) { _cachedState = PLAYING; } } _bufferState = BUFFER_FULL; break; default: //ifdef DEBUG //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); //endif break; } // switch (_bufferState) break; case "NetStream.Pause.Notify": if (_state == RESIZING && _hiddenForResize) { finishAutoResize(); } break; case "NetStream.Play.Start": clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = 0; _bufferState = BUFFER_EMPTY; if (_startingPlay) { _startingPlay = false; _cachedPlayheadTime = playheadTime; } else if (_state == PLAYING) { setState(BUFFERING); } break; case "NetStream.Play.Reset": clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = 0; if (_state == REWINDING) { clearInterval(_rtmpDoSeekIntervalID); _rtmpDoSeekIntervalID = 0; if (playheadTime == 0 || playheadTime < _cachedPlayheadTime) { setStateFromCachedState(); } else { _cachedPlayheadTime = playheadTime; _rtmpDoSeekIntervalID = setInterval(this, "rtmpDoSeek", RTMP_DO_SEEK_INTERVAL); } } break; case "NetStream.Seek.Notify": if (playheadTime != _cachedPlayheadTime) { setStateFromCachedState(); doUpdateTime(); } else { _sawSeekNotify = true; if (_rtmpDoSeekIntervalID == 0) { _rtmpDoSeekIntervalID = setInterval(this, "rtmpDoSeek", RTMP_DO_SEEK_INTERVAL); } } break; case "Netstream.Play.UnpublishNotify": break; case "Netstream.Play.PublishNotify": break; case "NetStream.Play.StreamNotFound": if (!_ncMgr.connectAgain()) { setState(CONNECTION_ERROR); } break; case "NetStream.Play.Failed": case "NetStream.Failed": setState(CONNECTION_ERROR); break; } // switch (info.code) } /** *

NetStream.onStatus callback for http. Handles * autorewind.

* * @private */ public function httpOnStatus(info:Object):Void { //ifdef DEBUG //debugTrace("httpOnStatus:"+info.code); //debugTrace("_state == " + _state); //debugTrace("playheadTime == " + playheadTime); //debugTrace("_bufferState = " + _bufferState); //endif switch (info.code) { case "NetStream.Play.Stop": clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = 0; if (_invalidSeekTime) { _invalidSeekTime = false; _invalidSeekRecovery = true; setState(_cachedState); seek(playheadTime); } else { switch (_state) { case PLAYING: case BUFFERING: case SEEKING: httpDoStopAtEnd(); break; } } break; case "NetStream.Seek.InvalidTime": if (_invalidSeekRecovery) { _invalidSeekTime = false; _invalidSeekRecovery = false; setState(_cachedState); seek(0); } else { _invalidSeekTime = true; } break; case "NetStream.Buffer.Empty": _bufferState = BUFFER_EMPTY; if (_state == PLAYING) { clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = setInterval(this, "doDelayedBuffering", _delayedBufferingInterval); } break; case "NetStream.Buffer.Full": case "NetStream.Buffer.Flush": clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = 0; _bufferState = BUFFER_FULL; if ( !_hiddenForResize ) { if ((_state == LOADING && _cachedState == PLAYING) || _state == BUFFERING) { setState(PLAYING); } else if (_cachedState == BUFFERING) { _cachedState = PLAYING; } } break; case "NetStream.Seek.Notify": _invalidSeekRecovery = false; switch (_state) { case SEEKING: case REWINDING: if (_httpDoSeekIntervalID == 0) { _httpDoSeekCount = 0; _httpDoSeekIntervalID = setInterval(this, "httpDoSeek", HTTP_DO_SEEK_INTERVAL); } break; } // switch (_state) break; case "NetStream.Play.StreamNotFound": setState(CONNECTION_ERROR); break; } // switch (info.code) } /** *

Called by INCManager after when connection complete or * failed after call to INCManager.connectToURL. * If connection failed, set INCManager.nc = null * before calling.

* * @see #ncReconnected() * @see INCManager#connectToURL * @see NCManager#connectToURL */ public function ncConnected():Void { //ifdef DEBUG //debugTrace("ncConnected()"); //endif if ( _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { setState(CONNECTION_ERROR); } else { _createStream(); _setUpStream(); } } /** *

Called by INCManager after when reconnection complete or * failed after call to INCManager.reconnect. If * connection failed, set INCManager.nc = null * before calling.

* * @see #ncConnected() * @see INCManager#reconnect * @see NCManager#reconnect */ public function ncReconnected():Void { //ifdef DEBUG //debugTrace("reconnected called!"); //endif if ( _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined ) { setState(CONNECTION_ERROR); } else { _ns = null; _state = STOPPED; execQueuedCmds(); } } /** * handles NetStream.onMetaData callback * * @private */ public function onMetaData(info:Object):Void { if (_metadata != null) return; _metadata = info; if ( _streamLength == undefined || _streamLength == null || _streamLength <= 0 ) { _streamLength = info.duration; } if (isNaN(_videoWidth) || _videoWidth <= 0) _videoWidth = info.width; if (isNaN(_videoHeight) || _videoHeight <= 0) _videoHeight = info.height; dispatchEvent({type:"metadataReceived", info:info}); } /** * handles NetStream.onCuePoint callback * * @private */ public function onCuePoint(info:Object):Void { if (!_hiddenForResize || (!isNaN(_hiddenRewindPlayheadTime) && playheadTime < _hiddenRewindPlayheadTime)) { dispatchEvent({type:"cuePoint", info:info}); } } // // private functions // /** * sets state, dispatches event, execs queued commands. Always try to call * this AFTER you do your work, because the state might change again after * you call this if you set it to a responsive state becasue of the call * to exec queued commands. If you set this to a responsive state and * then do more state based logic, check _state to make sure it did not * change out from under you. * * @private */ private function setState(s:String):Void { if (s == _state) return; _hiddenRewindPlayheadTime = undefined; _cachedState = _state; _cachedPlayheadTime = playheadTime; _state = s; var newState:String = _state; //ifdef DEBUG //debugTrace("state = " + newState); //debugTrace("_cachedState == " + _cachedState); ////debugTrace("_cachedPlayheadTime == " + _cachedPlayheadTime); //endif dispatchEvent({type:"stateChange", state:newState, playheadTime:playheadTime}); if (!_readyDispatched) { switch (newState) { case STOPPED: case PLAYING: case PAUSED: case BUFFERING: _readyDispatched = true; dispatchEvent({type:"ready", state:newState, playheadTime:playheadTime}); break; } // switch } switch (_cachedState) { case REWINDING: dispatchEvent({type:"rewind", state:newState, playheadTime:playheadTime}); if (_ncMgr.isRTMP() && newState == STOPPED) { closeNS(); } break; } // switch switch (newState) { case STOPPED: case PAUSED: if (_ncMgr.isRTMP() && _idleTimeoutIntervalID == 0) { _idleTimeoutIntervalID = setInterval(this, "doIdleTimeout", _idleTimeoutInterval); } break; case SEEKING: case REWINDING: _bufferState = BUFFER_EMPTY; // no break case PLAYING: case BUFFERING: if (_updateTimeIntervalID == 0) { _updateTimeIntervalID = setInterval(this, "doUpdateTime", _updateTimeInterval); } // no break case LOADING: case RESIZING: clearInterval(_idleTimeoutIntervalID); _idleTimeoutIntervalID = 0; break; } // switch execQueuedCmds(); } /** * Sets state to _cachedState if the _cachedState is PLAYING, * PAUSED or BUFFERING, otherwise sets state to STOPPED. * * @private */ private function setStateFromCachedState():Void { switch (_cachedState) { case PLAYING: case PAUSED: setState(_cachedState); break; case BUFFERING: if (_bufferState == BUFFER_EMPTY) { setState(BUFFERING); } else { setState(_cachedState); } break; default: setState(STOPPED); break; } } /** * creates our implementatino of the INCManager. * We put this off until we need to do it to give time for the * user to customize the DEFAULT_INCMANAGER * static variable. * * @private */ private function createINCManager():Void { if (ncMgrClassName == null || ncMgrClassName == undefined) { ncMgrClassName = DEFAULT_INCMANAGER; } var ncMgrConstructor:Function = eval( (ncMgrClassName) ); _ncMgr = new ncMgrConstructor; _ncMgr.setVideoPlayer(this); } /** *

ONLY CALL THIS WITH RTMP STREAMING

* *

Has the logic for what to do when we decide we have come to * a stop by coming to the end of an rtmp stream. There are a few * different ways we decide this has happened, and we sometimes * even set an interval that calls this function repeatedly to * check if the time is still changing, which is why it has its * own special function.

* * @private */ private function rtmpDoStopAtEnd(force:Boolean):Void { //ifdef DEBUG //debugTrace("rtmpDoStopAtEnd()"); //endif // check if we really want to stop if this was triggered on an // interval. If we are running this on an interval (see // rtmpOnStatus) we do a stop when the playhead hasn't moved // since last time we checked, we check every .25 seconds. if (_rtmpDoStopAtEndIntervalID > 0) { switch (_state) { case DISCONNECTED: case CONNECTION_ERROR: clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = 0; return; } if (force || _cachedPlayheadTime == playheadTime) { clearInterval(_rtmpDoStopAtEndIntervalID); _rtmpDoStopAtEndIntervalID = 0; } else { _cachedPlayheadTime = playheadTime; return; } } _bufferState = BUFFER_EMPTY; _atEnd = true; // all this triggers callbacks, so need to keep checking if // _state == STOPPED--if no longer, then we bail setState(STOPPED); if (_state != STOPPED) return; doUpdateTime(); if (_state != STOPPED) return; dispatchEvent({type:"complete", state:_state, playheadTime:playheadTime}); if (_state != STOPPED) return; if (_autoRewind && !_isLive && playheadTime != 0) { _atEnd = false; _currentPos = 0; _play(0, 0); setState(REWINDING); } else { closeNS(); } } /** *

ONLY CALL THIS WITH RTMP STREAMING

* *

Wait until time goes back to zero to leave rewinding state.

* * @private */ private function rtmpDoSeek():Void { //ifdef DEBUG //debugTrace("rtmpDoSeek()"); //endif if (_state != REWINDING && _state != SEEKING) { clearInterval(_rtmpDoSeekIntervalID); _rtmpDoSeekIntervalID = 0; _sawSeekNotify = false; } else if (playheadTime != _cachedPlayheadTime) { clearInterval(_rtmpDoSeekIntervalID); _rtmpDoSeekIntervalID = 0; _sawSeekNotify = false; setStateFromCachedState(); doUpdateTime(); } } /** *

ONLY CALL THIS WITH HTTP PROGRESSIVE DOWNLOAD

* *

Call this when playing stops by hitting the end.

* * @private */ private function httpDoStopAtEnd():Void { //ifdef DEBUG //debugTrace("httpDoStopAtEnd()"); //endif _atEnd = true; if ( _streamLength == undefined || _streamLength == null || _streamLength <= 0 ) { _streamLength = _ns.time; } _pause(true); setState(STOPPED); if (_state != STOPPED) return; doUpdateTime(); if (_state != STOPPED) return; dispatchEvent({type:"complete", state:_state, playheadTime:playheadTime}); if (_state != STOPPED) return; if (_autoRewind) { _atEnd = false; _pause(true); _seek(0); setState(REWINDING); } } /** *

ONLY CALL THIS WITH HTTP PROGRESSIVE DOWNLOAD

* *

If we get an onStatus callback indicating a seek is over, * but the playheadTime has not updated yet, then we wait on a * timer before moving forward.

* * @private */ private function httpDoSeek():Void { //ifdef DEBUG //debugTrace("httpDoSeek()"); //debugTrace("playheadTime = " + playheadTime); //debugTrace("_cachedPlayheadTime = " + _cachedPlayheadTime); //endif var seekState:Boolean = (_state == REWINDING || _state == SEEKING); // if seeking or rewinding, then need to wait for playhead time to // change or for timeout if ( seekState && _httpDoSeekCount < HTTP_DO_SEEK_MAX_COUNT && (_cachedPlayheadTime == playheadTime || _invalidSeekTime) ) { _httpDoSeekCount++; return; } // reset _httpDoSeekCount = 0; clearInterval(_httpDoSeekIntervalID); _httpDoSeekIntervalID = 0; // only do the rest if were seeking or rewinding to start with if (!seekState) return; setStateFromCachedState(); if (_invalidSeekTime) { _invalidSeekTime = false; _invalidSeekRecovery = true; seek(playheadTime); } else { doUpdateTime(); } } /** *

Wrapper for NetStream.close(). Never call * NetStream.close() directly, always call this * method because it does some other housekeeping.

* * @private */ private function closeNS(updateCurrentPos:Boolean):Void { //ifdef DEBUG //debugTrace("closeNS()"); //endif if (_ns != null && _ns != undefined) { if (updateCurrentPos) { clearInterval(_updateTimeIntervalID); _updateTimeIntervalID = 0; doUpdateTime(); _currentPos = _ns.time; } delete _ns.onStatus; _ns.onStatus = null; _ns.close(); _ns = null; } } /** *

We do a brief timer before entering BUFFERING state to avoid * quick switches from BUFFERING to PLAYING and back.

* * @private */ private function doDelayedBuffering():Void { //ifdef DEBUG //debugTrace("doDelayedBuffering()"); //endif switch (_state) { case LOADING: case RESIZING: // if loading or resizing, still at beginning so keep whirring, might go into buffering state break; case PLAYING: // still in that playing state, let's go to buffering clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = 0; setState(BUFFERING); break; default: // any other state, bail and kill timer clearInterval(_delayedBufferingIntervalID); _delayedBufferingIntervalID = 0; break; } } /** * Wrapper for NetStream.pause(). Never call * NetStream.pause() directly, always call this * method because it does some other housekeeping. * * @private */ private function _pause(doPause:Boolean):Void { //ifdef DEBUG //debugTrace("_pause(" + doPause + ")"); //endif _ns.pause(doPause); } /** * Wrapper for NetStream.play(). Never call * NetStream.play() directly, always call this * method because it does some other housekeeping. * * @private */ private function _play():Void { //ifdef DEBUG //var debugString:String = "_play(" //if (arguments.length > 0) { // debugString += arguments[0]; // if (arguments.length > 1) { // debugString += ", " + arguments[1]; // } //} //debugString += ")"; //debugTrace(debugString); //debugTrace("_ncMgr.getStreamName() = " + _ncMgr.getStreamName()); //endif _startingPlay = true; switch (arguments.length) { case 0: _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : 0, -1); break; case 1: _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : arguments[0], -1); break; case 2: _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : arguments[0], arguments[1]); break; default: throw new Error("bad args to _play"); } } /** * Wrapper for NetStream.seek(). Never call * NetStream.seek() directly, always call * this method because it does some other housekeeping. * * @private */ private function _seek(time:Number):Void { //ifdef DEBUG //debugTrace("_seek(" + time + ")"); //endif if (_metadata.audiodelay != undefined && time + _metadata.audiodelay < _streamLength) { time += _metadata.audiodelay; } _ns.seek(time); _invalidSeekTime = false; _bufferState = BUFFER_EMPTY; _sawSeekNotify = false; } /** * Gets whether connected to a stream. If not, then calls to APIs * play() with no args, stop(), * pause() and seek() will throw * exceptions. * * @see #stateResponsive * @private */ private function isXnOK():Boolean { if (_state == LOADING) return true; if (_state == CONNECTION_ERROR) return false; if (_state != DISCONNECTED) { if ( _ncMgr == null || _ncMgr == undefined || _ncMgr.getNetConnection() == null || _ncMgr.getNetConnection() == undefined || !_ncMgr.getNetConnection().isConnected ) { setState(DISCONNECTED); return false; } return true; } return false; } /** * Kicks off autoresize process * * @private */ private function startAutoResize() { switch (_state) { case DISCONNECTED: case CONNECTION_ERROR: // autoresize will happen later automatically return; default: _autoResizeDone = false; if (stateResponsive && _videoWidth != undefined && _videoHeight != undefined) { // do it now! doAutoResize(); } else { // do it on an interval, it won't happen until we are // back in a responsive state clearInterval(_autoResizeIntervalID); _autoResizeIntervalID = setInterval(this, "doAutoResize", AUTO_RESIZE_INTERVAL); break; } } } /** *

Does the actual work of resetting the width and height.

* *

Called on an interval which is stopped when width and height * of the Video object are not zero. Finishing the * resize is done in another method which is either called on a * interval set up here for live streams or on a * NetStream.Play.Stop event in rtmpOnStatus after * stream is rewound if it is not a live stream. Still need to * get a http solution.

* * @private */ private function doAutoResize():Void { //ifdef DEBUG //debugTrace("doAutoResize(), _video.width = " + _video.width + ", _video.height = " + _video.height); //endif if (_autoResizeIntervalID > 0) { switch (_state) { case RESIZING: case LOADING: break; case DISCONNECTED: case CONNECTION_ERROR: // autoresize will happen later automatically clearInterval(_autoResizeIntervalID); _autoResizeIntervalID = 0; return; default: if (!stateResponsive) { // keep trying until we get into a responsive state return; } } if ( _video.width != _prevVideoWidth || _video.height != _prevVideoHeight || _bufferState >= BUFFER_FULL || _ns.time > AUTO_RESIZE_PLAYHEAD_TIMEOUT ) { // if have not received metadata yet, slight delay to avoid race condition in player // but there may not be any metadata, so cannot wait forever if (_hiddenForResize && _metadata == null && _hiddenForResizeMetadataDelay < AUTO_RESIZE_METADATA_DELAY_MAX) { //ifdef DEBUG //debugTrace("Delaying for metadata: " + _hiddenForResizeMetadataDelay); //endif _hiddenForResizeMetadataDelay++; return; } _videoWidth = _video.width; _videoHeight = _video.height; clearInterval(_autoResizeIntervalID); _autoResizeIntervalID = 0; } else { // keep trying until our size is set return; } } // do not need to do autoresize, but DO need to signal readyness if ((!_autoSize && !_aspectRatio) || _autoResizeDone) { setState(_cachedState); return; } //ifdef DEBUG //debugTrace("Actually doing autoResize, _videoWidth = " + _videoWidth + ", _videoHeight = " + _videoHeight); //endif _autoResizeDone = true; if (_autoSize) { _video._width = _videoWidth; _video._height = _videoHeight; } else if (_aspectRatio) { var newWidth:Number = (_videoWidth * height / _videoHeight); var newHeight:Number = (_videoHeight * width / _videoWidth); if (newHeight < height) { _video._height = newHeight; } else if (newWidth < width) { _video._width = newWidth; } } if (_hiddenForResize) { _hiddenRewindPlayheadTime = playheadTime; if (_state == LOADING) { _cachedState = PLAYING; } if (!_ncMgr.isRTMP()) { _pause(true); _seek(0); clearInterval(_finishAutoResizeIntervalID); _finishAutoResizeIntervalID = setInterval(this, "finishAutoResize", FINISH_AUTO_RESIZE_INTERVAL); } else if (!_isLive) { _currentPos = 0; _play(0, 0); setState(RESIZING) } else if (_autoPlay) { clearInterval(_finishAutoResizeIntervalID); _finishAutoResizeIntervalID = setInterval(this, "finishAutoResize", FINISH_AUTO_RESIZE_INTERVAL); } else { finishAutoResize(); } } else { dispatchEvent({type:"resize", x:_x, y:_y, width:_width, height:_height}); } } /** *

Makes video visible, turns on sound and starts * playing if live or autoplay.

*/ private function finishAutoResize():Void { //ifdef DEBUG //debugTrace("finishAutoResize()"); //endif clearInterval(_finishAutoResizeIntervalID); _finishAutoResizeIntervalID = 0; if (stateResponsive) return; _visible = __visible; _sound.setVolume(_volume); _hiddenForResize = false; //ifdef DEBUG //debugTrace("_autoPlay = " + _autoPlay); //endif dispatchEvent({type:"resize", x:_x, y:_y, width:_width, height:_height}); if (_autoPlay) { if (_ncMgr.isRTMP()) { if (!_isLive) { _currentPos = 0; _play(0); } if (_state == RESIZING) { setState(LOADING); _cachedState = PLAYING; } } else { _pause(false); _cachedState = PLAYING; } } else { setState(STOPPED); } } /** *

Creates NetStream and does some basic * initialization.

* * @private */ private function _createStream():Void { //ifdef DEBUG //debugTrace("_createStream()"); //endif _ns = new NetStream(_ncMgr.getNetConnection()); _ns.mc = this; if (_ncMgr.isRTMP()) { _ns.onStatus = function(info:Object):Void { this.mc.rtmpOnStatus(info); }; } else { _ns.onStatus = function(info:Object):Void { this.mc.httpOnStatus(info); }; } _ns.onMetaData = function (info:Object) { this.mc.onMetaData(info); }; _ns.onCuePoint = function (info:Object) { this.mc.onCuePoint(info); }; _ns.setBufferTime(_bufferTime); } /** *

Does initialization after first connecting to the server * and creating the stream. Will get the stream duration from * the INCManager if it has it for us.

* *

Starts resize if necessary, otherwise starts playing if * necessary, otherwise loads first frame of video. In http case, * starts progressive download in any case.

* * @private */ private function _setUpStream():Void { //ifdef DEBUG //debugTrace("_setUpStream()"); //endif _video.attachVideo(_ns); this.attachAudio(_ns); // INCManager MIGHT have gotten the stream length, width and height for // us. If its length is null, undefined or < 0, then it did not. if ( !isNaN(_ncMgr.getStreamLength()) && _ncMgr.getStreamLength() >= 0 ) { _streamLength = _ncMgr.getStreamLength(); } if ( !isNaN(_ncMgr.getStreamWidth()) && _ncMgr.getStreamWidth() >= 0 ) { _videoWidth = _ncMgr.getStreamWidth(); } else { _videoWidth = undefined; } if ( !isNaN(_ncMgr.getStreamHeight()) && _ncMgr.getStreamHeight() >= 0 ) { _videoHeight = _ncMgr.getStreamHeight(); } else { _videoHeight = undefined; } // resize immediately if height and width set above if ((_autoSize || _aspectRatio) && _videoWidth != undefined && _videoHeight != undefined) { _prevVideoWidth = undefined; _prevVideoHeight = undefined; doAutoResize(); } // just start if static, start resize otherwise if ((!_autoSize && !_aspectRatio) || (_videoWidth != undefined && _videoHeight != undefined)) { if (_autoPlay) { if (!_ncMgr.isRTMP()) { _cachedState = BUFFERING; _play(); } else if (_isLive) { _cachedState = BUFFERING; _play(-1); } else { _cachedState = BUFFERING; _play(0); } } else { _cachedState = STOPPED; if (_ncMgr.isRTMP()) { _play(0, 0); } else { _play(); _pause(true); _seek(0); } } } else { _hiddenForResize = true; _hiddenForResizeMetadataDelay = 0; __visible = _visible; _visible = false; _volume = _sound.getVolume(); _sound.setVolume(0); _play(0); if (_currentPos > 0) { _seek(_currentPos); _currentPos = 0; } } clearInterval(_autoResizeIntervalID); _autoResizeIntervalID = setInterval(this, "doAutoResize", AUTO_RESIZE_INTERVAL); } /** *

ONLY CALL THIS WITH RTMP STREAMING

* *

Only used for rtmp connections. When we pause or stop, * setup an interval to call this after a delay (see property * idleTimeout). We do this to spare the server from * having a bunch of extra xns hanging around, although this needs * to be balanced with the load that creating connections puts on * the server, and keep in mind that FCS can be configured to * terminate idle connections on its own, which is a better way to * manage the issue.

* * @private */ private function doIdleTimeout():Void { //ifdef DEBUG //debugTrace("Closing NetConnection NOW"); //endif clearInterval(_idleTimeoutIntervalID); _idleTimeoutIntervalID = 0; close(); } /** * Dumps all queued commands without executing them * * @private */ private function flushQueuedCmds():Void { //ifdef DEBUG //debugTrace("flushQueuedCmds()"); //endif while (_cmdQueue.length > 0) _cmdQueue.pop(); } /** * Executes as many queued commands as possible, obviously * stopping when state becomes unresponsive. * * @private */ private function execQueuedCmds():Void { //ifdef DEBUG //debugTrace("execQueuedCmds()"); //endif while ( _cmdQueue.length > 0 && (stateResponsive || _state == CONNECTION_ERROR) && ( (_cmdQueue[0].url != null && _cmdQueue[0].url != undefined) || (_state != DISCONNECTED && _state != CONNECTION_ERROR) ) ) { //ifdef DEBUG //debugTrace("Exec Queued Command!"); //endif var nextCmd:Object = _cmdQueue.shift(); _cachedState = _state; _state = EXEC_QUEUED_CMD; switch (nextCmd.type) { case PLAY: this.play(nextCmd.url, nextCmd.isLive, nextCmd.time); break; case LOAD: this.load(nextCmd.url, nextCmd.isLive, nextCmd.time); break; case PAUSE: this.pause(); break; case STOP: this.stop(); break; case SEEK: this.seek(nextCmd.time); break; } // switch } } private function queueCmd( type:Number, url:String, isLive:Boolean, time:Number):Void { //ifdef DEBUG //debugTrace("queueCmd(" + type + ", " + url + ", " + isLive + ", " + time + ")"); //endif _cmdQueue.push( {type:type, url:url, isLive:false, time:time} ); } //ifdef DEBUG //function debugTrace(s:String):Void { //if (_parent != null && _parent != undefined) { // _parent.debugTrace(s); //} //} //endif } // class mx.video.VideoPlayer