Parent Directory | Revision Log
Revision 1.1 - (view) (download)
1 : | thiswind | 1.1 | //**************************************************************************** |
2 : | //Copyright (C) 2004-2005 Macromedia, Inc. All Rights Reserved. | ||
3 : | //The following is Sample Code and is subject to all restrictions on | ||
4 : | //such code as contained in the End User License Agreement accompanying | ||
5 : | //this product. | ||
6 : | //**************************************************************************** | ||
7 : | |||
8 : | import mx.events.EventDispatcher; | ||
9 : | import mx.video.*; | ||
10 : | |||
11 : | /** | ||
12 : | * <p>Event dispatched when <code>NetConnection</code> is closed, | ||
13 : | * whether by being timed out or by calling <code>close()</code> API. | ||
14 : | * Only dispatched with RTMP streams, never HTTP. Event Object has | ||
15 : | * properties state and playheadTime.</p> | ||
16 : | * | ||
17 : | */ | ||
18 : | [Event("close")] | ||
19 : | |||
20 : | /** | ||
21 : | * <p>Event dispatched when playing completes by reaching the end of | ||
22 : | * the FLV. Is not dispatched if the APIs <code>stop()</code> or | ||
23 : | * <code>pause()</code> are called. Event Object has properties state and | ||
24 : | * playheadTime.</p> | ||
25 : | * | ||
26 : | * <p>When using progressive download and not setting totalTime | ||
27 : | * explicitly and downloading an FLV with no metadata duration, | ||
28 : | * the totalTime will be set to an approximate total value, now | ||
29 : | * that we have played the whole file we can make a guess. That | ||
30 : | * value is set by the time this event is dispatched.</p> | ||
31 : | * | ||
32 : | */ | ||
33 : | [Event("complete")] | ||
34 : | |||
35 : | /** | ||
36 : | * <p>Event dispatched when a cue point is reached. Event Object has an | ||
37 : | * info property that contains the info object received by the | ||
38 : | * <code>NetStream.onCuePoint</code> callback for FLV cue points or | ||
39 : | * the object passed into the AS cue point APIs for AS cue points.</p> | ||
40 : | * | ||
41 : | */ | ||
42 : | [Event("cuePoint")] | ||
43 : | |||
44 : | /** | ||
45 : | * <p>Event dispatched the first time the FLV metadata is reached. | ||
46 : | * Event Object has an info property that contains the info object | ||
47 : | * received by the <code>NetStream.onMetaData</code> callback.</p> | ||
48 : | * | ||
49 : | */ | ||
50 : | [Event("metadataReceived")] | ||
51 : | |||
52 : | /** | ||
53 : | * <p>While FLV is playing, this event is dispatched every .25 | ||
54 : | * seconds. Not dispatched when we are paused or stopped, unless a | ||
55 : | * seek occurs. Event Object has properties state and playheadTime.</p> | ||
56 : | * | ||
57 : | */ | ||
58 : | [Event("playheadUpdate")] | ||
59 : | |||
60 : | /** | ||
61 : | * <p>Indicates progress made in number of bytes downloaded. User can | ||
62 : | * use this to check bytes loaded or number of bytes in the buffer. | ||
63 : | * Fires every .25 seconds, starting when load is called and ending | ||
64 : | * when all bytes are loaded or if there is a network error. Event Object is | ||
65 : | * of type <code>mx.events.ProgressEvent</code>.</p> | ||
66 : | * | ||
67 : | */ | ||
68 : | [Event("progress")] | ||
69 : | |||
70 : | /** | ||
71 : | * <p>Event dispatched when FLV is loaded and ready to display. Event | ||
72 : | * object has properties state and playheadTime.</p> | ||
73 : | * | ||
74 : | * <p>Fired the first time we enter a responsive state after we | ||
75 : | * load a new flv with play() or load() API. Only fires once | ||
76 : | * for each FLV loaded.</p> | ||
77 : | * | ||
78 : | */ | ||
79 : | [Event("ready")] | ||
80 : | |||
81 : | /** | ||
82 : | * <p>Event dispatched when video is autoresized due to | ||
83 : | * maintainAspectRatio or autoSize properties set to true. Event | ||
84 : | * Object has properties x, y, width and height.</p> | ||
85 : | * | ||
86 : | */ | ||
87 : | [Event("resize")] | ||
88 : | |||
89 : | /** | ||
90 : | * <p>Event dispatched when video autorewinds. Event Object has properties | ||
91 : | * state and playheadTime.</p> | ||
92 : | * | ||
93 : | */ | ||
94 : | [Event("rewind")] | ||
95 : | |||
96 : | /** | ||
97 : | * <p>Event dispatched when playback state changes. Event Object has | ||
98 : | * properties state and playheadTime.</p> | ||
99 : | * | ||
100 : | * <p>This event can be used to track when playback enters/leaves | ||
101 : | * unresponsive states (for example in the middle of connecting, | ||
102 : | * resizing or rewinding) during which times APIs <code>play()</code>, | ||
103 : | * <code>pause()</code>, <code>stop()</code> and <code>seek()</code> | ||
104 : | * will queue the requests to be executed when the player enters | ||
105 : | * a responsive state.</p> | ||
106 : | */ | ||
107 : | [Event("stateChange")] | ||
108 : | |||
109 : | /** | ||
110 : | * <p>VideoPlayer is an easy to use wrapper for Video, NetConnection, | ||
111 : | * NetStream, etc. that makes playing FLV easy. It supports streaming | ||
112 : | * from Flash Communication Server (FCS) and http download of FLVs.</p> | ||
113 : | * | ||
114 : | * <p>VideoPlayer extends MovieClip and wraps a Video object. It also | ||
115 : | * "extends" EventDispatcher using mixins.</p> | ||
116 : | * | ||
117 : | * @author copyright 2004-2005 Macromedia, Inc. | ||
118 : | */ | ||
119 : | |||
120 : | class mx.video.VideoPlayer extends MovieClip { | ||
121 : | |||
122 : | #include "ComponentVersion.as" | ||
123 : | |||
124 : | // public state constants | ||
125 : | |||
126 : | /** | ||
127 : | * <p>State constant. This is the state when the VideoPlayer is | ||
128 : | * constructed and when the stream is closed by a call to | ||
129 : | * <code>close()</code> or timed out on idle.</p> | ||
130 : | * | ||
131 : | * <p>This is a responsive state.</p> | ||
132 : | * | ||
133 : | * @see #state | ||
134 : | * @see #stateResponsive | ||
135 : | * @see #connected | ||
136 : | * @see #idleTimeout | ||
137 : | * @see #close() | ||
138 : | */ | ||
139 : | public static var DISCONNECTED:String = "disconnected"; | ||
140 : | |||
141 : | /** | ||
142 : | * <p>State constant. FLV is loaded and play is stopped. This state | ||
143 : | * is entered when <code>stop()</code> is called and when the | ||
144 : | * playhead reaches the end of the stream.</p> | ||
145 : | * | ||
146 : | * <p>This is a responsive state.</p> | ||
147 : | * | ||
148 : | * @see #state | ||
149 : | * @see #stateResponsive | ||
150 : | * @see #stop() | ||
151 : | */ | ||
152 : | public static var STOPPED:String = "stopped"; | ||
153 : | |||
154 : | /** | ||
155 : | * <p>State constant. FLV is loaded and is playing. | ||
156 : | * This state is entered when <code>play()</code> | ||
157 : | * is called.</p> | ||
158 : | * | ||
159 : | * <p>This is a responsive state.</p> | ||
160 : | * | ||
161 : | * @see #state | ||
162 : | * @see #stateResponsive | ||
163 : | * @see #play() | ||
164 : | */ | ||
165 : | public static var PLAYING:String = "playing"; | ||
166 : | |||
167 : | /** | ||
168 : | * <p>State constant. FLV is loaded, but play is paused. | ||
169 : | * This state is entered when <code>pause()</code> is | ||
170 : | * called or when <code>load()</code> is called.</p> | ||
171 : | * | ||
172 : | * <p>This is a responsive state.</p> | ||
173 : | * | ||
174 : | * @see #state | ||
175 : | * @see #stateResponsive | ||
176 : | * @see #pause() | ||
177 : | * @see #load() | ||
178 : | */ | ||
179 : | public static var PAUSED:String = "paused"; | ||
180 : | |||
181 : | /** | ||
182 : | * <p>State constant. State entered immediately after | ||
183 : | * <code>play()</code> or <code>load()</code> is called.</p> | ||
184 : | * | ||
185 : | * <p>This is a responsive state.</p> | ||
186 : | * | ||
187 : | * @see #state | ||
188 : | * @see #stateResponsive | ||
189 : | */ | ||
190 : | public static var BUFFERING:String = "buffering"; | ||
191 : | |||
192 : | /** | ||
193 : | * <p>State constant. State entered immediately after | ||
194 : | * <code>play()</code> or <code>load()</code> is called.</p> | ||
195 : | * | ||
196 : | * <p>This is a unresponsive state.</p> | ||
197 : | * | ||
198 : | * @see #state | ||
199 : | * @see #stateResponsive | ||
200 : | * @see #load() | ||
201 : | * @see #play() | ||
202 : | */ | ||
203 : | public static var LOADING:String = "loading"; | ||
204 : | |||
205 : | /** | ||
206 : | * <p>State constant. Stream attempted to load was unable to load | ||
207 : | * for some reason. Could be no connection to server, stream not | ||
208 : | * found, etc.</p> | ||
209 : | * | ||
210 : | * <p>This is a unresponsive state.</p> | ||
211 : | * | ||
212 : | * @see #state | ||
213 : | * @see #stateResponsive | ||
214 : | */ | ||
215 : | public static var CONNECTION_ERROR:String = "connectionError"; | ||
216 : | |||
217 : | /** | ||
218 : | * <p>State constant. State entered during a autorewind triggered | ||
219 : | * by a stop. After rewind is complete, the state will be | ||
220 : | * <code>STOPPED</code>.</p> | ||
221 : | * | ||
222 : | * <p>This is a unresponsive state.</p> | ||
223 : | * | ||
224 : | * @see #autoRewind | ||
225 : | * @see #state | ||
226 : | * @see #stateResponsive | ||
227 : | */ | ||
228 : | public static var REWINDING:String = "rewinding"; | ||
229 : | |||
230 : | /** | ||
231 : | * <p>State constant. State entered after <code>seek()</code> | ||
232 : | * is called.</p> | ||
233 : | * | ||
234 : | * <p>This is a unresponsive state.</p> | ||
235 : | * | ||
236 : | * @see #state | ||
237 : | * @see #stateResponsive | ||
238 : | * @see #seek() | ||
239 : | */ | ||
240 : | public static var SEEKING:String = "seeking"; | ||
241 : | |||
242 : | /** | ||
243 : | * <p>State constant. State entered during autoresize.</p> | ||
244 : | * | ||
245 : | * <p>This is a unresponsive state.</p> | ||
246 : | * | ||
247 : | * @see #autoSize | ||
248 : | * @see #maintainAspectRatio | ||
249 : | * @see #state | ||
250 : | * @see #stateResponsive | ||
251 : | */ | ||
252 : | public static var RESIZING:String = "resizing"; | ||
253 : | |||
254 : | /** | ||
255 : | * <P>State constant. State during execution of queued command. | ||
256 : | * There will never get a "stateChange" event notification with | ||
257 : | * this state; it is internal only.</p> | ||
258 : | * | ||
259 : | * <p>This is a unresponsive state.</p> | ||
260 : | * | ||
261 : | * @see #state | ||
262 : | * @see #stateResponsive | ||
263 : | */ | ||
264 : | static var EXEC_QUEUED_CMD:String = "execQueuedCmd"; | ||
265 : | |||
266 : | // buffer states | ||
267 : | private static var BUFFER_EMPTY:String = "bufferEmpty"; | ||
268 : | private static var BUFFER_FULL:String = "bufferFull"; | ||
269 : | private static var BUFFER_FULL_SAW_PLAY_STOP:String = "bufferFullSawPlayStop"; | ||
270 : | |||
271 : | // state | ||
272 : | private var _state:String; | ||
273 : | private var _cachedState:String; | ||
274 : | private var _bufferState:String; | ||
275 : | private var _cachedPlayheadTime:Number; | ||
276 : | private var _metadata:Object; | ||
277 : | private var _startingPlay:Boolean; | ||
278 : | private var _invalidSeekTime:Boolean; | ||
279 : | private var _invalidSeekRecovery:Boolean; | ||
280 : | private var _readyDispatched:Boolean; | ||
281 : | private var _autoResizeDone:Boolean; | ||
282 : | private var _lastUpdateTime:Number; | ||
283 : | private var _sawSeekNotify:Boolean; | ||
284 : | |||
285 : | // Video object | ||
286 : | private var _video:Video; | ||
287 : | |||
288 : | // INCManager | ||
289 : | private var _ncMgr:INCManager; | ||
290 : | public var ncMgrClassName:String; | ||
291 : | |||
292 : | /** | ||
293 : | * <p>Set this property to the name of your custom class to | ||
294 : | * make all VideoPlayer objects created use that class as the | ||
295 : | * default INCManager implementation. The default value is | ||
296 : | * "mx.video.NCManager".</p> | ||
297 : | */ | ||
298 : | public static var DEFAULT_INCMANAGER:String = "mx.video.NCManager"; | ||
299 : | |||
300 : | // info about NetStream | ||
301 : | private var _ns:NetStream; | ||
302 : | private var _currentPos:Number; | ||
303 : | private var _atEnd:Boolean; | ||
304 : | private var _streamLength:Number; | ||
305 : | |||
306 : | // store properties | ||
307 : | private var _autoSize:Boolean; | ||
308 : | private var _aspectRatio:Boolean; | ||
309 : | |||
310 : | /** | ||
311 : | * <p>If true, then video plays immediately, if false waits for | ||
312 : | * <code>play</code> to be called. Set to true if stream is | ||
313 : | * loaded with call to <code>play()</code>, false if loaded | ||
314 : | * by call to <code>load()</code>.</p> | ||
315 : | * | ||
316 : | * <p>Even if <code>_autoPlay</code> is set to false, we will start | ||
317 : | * loading the video after <code>initialize()</code> is called. | ||
318 : | * In the case of FCS, this means creating the stream and loading | ||
319 : | * the first frame to display (and loading more if | ||
320 : | * <code>autoSize</code> or <code>aspectRatio</code> is true). In | ||
321 : | * the case of HTTP download, we will start downloading the stream | ||
322 : | * and show the first frame.</p> | ||
323 : | * | ||
324 : | * @private | ||
325 : | */ | ||
326 : | private var _autoPlay:Boolean; | ||
327 : | |||
328 : | private var _autoRewind:Boolean; | ||
329 : | private var _contentPath:String; | ||
330 : | private var _bufferTime:Number; | ||
331 : | private var _isLive:Boolean; | ||
332 : | private var _volume:Number; | ||
333 : | private var _sound:Sound; | ||
334 : | private var __visible:Boolean; | ||
335 : | private var _hiddenForResize:Boolean; | ||
336 : | private var _hiddenForResizeMetadataDelay:Number; | ||
337 : | private var _hiddenRewindPlayheadTime:Number; | ||
338 : | private var _videoWidth:Number; | ||
339 : | private var _videoHeight:Number; | ||
340 : | private var _prevVideoWidth:Number; | ||
341 : | private var _prevVideoHeight:Number; | ||
342 : | |||
343 : | // intervals | ||
344 : | private var _updateTimeIntervalID:Number; | ||
345 : | private var _updateTimeInterval:Number; | ||
346 : | private var _updateProgressIntervalID:Number; | ||
347 : | private var _updateProgressInterval:Number; | ||
348 : | private var _idleTimeoutIntervalID:Number; | ||
349 : | private var _idleTimeoutInterval:Number; | ||
350 : | private var _autoResizeIntervalID:Number; | ||
351 : | private var _rtmpDoStopAtEndIntervalID:Number; | ||
352 : | private var _rtmpDoSeekIntervalID:Number; | ||
353 : | private var _httpDoSeekIntervalID:Number; | ||
354 : | private var _httpDoSeekCount:Number | ||
355 : | private var _finishAutoResizeIntervalID:Number; | ||
356 : | private var _delayedBufferingIntervalID:Number; | ||
357 : | private var _delayedBufferingInterval:Number | ||
358 : | |||
359 : | // default times for intervals | ||
360 : | static var DEFAULT_UPDATE_TIME_INTERVAL:Number = 250; // .25 seconds | ||
361 : | static var DEFAULT_UPDATE_PROGRESS_INTERVAL:Number = 250; // .25 seconds | ||
362 : | static var DEFAULT_IDLE_TIMEOUT_INTERVAL:Number = 300000; // five minutes | ||
363 : | private static var AUTO_RESIZE_INTERVAL:Number = 100; // .1 seconds | ||
364 : | private static var AUTO_RESIZE_PLAYHEAD_TIMEOUT = .5; // .5 seconds | ||
365 : | private static var AUTO_RESIZE_METADATA_DELAY_MAX:Number = 5; // .5 seconds | ||
366 : | private static var FINISH_AUTO_RESIZE_INTERVAL:Number = 250; // .25 seconds | ||
367 : | private static var RTMP_DO_STOP_AT_END_INTERVAL:Number = 500; // .5 seconds | ||
368 : | private static var RTMP_DO_SEEK_INTERVAL:Number = 100; // .1 seconds | ||
369 : | private static var HTTP_DO_SEEK_INTERVAL:Number = 250; // .25 seconds | ||
370 : | private static var HTTP_DO_SEEK_MAX_COUNT:Number = 4; // 4 times * .25 seconds = 1 second | ||
371 : | private static var CLOSE_NS_INTERVAL:Number = .25; // .25 secconds | ||
372 : | private static var HTTP_DELAYED_BUFFERING_INTERVAL:Number = 100; // .1 seconds | ||
373 : | |||
374 : | // queues up Objects describing queued commands to be run later | ||
375 : | private var _cmdQueue:Array; | ||
376 : | |||
377 : | // values for command types for _cmdQueue | ||
378 : | static var PLAY:Number = 0; | ||
379 : | static var LOAD:Number = 1; | ||
380 : | static var PAUSE:Number = 2; | ||
381 : | static var STOP:Number = 3; | ||
382 : | static var SEEK:Number = 4; | ||
383 : | |||
384 : | // EventDispatcher mixins | ||
385 : | public var addEventListener:Function; | ||
386 : | public var removeEventListener:Function; | ||
387 : | public var dispatchEvent:Function; | ||
388 : | public var dispatchQueue:Function; | ||
389 : | |||
390 : | //ifdef DEBUG | ||
391 : | //private static var _debugSingleton:VideoPlayer; | ||
392 : | //endif | ||
393 : | |||
394 : | // | ||
395 : | // public APIs | ||
396 : | // | ||
397 : | |||
398 : | /** | ||
399 : | * <p>Constructor.</p> | ||
400 : | * | ||
401 : | * @see INCManager | ||
402 : | * @see NCManager | ||
403 : | */ | ||
404 : | public function VideoPlayer() { | ||
405 : | // add EventDispatcher mixins | ||
406 : | EventDispatcher.initialize(this); | ||
407 : | |||
408 : | // init state variables | ||
409 : | _state = DISCONNECTED; | ||
410 : | _cachedState = _state; | ||
411 : | _bufferState = BUFFER_EMPTY; | ||
412 : | _cachedPlayheadTime = 0; | ||
413 : | _metadata = null; | ||
414 : | _startingPlay = false; | ||
415 : | _invalidSeekTime = false; | ||
416 : | _invalidSeekRecovery = false; | ||
417 : | _currentPos = 0; | ||
418 : | _atEnd = false; | ||
419 : | _cmdQueue = new Array(); | ||
420 : | _readyDispatched = false; | ||
421 : | _autoResizeDone = false; | ||
422 : | _lastUpdateTime = -1; | ||
423 : | _sawSeekNotify = false; | ||
424 : | |||
425 : | // put off creation of INCManager until last minute to | ||
426 : | // give time to customize DEFAULT_INCMANAGER | ||
427 : | |||
428 : | // setup intervals | ||
429 : | _updateTimeIntervalID = 0; | ||
430 : | _updateTimeInterval = DEFAULT_UPDATE_TIME_INTERVAL; | ||
431 : | _updateProgressIntervalID = 0; | ||
432 : | _updateProgressInterval = DEFAULT_UPDATE_PROGRESS_INTERVAL; | ||
433 : | _idleTimeoutIntervalID = 0; | ||
434 : | _idleTimeoutInterval = DEFAULT_IDLE_TIMEOUT_INTERVAL; | ||
435 : | _autoResizeIntervalID = 0; | ||
436 : | _rtmpDoStopAtEndIntervalID = 0; | ||
437 : | _rtmpDoSeekIntervalID = 0; | ||
438 : | _httpDoSeekIntervalID = 0; | ||
439 : | _httpDoSeekCount = 0; | ||
440 : | _finishAutoResizeIntervalID = 0; | ||
441 : | _delayedBufferingIntervalID = 0; | ||
442 : | _delayedBufferingInterval = HTTP_DELAYED_BUFFERING_INTERVAL; | ||
443 : | |||
444 : | // init get/set properties | ||
445 : | if (_isLive == undefined) _isLive = false; | ||
446 : | if (_autoSize == undefined) _autoSize = false; | ||
447 : | if (_aspectRatio == undefined) _aspectRatio = true; | ||
448 : | if (_autoPlay == undefined) _autoPlay = true; | ||
449 : | if (_autoRewind == undefined) _autoRewind = true; | ||
450 : | if (_bufferTime == undefined) _bufferTime = 0.1; | ||
451 : | if (_volume == undefined) _volume = 100; | ||
452 : | _sound = new Sound(this); | ||
453 : | _sound.setVolume(_volume); | ||
454 : | __visible = true; | ||
455 : | _hiddenForResize = false; | ||
456 : | _hiddenForResizeMetadataDelay = 0; | ||
457 : | _contentPath = ""; | ||
458 : | |||
459 : | //ifdef DEBUG | ||
460 : | //_debugSingleton = this; | ||
461 : | //endif | ||
462 : | } | ||
463 : | |||
464 : | /** | ||
465 : | * <p>set width and height simultaneously. Since setting either | ||
466 : | * one can trigger an autoresize, this can be better than invoking | ||
467 : | * set width and set height individually.</p> | ||
468 : | * | ||
469 : | * <p>If autoSize is true then this has no effect, since the player | ||
470 : | * sets its own dimensions. If maintainAspectRatio is true and | ||
471 : | * autoSize is false, then changing width or height will trigger | ||
472 : | * an autoresize.</p> | ||
473 : | * | ||
474 : | * @param width | ||
475 : | * @param height | ||
476 : | * @see width | ||
477 : | * @see height | ||
478 : | */ | ||
479 : | public function setSize(w:Number, h:Number):Void | ||
480 : | { | ||
481 : | if ( (w == _video._width && h == _video._height) || _autoSize ) return; | ||
482 : | _video._width = w; | ||
483 : | _video._height = h; | ||
484 : | if (_aspectRatio) { | ||
485 : | startAutoResize(); | ||
486 : | } | ||
487 : | } | ||
488 : | |||
489 : | /** | ||
490 : | * <p>set scaleX and scaleY simultaneously. Since setting either | ||
491 : | * one can trigger an autoresize, this can be better than invoking | ||
492 : | * set width and set height individually.</p> | ||
493 : | * | ||
494 : | * <p>If autoSize is true then this has no effect, since the player | ||
495 : | * sets its own dimensions. If maintainAspectRatio is true and | ||
496 : | * autoSize is false, then changing scaleX or scaleY will trigger an | ||
497 : | * autoresize.</p> | ||
498 : | * | ||
499 : | * @param scaleX | ||
500 : | * @param scaleY | ||
501 : | * @see scaleX | ||
502 : | * @see scaleY | ||
503 : | */ | ||
504 : | public function setScale(xs:Number, ys:Number) { | ||
505 : | if ( (xs == _video._xscale && ys == _video._yscale) || _autoSize ) return; | ||
506 : | _video._xscale = xs; | ||
507 : | _video._yscale = ys; | ||
508 : | if (_aspectRatio) { | ||
509 : | startAutoResize(); | ||
510 : | } | ||
511 : | } | ||
512 : | |||
513 : | /** | ||
514 : | * <p>Causes the video to play. Can be called while the video is | ||
515 : | * paused, stopped, or while the video is already playing. Call this | ||
516 : | * method with no arguments to play an already loaded video or pass | ||
517 : | * in a url to load a new stream.</p> | ||
518 : | * | ||
519 : | * <p>If player is in an unresponsive state, queues the request.</p> | ||
520 : | * | ||
521 : | * <p>Throws an exception if called with no args and no stream | ||
522 : | * is connected. Use "stateChange" event and | ||
523 : | * <code>connected</code> property to determine when it is | ||
524 : | * safe to call this method.</p> | ||
525 : | * | ||
526 : | * @param url Pass in a url string if you want to load and play a | ||
527 : | * new FLV. If you have already loaded an FLV and want to continue | ||
528 : | * playing it, pass in <code>null</code>. | ||
529 : | * @param isLive Pass in true if streaming a live feed from FCS. | ||
530 : | * Defaults to false. | ||
531 : | * @param totalTime Pass in length of FLV. Pass in 0 or null or | ||
532 : | * undefined to automatically detect length from metadata, server | ||
533 : | * or xml. If <code>INCManager.streamLength</code> is not 0 or | ||
534 : | * null or undefined when <code>ncConnected</code> is called, then | ||
535 : | * that value will trump this one in any case. Default is | ||
536 : | * undefined. | ||
537 : | * @see #connected | ||
538 : | * @see #stateResponsive | ||
539 : | * @see #load() | ||
540 : | */ | ||
541 : | public function play(url:String, isLive:Boolean, totalTime:Number):Void { | ||
542 : | //ifdef DEBUG | ||
543 : | //debugTrace("play(" + url + ")"); | ||
544 : | //endif | ||
545 : | |||
546 : | // if new url passed, ask the INCManager to reconnect for us | ||
547 : | if (url != null && url != undefined) { | ||
548 : | if (_state == EXEC_QUEUED_CMD) { | ||
549 : | _state = _cachedState; | ||
550 : | } else if (!stateResponsive) { | ||
551 : | queueCmd(PLAY, url, isLive, totalTime); | ||
552 : | return; | ||
553 : | } else { | ||
554 : | execQueuedCmds(); | ||
555 : | } | ||
556 : | _autoPlay = true; | ||
557 : | _load(url, isLive, totalTime); | ||
558 : | // playing will start automatically once stream is setup, so return. | ||
559 : | return; | ||
560 : | } | ||
561 : | |||
562 : | if (!isXnOK()) { | ||
563 : | if ( _state == CONNECTION_ERROR || | ||
564 : | _ncMgr == null || _ncMgr == undefined || | ||
565 : | _ncMgr.getNetConnection() == null || | ||
566 : | _ncMgr.getNetConnection() == undefined ) { | ||
567 : | throw new VideoError(VideoError.NO_CONNECTION); | ||
568 : | } else { | ||
569 : | //ifdef DEBUG | ||
570 : | //debugTrace("RECONNECTING!!!"); | ||
571 : | //endif | ||
572 : | flushQueuedCmds(); | ||
573 : | queueCmd(PLAY); | ||
574 : | setState(LOADING); | ||
575 : | _cachedState = LOADING; | ||
576 : | _ncMgr.reconnect(); | ||
577 : | // playing will start automatically once stream is setup, so return. | ||
578 : | return; | ||
579 : | } | ||
580 : | } else if (_state == EXEC_QUEUED_CMD) { | ||
581 : | _state = _cachedState; | ||
582 : | } else if (!stateResponsive) { | ||
583 : | queueCmd(PLAY); | ||
584 : | return; | ||
585 : | } else { | ||
586 : | execQueuedCmds(); | ||
587 : | } | ||
588 : | |||
589 : | // recreate stream if necessary (this will never happen with | ||
590 : | // http download, just rtmp) | ||
591 : | if (_ns == null || _ns == undefined) { | ||
592 : | _createStream(); | ||
593 : | _video.attachVideo(_ns); | ||
594 : | this.attachAudio(_ns); | ||
595 : | } | ||
596 : | |||
597 : | switch (_state) { | ||
598 : | case BUFFERING: | ||
599 : | if (_ncMgr.isRTMP()) { | ||
600 : | _play(0); | ||
601 : | if (_atEnd) { | ||
602 : | _atEnd = false; | ||
603 : | _currentPos = 0; | ||
604 : | setState(REWINDING); | ||
605 : | } else if (_currentPos > 0) { | ||
606 : | _seek(_currentPos); | ||
607 : | _currentPos = 0; | ||
608 : | } | ||
609 : | } | ||
610 : | // no break | ||
611 : | case PLAYING: | ||
612 : | // already playing | ||
613 : | return; | ||
614 : | case STOPPED: | ||
615 : | if (_ncMgr.isRTMP()) { | ||
616 : | if (_isLive) { | ||
617 : | _play(-1); | ||
618 : | setState(BUFFERING); | ||
619 : | } else { | ||
620 : | _play(0); | ||
621 : | if (_atEnd) { | ||
622 : | _atEnd = false; | ||
623 : | _currentPos = 0; | ||
624 : | _state = BUFFERING; | ||
625 : | setState(REWINDING); | ||
626 : | } else if (_currentPos > 0) { | ||
627 : | _seek(_currentPos); | ||
628 : | _currentPos = 0; | ||
629 : | setState(BUFFERING); | ||
630 : | } else { | ||
631 : | setState(BUFFERING); | ||
632 : | } | ||
633 : | } | ||
634 : | } else { | ||
635 : | _pause(false); | ||
636 : | if (_atEnd) { | ||
637 : | _atEnd = false; | ||
638 : | _seek(0); | ||
639 : | _state = BUFFERING; | ||
640 : | setState(REWINDING); | ||
641 : | } else { | ||
642 : | if (_bufferState == BUFFER_EMPTY) { | ||
643 : | setState(BUFFERING); | ||
644 : | } else { | ||
645 : | setState(PLAYING); | ||
646 : | } | ||
647 : | } | ||
648 : | } | ||
649 : | break; | ||
650 : | case PAUSED: | ||
651 : | _pause(false); | ||
652 : | if (!_ncMgr.isRTMP()) { | ||
653 : | if (_bufferState == BUFFER_EMPTY) { | ||
654 : | setState(BUFFERING); | ||
655 : | } else { | ||
656 : | setState(PLAYING); | ||
657 : | } | ||
658 : | } else { | ||
659 : | setState(BUFFERING); | ||
660 : | } | ||
661 : | break; | ||
662 : | } // switch | ||
663 : | } | ||
664 : | |||
665 : | /** | ||
666 : | * <p>Similar to play, but causes the FLV to be loaded without | ||
667 : | * playing. Autoresizing will occur if appropriate and the first | ||
668 : | * frame of FLV will be shown (except for maybe not in the live case). | ||
669 : | * After initial load and autoresize, state will be <code>PAUSED</code>.</p> | ||
670 : | * | ||
671 : | * <p>Takes same arguments as <code>play()</code>, but unlike that | ||
672 : | * method it is never acceptable to call <code>load()</code> with | ||
673 : | * no url. If you do, an <code>Error</code> will be thrown.</p> | ||
674 : | * | ||
675 : | * <p>If player is in an unresponsive state, queues the request.</p> | ||
676 : | * | ||
677 : | * @param url Pass in a url string for the FLV you want to load. | ||
678 : | * @param isLive Pass in true if streaming a live feed from FCS. | ||
679 : | * Defaults to false. | ||
680 : | * @param totalTime Pass in length of FLV. Pass in 0 or null or | ||
681 : | * undefined to automatically detect length from metadata, server | ||
682 : | * or xml. If <code>INCManager.streamLength</code> is not 0 or | ||
683 : | * null or undefined when <code>ncConnected</code> is called, then | ||
684 : | * that value will trump this one in any case. Default is | ||
685 : | * undefined. | ||
686 : | * @see #connected | ||
687 : | * @see #play() | ||
688 : | */ | ||
689 : | public function load(url:String, isLive:Boolean, totalTime:Number):Void { | ||
690 : | if (url == null || url == undefined) { | ||
691 : | throw new Error("null url sent to VideoPlayer.load"); | ||
692 : | } | ||
693 : | |||
694 : | //ifdef DEBUG | ||
695 : | //debugTrace("load(" + url + ")"); | ||
696 : | //endif | ||
697 : | |||
698 : | if (_state == EXEC_QUEUED_CMD) { | ||
699 : | _state = _cachedState; | ||
700 : | } else if (!stateResponsive) { | ||
701 : | queueCmd(LOAD, url, isLive, totalTime); | ||
702 : | return; | ||
703 : | } else { | ||
704 : | execQueuedCmds(); | ||
705 : | } | ||
706 : | _autoPlay = false; | ||
707 : | _load(url, isLive, totalTime); | ||
708 : | } | ||
709 : | |||
710 : | /* | ||
711 : | * does loading work for play and load | ||
712 : | */ | ||
713 : | private function _load(url:String, isLive:Boolean, totalTime:Number):Void { | ||
714 : | //ifdef DEBUG | ||
715 : | //debugTrace("_load(" + url + ", " + isLive + ", " + totalTime + ")"); | ||
716 : | //endif | ||
717 : | _prevVideoWidth = this.videoWidth; | ||
718 : | if (_prevVideoWidth == undefined) { | ||
719 : | _prevVideoWidth = _video.width; | ||
720 : | if (_prevVideoWidth == undefined) _prevVideoWidth = 0; | ||
721 : | } | ||
722 : | _prevVideoHeight = this.videoHeight; | ||
723 : | if (_prevVideoHeight == undefined) { | ||
724 : | _prevVideoHeight = _video.height; | ||
725 : | if (_prevVideoHeight == undefined) _prevVideoHeight = 0; | ||
726 : | } | ||
727 : | |||
728 : | // reset state | ||
729 : | _autoResizeDone = false; | ||
730 : | _cachedPlayheadTime = 0; | ||
731 : | _bufferState = BUFFER_EMPTY; | ||
732 : | _metadata = null; | ||
733 : | _startingPlay = false; | ||
734 : | _invalidSeekTime = false; | ||
735 : | _invalidSeekRecovery = false; | ||
736 : | _isLive = (isLive == undefined) ? false : isLive; | ||
737 : | _contentPath = url; | ||
738 : | _currentPos = 0; | ||
739 : | _streamLength = totalTime; | ||
740 : | _atEnd = false; | ||
741 : | _videoWidth = undefined; | ||
742 : | _videoHeight = undefined; | ||
743 : | _readyDispatched = false; | ||
744 : | _lastUpdateTime = -1; | ||
745 : | _sawSeekNotify = false; | ||
746 : | |||
747 : | // must stop ALL intervals here | ||
748 : | clearInterval(_updateTimeIntervalID); | ||
749 : | _updateTimeIntervalID = 0; | ||
750 : | clearInterval(_updateProgressIntervalID); | ||
751 : | _updateProgressIntervalID = 0; | ||
752 : | clearInterval(_idleTimeoutIntervalID); | ||
753 : | _idleTimeoutIntervalID = 0; | ||
754 : | clearInterval(_autoResizeIntervalID); | ||
755 : | _autoResizeIntervalID = 0; | ||
756 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
757 : | _rtmpDoStopAtEndIntervalID = 0; | ||
758 : | clearInterval(_rtmpDoSeekIntervalID); | ||
759 : | _rtmpDoSeekIntervalID = 0; | ||
760 : | clearInterval(_httpDoSeekIntervalID); | ||
761 : | _httpDoSeekIntervalID = 0; | ||
762 : | clearInterval(_finishAutoResizeIntervalID); | ||
763 : | _finishAutoResizeIntervalID = 0; | ||
764 : | clearInterval(_delayedBufferingIntervalID); | ||
765 : | _delayedBufferingIntervalID = 0; | ||
766 : | |||
767 : | // close netstream | ||
768 : | closeNS(false); | ||
769 : | |||
770 : | // if returns false, wait for a "connected" message and | ||
771 : | // then do these things | ||
772 : | if (_ncMgr == null || _ncMgr == undefined) { | ||
773 : | createINCManager(); | ||
774 : | } | ||
775 : | var instantConnect:Boolean = _ncMgr.connectToURL(_contentPath); | ||
776 : | setState(LOADING); | ||
777 : | _cachedState = LOADING; | ||
778 : | if (instantConnect) { | ||
779 : | _createStream(); | ||
780 : | _setUpStream(); | ||
781 : | } | ||
782 : | if (!_ncMgr.isRTMP()) { | ||
783 : | clearInterval(_updateProgressIntervalID); | ||
784 : | _updateProgressIntervalID = setInterval(this, "doUpdateProgress", _updateProgressInterval); | ||
785 : | } | ||
786 : | } | ||
787 : | |||
788 : | /** | ||
789 : | * <p>Pauses video playback. If video is paused or stopped, has | ||
790 : | * no effect. To start playback again, call <code>play()</code>. | ||
791 : | * Takes no parameters</p> | ||
792 : | * | ||
793 : | * <p>If player is in an unresponsive state, queues the request.</p> | ||
794 : | * | ||
795 : | * <p>Throws an exception if called when no stream is | ||
796 : | * connected. Use "stateChange" event and | ||
797 : | * <code>connected</code> property to determine when it is | ||
798 : | * safe to call this method.</p> | ||
799 : | * | ||
800 : | * <p>If state is already stopped, pause is does nothing and state | ||
801 : | * remains stopped.</p> | ||
802 : | * | ||
803 : | * @see #connected | ||
804 : | * @see #stateResponsive | ||
805 : | * @see #play() | ||
806 : | */ | ||
807 : | public function pause():Void { | ||
808 : | //ifdef DEBUG | ||
809 : | //debugTrace("pause()"); | ||
810 : | //endif | ||
811 : | |||
812 : | if (!isXnOK()) { | ||
813 : | if ( _state == CONNECTION_ERROR || | ||
814 : | _ncMgr == null || _ncMgr == undefined || | ||
815 : | _ncMgr.getNetConnection() == null || | ||
816 : | _ncMgr.getNetConnection() == undefined ) { | ||
817 : | throw new VideoError(VideoError.NO_CONNECTION); | ||
818 : | } else { | ||
819 : | return; | ||
820 : | } | ||
821 : | } else if (_state == EXEC_QUEUED_CMD) { | ||
822 : | _state = _cachedState; | ||
823 : | } else if (!stateResponsive) { | ||
824 : | queueCmd(PAUSE); | ||
825 : | return; | ||
826 : | } else { | ||
827 : | execQueuedCmds(); | ||
828 : | } | ||
829 : | if (_state == PAUSED || _state == STOPPED || _ns == null || _ns == undefined) return; | ||
830 : | _pause(true); | ||
831 : | setState(PAUSED); | ||
832 : | } | ||
833 : | |||
834 : | /** | ||
835 : | * <p>Stops video playback. If <code>autoRewind</code> is set to | ||
836 : | * <code>true</code>, rewinds to first frame. If video is already | ||
837 : | * stopped, has no effect. To start playback again, call | ||
838 : | * <code>play()</code>. Takes no parameters</p> | ||
839 : | * | ||
840 : | * <p>If player is in an unresponsive state, queues the request.</p> | ||
841 : | * | ||
842 : | * <p>Throws an exception if called when no stream is | ||
843 : | * connected. Use "stateChange" event and | ||
844 : | * <code>connected</code> property to determine when it is | ||
845 : | * safe to call this method.</p> | ||
846 : | * | ||
847 : | * @see #connected | ||
848 : | * @see #stateResponsive | ||
849 : | * @see #autoRewind | ||
850 : | * @see #play() | ||
851 : | */ | ||
852 : | public function stop():Void | ||
853 : | { | ||
854 : | //ifdef DEBUG | ||
855 : | //debugTrace("stop()"); | ||
856 : | //endif | ||
857 : | |||
858 : | if (!isXnOK()) { | ||
859 : | if ( _state == CONNECTION_ERROR || | ||
860 : | _ncMgr == null || _ncMgr == undefined || | ||
861 : | _ncMgr.getNetConnection() == null || | ||
862 : | _ncMgr.getNetConnection() == undefined ) { | ||
863 : | throw new VideoError(VideoError.NO_CONNECTION); | ||
864 : | } else { | ||
865 : | return; | ||
866 : | } | ||
867 : | } else if (_state == EXEC_QUEUED_CMD) { | ||
868 : | _state = _cachedState; | ||
869 : | } else if (!stateResponsive) { | ||
870 : | queueCmd(STOP); | ||
871 : | return; | ||
872 : | } else { | ||
873 : | execQueuedCmds(); | ||
874 : | } | ||
875 : | if (_state == STOPPED || _ns == null || _ns == undefined) return; | ||
876 : | if (_ncMgr.isRTMP()) { | ||
877 : | if (_autoRewind && !_isLive) { | ||
878 : | _currentPos = 0; | ||
879 : | _play(0, 0); | ||
880 : | _state = STOPPED; | ||
881 : | setState(REWINDING); | ||
882 : | } else { | ||
883 : | closeNS(true); | ||
884 : | setState(STOPPED); | ||
885 : | } | ||
886 : | } else { | ||
887 : | _pause(true); | ||
888 : | if (_autoRewind) { | ||
889 : | _seek(0); | ||
890 : | _state = STOPPED; | ||
891 : | setState(REWINDING); | ||
892 : | } else { | ||
893 : | setState(STOPPED); | ||
894 : | } | ||
895 : | } | ||
896 : | } | ||
897 : | |||
898 : | /** | ||
899 : | * <p>Seeks to given second in video. If video is playing, | ||
900 : | * continues playing from that point. If video is paused, seek to | ||
901 : | * that point and remain paused. If video is stopped, seek to | ||
902 : | * that point and enters paused state. Has no effect with live | ||
903 : | * streams.</p> | ||
904 : | * | ||
905 : | * <p>If time is less than 0 or NaN, throws exeption. If time | ||
906 : | * is past the end of the stream, or past the amount of file | ||
907 : | * downloaded so far, then will attempt seek and when fails | ||
908 : | * will recover.</p> | ||
909 : | * | ||
910 : | * <p>If player is in an unresponsive state, queues the request.</p> | ||
911 : | * | ||
912 : | * <p>Throws an exception if called when no stream is | ||
913 : | * connected. Use "stateChange" event and | ||
914 : | * <code>connected</code> property to determine when it is | ||
915 : | * safe to call this method.</p> | ||
916 : | * | ||
917 : | * @param time seconds | ||
918 : | * @throws VideoError if time is < 0 | ||
919 : | * @see #connected | ||
920 : | * @see #stateResponsive | ||
921 : | */ | ||
922 : | public function seek(time:Number):Void | ||
923 : | { | ||
924 : | //ifdef DEBUG | ||
925 : | //debugTrace("seek:"+time); | ||
926 : | //endif | ||
927 : | // we do not allow more seeks until we are out of an invalid seek time state | ||
928 : | if (_invalidSeekTime) return; | ||
929 : | if (isNaN(time) || time < 0) throw new VideoError(VideoError.INVALID_SEEK); | ||
930 : | if (!isXnOK()) { | ||
931 : | if ( _state == CONNECTION_ERROR || | ||
932 : | _ncMgr == null || _ncMgr == undefined || | ||
933 : | _ncMgr.getNetConnection() == null || | ||
934 : | _ncMgr.getNetConnection() == undefined ) { | ||
935 : | throw new VideoError(VideoError.NO_CONNECTION); | ||
936 : | } else { | ||
937 : | //ifdef DEBUG | ||
938 : | //debugTrace("RECONNECTING!!!"); | ||
939 : | //endif | ||
940 : | flushQueuedCmds(); | ||
941 : | queueCmd(SEEK, null, false, time); | ||
942 : | setState(LOADING); | ||
943 : | _cachedState = LOADING; | ||
944 : | _ncMgr.reconnect(); | ||
945 : | // playing will start automatically once stream is setup, so return. | ||
946 : | return; | ||
947 : | } | ||
948 : | } else if (_state == EXEC_QUEUED_CMD) { | ||
949 : | _state = _cachedState; | ||
950 : | } else if (!stateResponsive) { | ||
951 : | queueCmd(SEEK, null, false, time); | ||
952 : | return; | ||
953 : | } else { | ||
954 : | execQueuedCmds(); | ||
955 : | } | ||
956 : | |||
957 : | // recreate stream if necessary (this will never happen with | ||
958 : | // http download, just rtmp) | ||
959 : | if (_ns == null || _ns == undefined) { | ||
960 : | _createStream(); | ||
961 : | _video.attachVideo(_ns); | ||
962 : | this.attachAudio(_ns); | ||
963 : | } | ||
964 : | |||
965 : | if (_atEnd && time < playheadTime) { | ||
966 : | _atEnd = false; | ||
967 : | } | ||
968 : | |||
969 : | switch (_state) { | ||
970 : | case PLAYING: | ||
971 : | _state = BUFFERING; | ||
972 : | // no break; | ||
973 : | case BUFFERING: | ||
974 : | case PAUSED: | ||
975 : | _seek(time); | ||
976 : | setState(SEEKING); | ||
977 : | break; | ||
978 : | case STOPPED: | ||
979 : | if (_ncMgr.isRTMP()) { | ||
980 : | _play(0); | ||
981 : | _pause(true); | ||
982 : | } | ||
983 : | _seek(time); | ||
984 : | _state = PAUSED; | ||
985 : | setState(SEEKING); | ||
986 : | break; | ||
987 : | } | ||
988 : | } | ||
989 : | |||
990 : | /** | ||
991 : | * <p>Forces close of video stream and FCS connection. Triggers | ||
992 : | * "close" event. Typically calling this directly is not necessary | ||
993 : | * because the idle timeout functionality will take care of this.</p> | ||
994 : | * | ||
995 : | * @see idleTimeout | ||
996 : | */ | ||
997 : | public function close():Void { | ||
998 : | //ifdef DEBUG | ||
999 : | //debugTrace("close()"); | ||
1000 : | //endif | ||
1001 : | closeNS(true); | ||
1002 : | // never makes sense to close an http NetConnection, it doesn't really maintain | ||
1003 : | // any kind of network connection! | ||
1004 : | if (_ncMgr != null && _ncMgr != undefined && _ncMgr.isRTMP()) { | ||
1005 : | _ncMgr.close(); | ||
1006 : | } | ||
1007 : | setState(DISCONNECTED); | ||
1008 : | dispatchEvent({type:"close", state:_state, playheadTime:playheadTime}); | ||
1009 : | } | ||
1010 : | |||
1011 : | |||
1012 : | // | ||
1013 : | // public getters, setters | ||
1014 : | // | ||
1015 : | |||
1016 : | |||
1017 : | public function get x():Number { | ||
1018 : | return this._x; | ||
1019 : | } | ||
1020 : | public function set x(xpos:Number) { | ||
1021 : | this._x = xpos; | ||
1022 : | } | ||
1023 : | |||
1024 : | public function get y():Number { | ||
1025 : | return this._y; | ||
1026 : | } | ||
1027 : | public function set y(ypos:Number) { | ||
1028 : | this._y = ypos; | ||
1029 : | } | ||
1030 : | |||
1031 : | /** | ||
1032 : | * 100 is standard scale | ||
1033 : | * | ||
1034 : | * @see #setScale() | ||
1035 : | */ | ||
1036 : | function get scaleX():Number | ||
1037 : | { | ||
1038 : | return _video._xscale; | ||
1039 : | } | ||
1040 : | function set scaleX(xs:Number):Void | ||
1041 : | { | ||
1042 : | setScale(xs, this.scaleY); | ||
1043 : | } | ||
1044 : | |||
1045 : | /** | ||
1046 : | * 100 is standard scale | ||
1047 : | * | ||
1048 : | * @see #setScale() | ||
1049 : | */ | ||
1050 : | function get scaleY():Number | ||
1051 : | { | ||
1052 : | return _video._yscale; | ||
1053 : | } | ||
1054 : | function set scaleY(ys:Number):Void | ||
1055 : | { | ||
1056 : | setScale(this.scaleX, ys); | ||
1057 : | } | ||
1058 : | |||
1059 : | /** | ||
1060 : | * <p>Width of video instance. Not same as Video.width, that is videoWidth.</p> | ||
1061 : | * | ||
1062 : | * @see #setSize() | ||
1063 : | * @see #videoWidth | ||
1064 : | */ | ||
1065 : | public function get width():Number { | ||
1066 : | return _video._width; | ||
1067 : | } | ||
1068 : | public function set width(w:Number):Void | ||
1069 : | { | ||
1070 : | setSize(w, _video._height); | ||
1071 : | } | ||
1072 : | |||
1073 : | /** | ||
1074 : | * <p>Height of video. Not same as Video.height, that is videoHeight.</p> | ||
1075 : | * | ||
1076 : | * @see #setSize() | ||
1077 : | * @see #videoHeight | ||
1078 : | */ | ||
1079 : | public function get height():Number { | ||
1080 : | return _video._height; | ||
1081 : | } | ||
1082 : | public function set height(h:Number):Void | ||
1083 : | { | ||
1084 : | setSize(_video._width, h); | ||
1085 : | } | ||
1086 : | |||
1087 : | /** | ||
1088 : | * <p>Source width of loaded FLV file. Read only. Returns | ||
1089 : | * undefined if no information available yet.</p> | ||
1090 : | * | ||
1091 : | * @see #width | ||
1092 : | */ | ||
1093 : | public function get videoWidth() { | ||
1094 : | if (_readyDispatched) { | ||
1095 : | _videoWidth = _video.width; | ||
1096 : | } | ||
1097 : | return _videoWidth; | ||
1098 : | } | ||
1099 : | |||
1100 : | /** | ||
1101 : | * <p>Source height of loaded FLV file. Read only. Returns | ||
1102 : | * undefined if no information available yet.</p> | ||
1103 : | * | ||
1104 : | * @see #height | ||
1105 : | */ | ||
1106 : | public function get videoHeight() { | ||
1107 : | if (_readyDispatched) { | ||
1108 : | _videoHeight = _video.height; | ||
1109 : | } | ||
1110 : | return _videoHeight; | ||
1111 : | } | ||
1112 : | |||
1113 : | /** | ||
1114 : | * <p>Use this instead of <code>_visible</code> because we | ||
1115 : | * sometimes do internal visibility management when doing an | ||
1116 : | * autoresize.</p> | ||
1117 : | */ | ||
1118 : | public function get visible():Boolean { | ||
1119 : | if (!_hiddenForResize) { | ||
1120 : | __visible = _visible; | ||
1121 : | } | ||
1122 : | return __visible; | ||
1123 : | } | ||
1124 : | |||
1125 : | public function set visible(v:Boolean):Void { | ||
1126 : | __visible = v; | ||
1127 : | if (!_hiddenForResize) { | ||
1128 : | _visible = __visible; | ||
1129 : | } | ||
1130 : | } | ||
1131 : | |||
1132 : | /** | ||
1133 : | * <p>Determines whether the instance is automatically resized to | ||
1134 : | * the source dimensions. If this is set from false to true after | ||
1135 : | * an FLV has been loaded, an automatic resize will start | ||
1136 : | * immediately.</p> | ||
1137 : | * | ||
1138 : | */ | ||
1139 : | public function get autoSize():Boolean | ||
1140 : | { | ||
1141 : | return _autoSize; | ||
1142 : | } | ||
1143 : | public function set autoSize(flag:Boolean):Void | ||
1144 : | { | ||
1145 : | if (_autoSize != flag) { | ||
1146 : | _autoSize = flag; | ||
1147 : | if (_autoSize) { | ||
1148 : | startAutoResize(); | ||
1149 : | } | ||
1150 : | } | ||
1151 : | } | ||
1152 : | |||
1153 : | /** | ||
1154 : | * <p>Determines whether video aspect ratio is maintained. If | ||
1155 : | * this is set from false to true and <code>autoSize</code is | ||
1156 : | * false after an FLV has been loaded, an automatic resize will | ||
1157 : | * start immediately.</p> | ||
1158 : | * | ||
1159 : | * @see #autoSize | ||
1160 : | */ | ||
1161 : | public function get maintainAspectRatio():Boolean | ||
1162 : | { | ||
1163 : | return _aspectRatio; | ||
1164 : | } | ||
1165 : | public function set maintainAspectRatio(flag:Boolean):Void | ||
1166 : | { | ||
1167 : | if (_aspectRatio != flag) { | ||
1168 : | _aspectRatio = flag; | ||
1169 : | if (_aspectRatio && !_autoSize) { | ||
1170 : | startAutoResize(); | ||
1171 : | } | ||
1172 : | } | ||
1173 : | } | ||
1174 : | |||
1175 : | /** | ||
1176 : | * <p>Determines whether the FLV is rewound to the first frame | ||
1177 : | * when play stops, either by calling <code>stop()</code> or by | ||
1178 : | * reaching the end of the stream. Meaningless for live streams.</p> | ||
1179 : | * | ||
1180 : | */ | ||
1181 : | public function get autoRewind():Boolean | ||
1182 : | { | ||
1183 : | return _autoRewind; | ||
1184 : | } | ||
1185 : | public function set autoRewind(flag:Boolean):Void | ||
1186 : | { | ||
1187 : | _autoRewind = flag; | ||
1188 : | } | ||
1189 : | |||
1190 : | /** | ||
1191 : | * <p>The current playhead time in seconds. Setting does a seek | ||
1192 : | * and has all the restrictions of a seek.</p> | ||
1193 : | * | ||
1194 : | * <p>The event "playheadUpdate" is dispatched when the playhead | ||
1195 : | * time changes, including every .25 seconds while the FLV is | ||
1196 : | * playing.</p> | ||
1197 : | * | ||
1198 : | * @return The playhead position, measured in seconds since the start. Will return a fractional value. | ||
1199 : | * @see #seek() | ||
1200 : | */ | ||
1201 : | public function get playheadTime():Number | ||
1202 : | { | ||
1203 : | var nowTime:Number = (_ns == null || _ns == undefined) ? _currentPos : _ns.time; | ||
1204 : | if (_metadata.audiodelay != undefined) { | ||
1205 : | nowTime -= _metadata.audiodelay; | ||
1206 : | if (nowTime < 0) nowTime = 0; | ||
1207 : | } | ||
1208 : | return nowTime; | ||
1209 : | } | ||
1210 : | public function set playheadTime(position:Number):Void | ||
1211 : | { | ||
1212 : | seek(position); | ||
1213 : | } | ||
1214 : | |||
1215 : | /** | ||
1216 : | * <p>url of currently loaded (or loading) stream. Will be url | ||
1217 : | * last sent to <code>play()</code> or <code>load()</code>, <code>null</code> | ||
1218 : | * if no stream is loaded.</p> | ||
1219 : | * | ||
1220 : | */ | ||
1221 : | public function get url():String | ||
1222 : | { | ||
1223 : | return _contentPath; | ||
1224 : | } | ||
1225 : | |||
1226 : | /** | ||
1227 : | * <p>Volume control in range from 0 to 100.</p> | ||
1228 : | * | ||
1229 : | * @return The most recent volume setting | ||
1230 : | * @see #transform | ||
1231 : | */ | ||
1232 : | public function get volume():Number | ||
1233 : | { | ||
1234 : | return _volume; | ||
1235 : | } | ||
1236 : | public function set volume(aVol:Number):Void | ||
1237 : | { | ||
1238 : | _volume = aVol; | ||
1239 : | if (!_hiddenForResize) { | ||
1240 : | _sound.setVolume(_volume); | ||
1241 : | } | ||
1242 : | } | ||
1243 : | |||
1244 : | /** | ||
1245 : | * <p>Provides direct access to the | ||
1246 : | * <code>Sound.setTransform()</code> and | ||
1247 : | * <code>Sound.getTransform()</code> APIs. to expose more sound | ||
1248 : | * control. Must set property for changes to take effect, get | ||
1249 : | * property just to get a copy of the current settings to tweak.</p> | ||
1250 : | * | ||
1251 : | * @see #volume | ||
1252 : | */ | ||
1253 : | public function get transform():Object { | ||
1254 : | return _sound.getTransform(); | ||
1255 : | } | ||
1256 : | public function set transform(s:Object):Void { | ||
1257 : | _sound.setTransform(s); | ||
1258 : | } | ||
1259 : | |||
1260 : | /** | ||
1261 : | * True if stream is RTMP download (streaming from Flash | ||
1262 : | * Communication Server), read only. | ||
1263 : | */ | ||
1264 : | public function get isRTMP():Boolean { | ||
1265 : | if (_ncMgr == null || _ncMgr == undefined) return undefined; | ||
1266 : | return _ncMgr.isRTMP(); | ||
1267 : | } | ||
1268 : | |||
1269 : | /** | ||
1270 : | * <p>True if stream is live, read only. isLive only makes sense when | ||
1271 : | * streaming from FVSS or FCS, value is ignored when doing http | ||
1272 : | * download.</p> | ||
1273 : | */ | ||
1274 : | public function get isLive():Boolean { | ||
1275 : | return _isLive; | ||
1276 : | } | ||
1277 : | |||
1278 : | /** | ||
1279 : | * Get state. Read only. Set with <code>load</code>, | ||
1280 : | * <code>play()</code>, <code>stop()</code>, | ||
1281 : | * <code>pause()</code> and <code>seek()</code>. | ||
1282 : | */ | ||
1283 : | public function get state():String { | ||
1284 : | return _state; | ||
1285 : | } | ||
1286 : | |||
1287 : | /** | ||
1288 : | * Read only. Gets whether state is responsive. If state is | ||
1289 : | * unresponsive, calls to APIs <code>play()</code>, | ||
1290 : | * <code>load()</code>, <code>stop()</code>, | ||
1291 : | * <code>pause()</code> and <code>seek()</code> will queue the | ||
1292 : | * requests for later, when the state changes to a responsive | ||
1293 : | * one. | ||
1294 : | * | ||
1295 : | * @see #connected | ||
1296 : | * @see #DISCONNECTED | ||
1297 : | * @see #STOPPED | ||
1298 : | * @see #PLAYING | ||
1299 : | * @see #PAUSED | ||
1300 : | * @see #LOADING | ||
1301 : | * @see #RESIZING | ||
1302 : | * @see #CONNECTION_ERROR | ||
1303 : | * @see #REWINDING | ||
1304 : | */ | ||
1305 : | public function get stateResponsive():Boolean { | ||
1306 : | switch (_state) { | ||
1307 : | case DISCONNECTED: | ||
1308 : | case STOPPED: | ||
1309 : | case PLAYING: | ||
1310 : | case PAUSED: | ||
1311 : | case BUFFERING: | ||
1312 : | return true; | ||
1313 : | default: | ||
1314 : | return false; | ||
1315 : | } | ||
1316 : | } | ||
1317 : | |||
1318 : | /** | ||
1319 : | * <p>property bytesLoaded, read only. Returns -1 when there | ||
1320 : | * is no stream, when the stream is FCS or if the information | ||
1321 : | * is not yet available. Return value only useful for HTTP | ||
1322 : | * download.</p> | ||
1323 : | * | ||
1324 : | */ | ||
1325 : | public function get bytesLoaded():Number | ||
1326 : | { | ||
1327 : | if (_ns == null || _ns == undefined || _ncMgr.isRTMP()) return -1; | ||
1328 : | return _ns.bytesLoaded; | ||
1329 : | } | ||
1330 : | |||
1331 : | /** | ||
1332 : | * <p>property bytesTotal, read only. Returns -1 when there | ||
1333 : | * is no stream, when the stream is FCS or if the information | ||
1334 : | * is not yet available. Return value only useful for HTTP | ||
1335 : | * download.</p> | ||
1336 : | * | ||
1337 : | */ | ||
1338 : | public function get bytesTotal():Number | ||
1339 : | { | ||
1340 : | if (_ns == null || _ns == undefined || _ncMgr.isRTMP()) return -1; | ||
1341 : | return _ns.bytesTotal; | ||
1342 : | } | ||
1343 : | |||
1344 : | /** | ||
1345 : | * <p>property totalTime. read only. 0 or null or undefined | ||
1346 : | * means that property was not passed into <code>play()</code> or | ||
1347 : | * <code>load()</code> and was unable to detect automatically, or | ||
1348 : | * have not yet.</p> | ||
1349 : | * | ||
1350 : | * @return The total running time of the FLV in seconds | ||
1351 : | */ | ||
1352 : | public function get totalTime():Number | ||
1353 : | { | ||
1354 : | return _streamLength; | ||
1355 : | } | ||
1356 : | |||
1357 : | /** | ||
1358 : | * <p>Sets number of seconds to buffer in memory before playing | ||
1359 : | * back stream. For slow connections streaming over rtmp, it is | ||
1360 : | * important to increase this from the default. Default is | ||
1361 : | * 0.1</p> | ||
1362 : | */ | ||
1363 : | public function get bufferTime():Number | ||
1364 : | { | ||
1365 : | return _bufferTime; | ||
1366 : | } | ||
1367 : | public function set bufferTime(aTime:Number):Void | ||
1368 : | { | ||
1369 : | _bufferTime = aTime; | ||
1370 : | if (_ns != null && _ns != undefined) { | ||
1371 : | _ns.setBufferTime(_bufferTime); | ||
1372 : | } | ||
1373 : | } | ||
1374 : | |||
1375 : | /** | ||
1376 : | * <p>Property idleTimeout, which is amount of time in | ||
1377 : | * milliseconds before connection is idle (playing is paused | ||
1378 : | * or stopped) before connection to the FCS server is | ||
1379 : | * terminated. Has no effect to HTTP download of FLV.</p> | ||
1380 : | * | ||
1381 : | * <p>If set when stream already idle, restarts idle timeout with | ||
1382 : | * new value.</p> | ||
1383 : | */ | ||
1384 : | public function get idleTimeout():Number { | ||
1385 : | return _idleTimeoutInterval; | ||
1386 : | } | ||
1387 : | public function set idleTimeout(aTime:Number):Void { | ||
1388 : | _idleTimeoutInterval = aTime; | ||
1389 : | if (_idleTimeoutIntervalID > 0) { | ||
1390 : | clearInterval(_idleTimeoutIntervalID); | ||
1391 : | _idleTimeoutIntervalID = setInterval(this, "doIdleTimeout", _idleTimeoutInterval); | ||
1392 : | } | ||
1393 : | } | ||
1394 : | |||
1395 : | /** | ||
1396 : | * <p>Property playheadUpdateInterval, which is amount of time | ||
1397 : | * in milliseconds between each "playheadUpdate" event.</p> | ||
1398 : | * | ||
1399 : | * <p>If set when stream is playing, will restart interval.</p> | ||
1400 : | */ | ||
1401 : | public function get playheadUpdateInterval():Number { | ||
1402 : | return _updateTimeInterval; | ||
1403 : | } | ||
1404 : | public function set playheadUpdateInterval(aTime:Number):Void { | ||
1405 : | _updateTimeInterval = aTime; | ||
1406 : | if (_updateTimeIntervalID > 0) { | ||
1407 : | clearInterval(_updateTimeIntervalID); | ||
1408 : | _updateTimeIntervalID = setInterval(this, "doUpdateTime", _updateTimeInterval); | ||
1409 : | } | ||
1410 : | } | ||
1411 : | |||
1412 : | /** | ||
1413 : | * <p>Property progressInterval, which is amount of time | ||
1414 : | * in milliseconds between each "progress" event.</p> | ||
1415 : | * | ||
1416 : | * <p>If set when stream is playing, will restart interval.</p> | ||
1417 : | */ | ||
1418 : | public function get progressInterval():Number { | ||
1419 : | return _updateProgressInterval; | ||
1420 : | } | ||
1421 : | public function set progressInterval(aTime:Number):Void { | ||
1422 : | _updateProgressInterval = aTime; | ||
1423 : | if (_updateProgressIntervalID > 0) { | ||
1424 : | clearInterval(_updateProgressIntervalID); | ||
1425 : | _updateProgressIntervalID = setInterval(this, "doUpdateProgress", _updateProgressInterval); | ||
1426 : | } | ||
1427 : | } | ||
1428 : | |||
1429 : | /** | ||
1430 : | * <p>Access to instance of the class implementing | ||
1431 : | * <code>INCManager</code>. Read only.</p> | ||
1432 : | * | ||
1433 : | * <p>One use case for this is that a custom | ||
1434 : | * <code>INCManager</code> implementation may require custom | ||
1435 : | * initialization.</p> | ||
1436 : | */ | ||
1437 : | public function get ncMgr():INCManager { | ||
1438 : | if (_ncMgr == null || _ncMgr == undefined) { | ||
1439 : | createINCManager(); | ||
1440 : | } | ||
1441 : | return _ncMgr; | ||
1442 : | } | ||
1443 : | |||
1444 : | /** | ||
1445 : | * <p>Read only. Object received by call to onMetaData callback. | ||
1446 : | * null if onMetaData callback has not been called since the last | ||
1447 : | * load or play call. Always null with FLVs with no onMetaData | ||
1448 : | * packet.</p> | ||
1449 : | * | ||
1450 : | * @see #load() | ||
1451 : | * @see #play() | ||
1452 : | */ | ||
1453 : | public function get metadata() { return _metadata; }; | ||
1454 : | |||
1455 : | // | ||
1456 : | // public callbacks, not really APIs | ||
1457 : | // | ||
1458 : | |||
1459 : | |||
1460 : | /** | ||
1461 : | * <p>Called on interval determined by | ||
1462 : | * <code>playheadUpdateInterval</code> to send "playheadUpdate" | ||
1463 : | * events. Events only sent when playhead is moving, sent every | ||
1464 : | * .25 seconds by default.</p> | ||
1465 : | * | ||
1466 : | * @private | ||
1467 : | */ | ||
1468 : | public function doUpdateTime():Void { | ||
1469 : | //ifdef DEBUG | ||
1470 : | ////debugTrace("doUpdateTime()"); | ||
1471 : | //endif | ||
1472 : | var theTime:Number = playheadTime; | ||
1473 : | |||
1474 : | // stop interval if we are stopped or paused | ||
1475 : | switch (_state) { | ||
1476 : | case STOPPED: | ||
1477 : | case PAUSED: | ||
1478 : | case DISCONNECTED: | ||
1479 : | case CONNECTION_ERROR: | ||
1480 : | clearInterval(_updateTimeIntervalID); | ||
1481 : | _updateTimeIntervalID = 0; | ||
1482 : | break; | ||
1483 : | } | ||
1484 : | |||
1485 : | if (_lastUpdateTime != theTime) { | ||
1486 : | dispatchEvent({type:"playheadUpdate", state:_state, playheadTime:theTime}); | ||
1487 : | _lastUpdateTime = theTime; | ||
1488 : | } | ||
1489 : | } | ||
1490 : | |||
1491 : | /** | ||
1492 : | * <p>Called at interval determined by | ||
1493 : | * <code>progressInterval</code> to send "progress" events. | ||
1494 : | * Object dispatch starts when <code>_load</code> is called, ends | ||
1495 : | * when all bytes downloaded or a network error of some kind | ||
1496 : | * occurs, dispatched every .25 seconds by default.</p> | ||
1497 : | * | ||
1498 : | * @private | ||
1499 : | */ | ||
1500 : | public function doUpdateProgress():Void { | ||
1501 : | if (_ns == null || _ns == undefined) return; | ||
1502 : | //ifdef DEBUG | ||
1503 : | ////debugTrace("doUpdateProgress()"); | ||
1504 : | ////debugTrace("_ns.bytesLoaded = " + _ns.bytesLoaded); | ||
1505 : | ////debugTrace("_ns.bytesTotal = " + _ns.bytesTotal); | ||
1506 : | //endif | ||
1507 : | |||
1508 : | if (_ns.bytesTotal >= 0 && _ns.bytesTotal >= 0) { | ||
1509 : | dispatchEvent({type:"progress", bytesLoaded:_ns.bytesLoaded, bytesTotal:_ns.bytesTotal}); | ||
1510 : | } | ||
1511 : | if ( _state == DISCONNECTED || _state == CONNECTION_ERROR || | ||
1512 : | _ns.bytesLoaded == _ns.bytesTotal ) { | ||
1513 : | clearInterval(_updateProgressIntervalID); | ||
1514 : | _updateProgressIntervalID = 0; | ||
1515 : | } | ||
1516 : | } | ||
1517 : | |||
1518 : | /** | ||
1519 : | * <p><code>NetStream.onStatus</code> callback for rtmp. Handles | ||
1520 : | * automatic resizing, autorewind and buffering messaging.</p> | ||
1521 : | * | ||
1522 : | * @private | ||
1523 : | */ | ||
1524 : | public function rtmpOnStatus(info:Object):Void | ||
1525 : | { | ||
1526 : | //ifdef DEBUG | ||
1527 : | //debugTrace("rtmpOnStatus:"+info.code); | ||
1528 : | //debugTrace("_state == " + _state); | ||
1529 : | //debugTrace("_cachedState == " + _cachedState); | ||
1530 : | //debugTrace("_bufferState == " + _bufferState); | ||
1531 : | //debugTrace("_cachedPlayheadTime == " + _cachedPlayheadTime); | ||
1532 : | //debugTrace("playheadTime == " + playheadTime); | ||
1533 : | //endif | ||
1534 : | |||
1535 : | if (_state == CONNECTION_ERROR) { | ||
1536 : | // always do nothing | ||
1537 : | return; | ||
1538 : | } | ||
1539 : | |||
1540 : | switch (info.code) { | ||
1541 : | case "NetStream.Play.Stop": | ||
1542 : | if (_startingPlay) return; | ||
1543 : | switch (_state) { | ||
1544 : | case RESIZING: | ||
1545 : | if (_hiddenForResize) finishAutoResize(); | ||
1546 : | break; | ||
1547 : | case LOADING: | ||
1548 : | case STOPPED: | ||
1549 : | case PAUSED: | ||
1550 : | // yes we are stopped, we already know this | ||
1551 : | break; | ||
1552 : | default: | ||
1553 : | if (_bufferState == BUFFER_EMPTY || _bufferTime <= 0.1) { | ||
1554 : | // if we did a seek toward the end of the file so that | ||
1555 : | // there is less file left to show than we have | ||
1556 : | // buffer, than we will get a NetStream.Play.Stop when | ||
1557 : | // the buffer loads rest of the file, but never get | ||
1558 : | // a NetStream.Buffer.Full, since it won't fill, so | ||
1559 : | // we check if we are done on a timer | ||
1560 : | _cachedPlayheadTime = playheadTime; | ||
1561 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
1562 : | _rtmpDoStopAtEndIntervalID = setInterval(this, "rtmpDoStopAtEnd", RTMP_DO_STOP_AT_END_INTERVAL); | ||
1563 : | } else if (_bufferState == BUFFER_FULL) { | ||
1564 : | _bufferState = BUFFER_FULL_SAW_PLAY_STOP; | ||
1565 : | } else { | ||
1566 : | //ifdef DEBUG | ||
1567 : | //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); | ||
1568 : | //endif | ||
1569 : | } | ||
1570 : | break; | ||
1571 : | } // switch (_state) | ||
1572 : | break; | ||
1573 : | case "NetStream.Buffer.Empty": | ||
1574 : | switch (_bufferState) { | ||
1575 : | case BUFFER_FULL_SAW_PLAY_STOP: | ||
1576 : | rtmpDoStopAtEnd(true); | ||
1577 : | break; | ||
1578 : | case BUFFER_FULL: | ||
1579 : | if (_state == PLAYING) { | ||
1580 : | setState(BUFFERING); | ||
1581 : | } | ||
1582 : | break; | ||
1583 : | default: | ||
1584 : | //ifdef DEBUG | ||
1585 : | //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); | ||
1586 : | //endif | ||
1587 : | break; | ||
1588 : | } | ||
1589 : | _bufferState = BUFFER_EMPTY; | ||
1590 : | break; | ||
1591 : | case "NetStream.Buffer.Flush": | ||
1592 : | case "NetStream.Buffer.Full": | ||
1593 : | if (_sawSeekNotify && _state == SEEKING) { | ||
1594 : | _bufferState = BUFFER_EMPTY; | ||
1595 : | setStateFromCachedState(); | ||
1596 : | doUpdateTime(); | ||
1597 : | } | ||
1598 : | switch (_bufferState) { | ||
1599 : | case BUFFER_EMPTY: | ||
1600 : | if ( !_hiddenForResize ) { | ||
1601 : | if ((_state == LOADING && _cachedState == PLAYING) || _state == BUFFERING) { | ||
1602 : | setState(PLAYING); | ||
1603 : | } else if (_cachedState == BUFFERING) { | ||
1604 : | _cachedState = PLAYING; | ||
1605 : | } | ||
1606 : | } | ||
1607 : | _bufferState = BUFFER_FULL; | ||
1608 : | break; | ||
1609 : | default: | ||
1610 : | //ifdef DEBUG | ||
1611 : | //debugTrace("Unexpected buffer state with " + info.code + ": " + _bufferState); | ||
1612 : | //endif | ||
1613 : | break; | ||
1614 : | } // switch (_bufferState) | ||
1615 : | break; | ||
1616 : | case "NetStream.Pause.Notify": | ||
1617 : | if (_state == RESIZING && _hiddenForResize) { | ||
1618 : | finishAutoResize(); | ||
1619 : | } | ||
1620 : | break; | ||
1621 : | case "NetStream.Play.Start": | ||
1622 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
1623 : | _rtmpDoStopAtEndIntervalID = 0; | ||
1624 : | _bufferState = BUFFER_EMPTY; | ||
1625 : | if (_startingPlay) { | ||
1626 : | _startingPlay = false; | ||
1627 : | _cachedPlayheadTime = playheadTime; | ||
1628 : | } else if (_state == PLAYING) { | ||
1629 : | setState(BUFFERING); | ||
1630 : | } | ||
1631 : | break; | ||
1632 : | case "NetStream.Play.Reset": | ||
1633 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
1634 : | _rtmpDoStopAtEndIntervalID = 0; | ||
1635 : | if (_state == REWINDING) { | ||
1636 : | clearInterval(_rtmpDoSeekIntervalID); | ||
1637 : | _rtmpDoSeekIntervalID = 0; | ||
1638 : | if (playheadTime == 0 || playheadTime < _cachedPlayheadTime) { | ||
1639 : | setStateFromCachedState(); | ||
1640 : | } else { | ||
1641 : | _cachedPlayheadTime = playheadTime; | ||
1642 : | _rtmpDoSeekIntervalID = setInterval(this, "rtmpDoSeek", RTMP_DO_SEEK_INTERVAL); | ||
1643 : | } | ||
1644 : | } | ||
1645 : | break; | ||
1646 : | case "NetStream.Seek.Notify": | ||
1647 : | if (playheadTime != _cachedPlayheadTime) { | ||
1648 : | setStateFromCachedState(); | ||
1649 : | doUpdateTime(); | ||
1650 : | } else { | ||
1651 : | _sawSeekNotify = true; | ||
1652 : | if (_rtmpDoSeekIntervalID == 0) { | ||
1653 : | _rtmpDoSeekIntervalID = setInterval(this, "rtmpDoSeek", RTMP_DO_SEEK_INTERVAL); | ||
1654 : | } | ||
1655 : | } | ||
1656 : | break; | ||
1657 : | case "Netstream.Play.UnpublishNotify": | ||
1658 : | break; | ||
1659 : | case "Netstream.Play.PublishNotify": | ||
1660 : | break; | ||
1661 : | case "NetStream.Play.StreamNotFound": | ||
1662 : | if (!_ncMgr.connectAgain()) { | ||
1663 : | setState(CONNECTION_ERROR); | ||
1664 : | } | ||
1665 : | break; | ||
1666 : | case "NetStream.Play.Failed": | ||
1667 : | case "NetStream.Failed": | ||
1668 : | setState(CONNECTION_ERROR); | ||
1669 : | break; | ||
1670 : | } // switch (info.code) | ||
1671 : | } | ||
1672 : | |||
1673 : | /** | ||
1674 : | * <p><code>NetStream.onStatus</code> callback for http. Handles | ||
1675 : | * autorewind.</p> | ||
1676 : | * | ||
1677 : | * @private | ||
1678 : | */ | ||
1679 : | public function httpOnStatus(info:Object):Void | ||
1680 : | { | ||
1681 : | //ifdef DEBUG | ||
1682 : | //debugTrace("httpOnStatus:"+info.code); | ||
1683 : | //debugTrace("_state == " + _state); | ||
1684 : | //debugTrace("playheadTime == " + playheadTime); | ||
1685 : | //debugTrace("_bufferState = " + _bufferState); | ||
1686 : | //endif | ||
1687 : | |||
1688 : | switch (info.code) { | ||
1689 : | case "NetStream.Play.Stop": | ||
1690 : | clearInterval(_delayedBufferingIntervalID); | ||
1691 : | _delayedBufferingIntervalID = 0; | ||
1692 : | if (_invalidSeekTime) { | ||
1693 : | _invalidSeekTime = false; | ||
1694 : | _invalidSeekRecovery = true; | ||
1695 : | setState(_cachedState); | ||
1696 : | seek(playheadTime); | ||
1697 : | } else { | ||
1698 : | switch (_state) { | ||
1699 : | case PLAYING: | ||
1700 : | case BUFFERING: | ||
1701 : | case SEEKING: | ||
1702 : | httpDoStopAtEnd(); | ||
1703 : | break; | ||
1704 : | } | ||
1705 : | } | ||
1706 : | break; | ||
1707 : | case "NetStream.Seek.InvalidTime": | ||
1708 : | if (_invalidSeekRecovery) { | ||
1709 : | _invalidSeekTime = false; | ||
1710 : | _invalidSeekRecovery = false; | ||
1711 : | setState(_cachedState); | ||
1712 : | seek(0); | ||
1713 : | } else { | ||
1714 : | _invalidSeekTime = true; | ||
1715 : | } | ||
1716 : | break; | ||
1717 : | case "NetStream.Buffer.Empty": | ||
1718 : | _bufferState = BUFFER_EMPTY; | ||
1719 : | if (_state == PLAYING) { | ||
1720 : | clearInterval(_delayedBufferingIntervalID); | ||
1721 : | _delayedBufferingIntervalID = setInterval(this, "doDelayedBuffering", _delayedBufferingInterval); | ||
1722 : | } | ||
1723 : | break; | ||
1724 : | case "NetStream.Buffer.Full": | ||
1725 : | case "NetStream.Buffer.Flush": | ||
1726 : | clearInterval(_delayedBufferingIntervalID); | ||
1727 : | _delayedBufferingIntervalID = 0; | ||
1728 : | _bufferState = BUFFER_FULL; | ||
1729 : | if ( !_hiddenForResize ) { | ||
1730 : | if ((_state == LOADING && _cachedState == PLAYING) || _state == BUFFERING) { | ||
1731 : | setState(PLAYING); | ||
1732 : | } else if (_cachedState == BUFFERING) { | ||
1733 : | _cachedState = PLAYING; | ||
1734 : | } | ||
1735 : | } | ||
1736 : | break; | ||
1737 : | case "NetStream.Seek.Notify": | ||
1738 : | _invalidSeekRecovery = false; | ||
1739 : | switch (_state) { | ||
1740 : | case SEEKING: | ||
1741 : | case REWINDING: | ||
1742 : | if (_httpDoSeekIntervalID == 0) { | ||
1743 : | _httpDoSeekCount = 0; | ||
1744 : | _httpDoSeekIntervalID = setInterval(this, "httpDoSeek", HTTP_DO_SEEK_INTERVAL); | ||
1745 : | } | ||
1746 : | break; | ||
1747 : | } // switch (_state) | ||
1748 : | break; | ||
1749 : | case "NetStream.Play.StreamNotFound": | ||
1750 : | setState(CONNECTION_ERROR); | ||
1751 : | break; | ||
1752 : | } // switch (info.code) | ||
1753 : | } | ||
1754 : | |||
1755 : | /** | ||
1756 : | * <p>Called by INCManager after when connection complete or | ||
1757 : | * failed after call to <code>INCManager.connectToURL</code>. | ||
1758 : | * If connection failed, set <code>INCManager.nc = null</code> | ||
1759 : | * before calling.</p> | ||
1760 : | * | ||
1761 : | * @see #ncReconnected() | ||
1762 : | * @see INCManager#connectToURL | ||
1763 : | * @see NCManager#connectToURL | ||
1764 : | */ | ||
1765 : | public function ncConnected():Void { | ||
1766 : | //ifdef DEBUG | ||
1767 : | //debugTrace("ncConnected()"); | ||
1768 : | //endif | ||
1769 : | |||
1770 : | if ( _ncMgr == null || _ncMgr == undefined || | ||
1771 : | _ncMgr.getNetConnection() == null || | ||
1772 : | _ncMgr.getNetConnection() == undefined ) { | ||
1773 : | setState(CONNECTION_ERROR); | ||
1774 : | } else { | ||
1775 : | _createStream(); | ||
1776 : | _setUpStream(); | ||
1777 : | } | ||
1778 : | } | ||
1779 : | |||
1780 : | /** | ||
1781 : | * <p>Called by INCManager after when reconnection complete or | ||
1782 : | * failed after call to <code>INCManager.reconnect</code>. If | ||
1783 : | * connection failed, set <code>INCManager.nc = null</code> | ||
1784 : | * before calling.</p> | ||
1785 : | * | ||
1786 : | * @see #ncConnected() | ||
1787 : | * @see INCManager#reconnect | ||
1788 : | * @see NCManager#reconnect | ||
1789 : | */ | ||
1790 : | public function ncReconnected():Void | ||
1791 : | { | ||
1792 : | //ifdef DEBUG | ||
1793 : | //debugTrace("reconnected called!"); | ||
1794 : | //endif | ||
1795 : | if ( _ncMgr == null || _ncMgr == undefined || | ||
1796 : | _ncMgr.getNetConnection() == null || | ||
1797 : | _ncMgr.getNetConnection() == undefined ) { | ||
1798 : | setState(CONNECTION_ERROR); | ||
1799 : | } else { | ||
1800 : | _ns = null; | ||
1801 : | _state = STOPPED; | ||
1802 : | execQueuedCmds(); | ||
1803 : | } | ||
1804 : | } | ||
1805 : | |||
1806 : | /** | ||
1807 : | * handles NetStream.onMetaData callback | ||
1808 : | * | ||
1809 : | * @private | ||
1810 : | */ | ||
1811 : | public function onMetaData(info:Object):Void { | ||
1812 : | if (_metadata != null) return; | ||
1813 : | _metadata = info; | ||
1814 : | if ( _streamLength == undefined || | ||
1815 : | _streamLength == null || | ||
1816 : | _streamLength <= 0 ) { | ||
1817 : | _streamLength = info.duration; | ||
1818 : | } | ||
1819 : | if (isNaN(_videoWidth) || _videoWidth <= 0) _videoWidth = info.width; | ||
1820 : | if (isNaN(_videoHeight) || _videoHeight <= 0) _videoHeight = info.height; | ||
1821 : | dispatchEvent({type:"metadataReceived", info:info}); | ||
1822 : | } | ||
1823 : | |||
1824 : | /** | ||
1825 : | * handles NetStream.onCuePoint callback | ||
1826 : | * | ||
1827 : | * @private | ||
1828 : | */ | ||
1829 : | public function onCuePoint(info:Object):Void { | ||
1830 : | if (!_hiddenForResize || (!isNaN(_hiddenRewindPlayheadTime) && playheadTime < _hiddenRewindPlayheadTime)) { | ||
1831 : | dispatchEvent({type:"cuePoint", info:info}); | ||
1832 : | } | ||
1833 : | } | ||
1834 : | |||
1835 : | |||
1836 : | // | ||
1837 : | // private functions | ||
1838 : | // | ||
1839 : | |||
1840 : | |||
1841 : | /** | ||
1842 : | * sets state, dispatches event, execs queued commands. Always try to call | ||
1843 : | * this AFTER you do your work, because the state might change again after | ||
1844 : | * you call this if you set it to a responsive state becasue of the call | ||
1845 : | * to exec queued commands. If you set this to a responsive state and | ||
1846 : | * then do more state based logic, check _state to make sure it did not | ||
1847 : | * change out from under you. | ||
1848 : | * | ||
1849 : | * @private | ||
1850 : | */ | ||
1851 : | private function setState(s:String):Void { | ||
1852 : | if (s == _state) return; | ||
1853 : | _hiddenRewindPlayheadTime = undefined; | ||
1854 : | _cachedState = _state; | ||
1855 : | _cachedPlayheadTime = playheadTime; | ||
1856 : | _state = s; | ||
1857 : | var newState:String = _state; | ||
1858 : | //ifdef DEBUG | ||
1859 : | //debugTrace("state = " + newState); | ||
1860 : | //debugTrace("_cachedState == " + _cachedState); | ||
1861 : | ////debugTrace("_cachedPlayheadTime == " + _cachedPlayheadTime); | ||
1862 : | //endif | ||
1863 : | dispatchEvent({type:"stateChange", state:newState, playheadTime:playheadTime}); | ||
1864 : | if (!_readyDispatched) { | ||
1865 : | switch (newState) { | ||
1866 : | case STOPPED: | ||
1867 : | case PLAYING: | ||
1868 : | case PAUSED: | ||
1869 : | case BUFFERING: | ||
1870 : | _readyDispatched = true; | ||
1871 : | dispatchEvent({type:"ready", state:newState, playheadTime:playheadTime}); | ||
1872 : | break; | ||
1873 : | } // switch | ||
1874 : | } | ||
1875 : | switch (_cachedState) { | ||
1876 : | case REWINDING: | ||
1877 : | dispatchEvent({type:"rewind", state:newState, playheadTime:playheadTime}); | ||
1878 : | if (_ncMgr.isRTMP() && newState == STOPPED) { | ||
1879 : | closeNS(); | ||
1880 : | } | ||
1881 : | break; | ||
1882 : | } // switch | ||
1883 : | switch (newState) { | ||
1884 : | case STOPPED: | ||
1885 : | case PAUSED: | ||
1886 : | if (_ncMgr.isRTMP() && _idleTimeoutIntervalID == 0) { | ||
1887 : | _idleTimeoutIntervalID = setInterval(this, "doIdleTimeout", _idleTimeoutInterval); | ||
1888 : | } | ||
1889 : | break; | ||
1890 : | case SEEKING: | ||
1891 : | case REWINDING: | ||
1892 : | _bufferState = BUFFER_EMPTY; | ||
1893 : | // no break | ||
1894 : | case PLAYING: | ||
1895 : | case BUFFERING: | ||
1896 : | if (_updateTimeIntervalID == 0) { | ||
1897 : | _updateTimeIntervalID = setInterval(this, "doUpdateTime", _updateTimeInterval); | ||
1898 : | } | ||
1899 : | // no break | ||
1900 : | case LOADING: | ||
1901 : | case RESIZING: | ||
1902 : | clearInterval(_idleTimeoutIntervalID); | ||
1903 : | _idleTimeoutIntervalID = 0; | ||
1904 : | break; | ||
1905 : | } // switch | ||
1906 : | execQueuedCmds(); | ||
1907 : | } | ||
1908 : | |||
1909 : | /** | ||
1910 : | * Sets state to _cachedState if the _cachedState is PLAYING, | ||
1911 : | * PAUSED or BUFFERING, otherwise sets state to STOPPED. | ||
1912 : | * | ||
1913 : | * @private | ||
1914 : | */ | ||
1915 : | private function setStateFromCachedState():Void { | ||
1916 : | switch (_cachedState) { | ||
1917 : | case PLAYING: | ||
1918 : | case PAUSED: | ||
1919 : | setState(_cachedState); | ||
1920 : | break; | ||
1921 : | case BUFFERING: | ||
1922 : | if (_bufferState == BUFFER_EMPTY) { | ||
1923 : | setState(BUFFERING); | ||
1924 : | } else { | ||
1925 : | setState(_cachedState); | ||
1926 : | } | ||
1927 : | break; | ||
1928 : | default: | ||
1929 : | setState(STOPPED); | ||
1930 : | break; | ||
1931 : | } | ||
1932 : | } | ||
1933 : | |||
1934 : | /** | ||
1935 : | * creates our implementatino of the <code>INCManager</code>. | ||
1936 : | * We put this off until we need to do it to give time for the | ||
1937 : | * user to customize the <code>DEFAULT_INCMANAGER</code> | ||
1938 : | * static variable. | ||
1939 : | * | ||
1940 : | * @private | ||
1941 : | */ | ||
1942 : | private function createINCManager():Void { | ||
1943 : | if (ncMgrClassName == null || ncMgrClassName == undefined) { | ||
1944 : | ncMgrClassName = DEFAULT_INCMANAGER; | ||
1945 : | } | ||
1946 : | var ncMgrConstructor:Function = eval( (ncMgrClassName) ); | ||
1947 : | _ncMgr = new ncMgrConstructor; | ||
1948 : | _ncMgr.setVideoPlayer(this); | ||
1949 : | } | ||
1950 : | |||
1951 : | /** | ||
1952 : | * <p>ONLY CALL THIS WITH RTMP STREAMING</p> | ||
1953 : | * | ||
1954 : | * <p>Has the logic for what to do when we decide we have come to | ||
1955 : | * a stop by coming to the end of an rtmp stream. There are a few | ||
1956 : | * different ways we decide this has happened, and we sometimes | ||
1957 : | * even set an interval that calls this function repeatedly to | ||
1958 : | * check if the time is still changing, which is why it has its | ||
1959 : | * own special function.</p> | ||
1960 : | * | ||
1961 : | * @private | ||
1962 : | */ | ||
1963 : | private function rtmpDoStopAtEnd(force:Boolean):Void { | ||
1964 : | //ifdef DEBUG | ||
1965 : | //debugTrace("rtmpDoStopAtEnd()"); | ||
1966 : | //endif | ||
1967 : | // check if we really want to stop if this was triggered on an | ||
1968 : | // interval. If we are running this on an interval (see | ||
1969 : | // rtmpOnStatus) we do a stop when the playhead hasn't moved | ||
1970 : | // since last time we checked, we check every .25 seconds. | ||
1971 : | if (_rtmpDoStopAtEndIntervalID > 0) { | ||
1972 : | switch (_state) { | ||
1973 : | case DISCONNECTED: | ||
1974 : | case CONNECTION_ERROR: | ||
1975 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
1976 : | _rtmpDoStopAtEndIntervalID = 0; | ||
1977 : | return; | ||
1978 : | } | ||
1979 : | if (force || _cachedPlayheadTime == playheadTime) { | ||
1980 : | clearInterval(_rtmpDoStopAtEndIntervalID); | ||
1981 : | _rtmpDoStopAtEndIntervalID = 0; | ||
1982 : | } else { | ||
1983 : | _cachedPlayheadTime = playheadTime; | ||
1984 : | return; | ||
1985 : | } | ||
1986 : | } | ||
1987 : | _bufferState = BUFFER_EMPTY; | ||
1988 : | _atEnd = true; | ||
1989 : | // all this triggers callbacks, so need to keep checking if | ||
1990 : | // _state == STOPPED--if no longer, then we bail | ||
1991 : | setState(STOPPED); | ||
1992 : | if (_state != STOPPED) return; | ||
1993 : | doUpdateTime(); | ||
1994 : | if (_state != STOPPED) return; | ||
1995 : | dispatchEvent({type:"complete", state:_state, playheadTime:playheadTime}); | ||
1996 : | if (_state != STOPPED) return; | ||
1997 : | if (_autoRewind && !_isLive && playheadTime != 0) { | ||
1998 : | _atEnd = false; | ||
1999 : | _currentPos = 0; | ||
2000 : | _play(0, 0); | ||
2001 : | setState(REWINDING); | ||
2002 : | } else { | ||
2003 : | closeNS(); | ||
2004 : | } | ||
2005 : | } | ||
2006 : | |||
2007 : | /** | ||
2008 : | * <p>ONLY CALL THIS WITH RTMP STREAMING</p> | ||
2009 : | * | ||
2010 : | * <p>Wait until time goes back to zero to leave rewinding state.</p> | ||
2011 : | * | ||
2012 : | * @private | ||
2013 : | */ | ||
2014 : | private function rtmpDoSeek():Void { | ||
2015 : | //ifdef DEBUG | ||
2016 : | //debugTrace("rtmpDoSeek()"); | ||
2017 : | //endif | ||
2018 : | if (_state != REWINDING && _state != SEEKING) { | ||
2019 : | clearInterval(_rtmpDoSeekIntervalID); | ||
2020 : | _rtmpDoSeekIntervalID = 0; | ||
2021 : | _sawSeekNotify = false; | ||
2022 : | } else if (playheadTime != _cachedPlayheadTime) { | ||
2023 : | clearInterval(_rtmpDoSeekIntervalID); | ||
2024 : | _rtmpDoSeekIntervalID = 0; | ||
2025 : | _sawSeekNotify = false; | ||
2026 : | setStateFromCachedState(); | ||
2027 : | doUpdateTime(); | ||
2028 : | } | ||
2029 : | } | ||
2030 : | |||
2031 : | /** | ||
2032 : | * <p>ONLY CALL THIS WITH HTTP PROGRESSIVE DOWNLOAD</p> | ||
2033 : | * | ||
2034 : | * <p>Call this when playing stops by hitting the end.</p> | ||
2035 : | * | ||
2036 : | * @private | ||
2037 : | */ | ||
2038 : | private function httpDoStopAtEnd():Void { | ||
2039 : | //ifdef DEBUG | ||
2040 : | //debugTrace("httpDoStopAtEnd()"); | ||
2041 : | //endif | ||
2042 : | _atEnd = true; | ||
2043 : | if ( _streamLength == undefined || | ||
2044 : | _streamLength == null || | ||
2045 : | _streamLength <= 0 ) { | ||
2046 : | _streamLength = _ns.time; | ||
2047 : | } | ||
2048 : | _pause(true); | ||
2049 : | setState(STOPPED); | ||
2050 : | if (_state != STOPPED) return; | ||
2051 : | doUpdateTime(); | ||
2052 : | if (_state != STOPPED) return; | ||
2053 : | dispatchEvent({type:"complete", state:_state, playheadTime:playheadTime}); | ||
2054 : | if (_state != STOPPED) return; | ||
2055 : | if (_autoRewind) { | ||
2056 : | _atEnd = false; | ||
2057 : | _pause(true); | ||
2058 : | _seek(0); | ||
2059 : | setState(REWINDING); | ||
2060 : | } | ||
2061 : | } | ||
2062 : | |||
2063 : | /** | ||
2064 : | * <p>ONLY CALL THIS WITH HTTP PROGRESSIVE DOWNLOAD</p> | ||
2065 : | * | ||
2066 : | * <p>If we get an onStatus callback indicating a seek is over, | ||
2067 : | * but the playheadTime has not updated yet, then we wait on a | ||
2068 : | * timer before moving forward.</p> | ||
2069 : | * | ||
2070 : | * @private | ||
2071 : | */ | ||
2072 : | private function httpDoSeek():Void { | ||
2073 : | //ifdef DEBUG | ||
2074 : | //debugTrace("httpDoSeek()"); | ||
2075 : | //debugTrace("playheadTime = " + playheadTime); | ||
2076 : | //debugTrace("_cachedPlayheadTime = " + _cachedPlayheadTime); | ||
2077 : | //endif | ||
2078 : | var seekState:Boolean = (_state == REWINDING || _state == SEEKING); | ||
2079 : | // if seeking or rewinding, then need to wait for playhead time to | ||
2080 : | // change or for timeout | ||
2081 : | if ( seekState && _httpDoSeekCount < HTTP_DO_SEEK_MAX_COUNT && | ||
2082 : | (_cachedPlayheadTime == playheadTime || _invalidSeekTime) ) { | ||
2083 : | _httpDoSeekCount++; | ||
2084 : | return; | ||
2085 : | } | ||
2086 : | |||
2087 : | // reset | ||
2088 : | _httpDoSeekCount = 0; | ||
2089 : | clearInterval(_httpDoSeekIntervalID); | ||
2090 : | _httpDoSeekIntervalID = 0; | ||
2091 : | |||
2092 : | // only do the rest if were seeking or rewinding to start with | ||
2093 : | if (!seekState) return; | ||
2094 : | |||
2095 : | setStateFromCachedState(); | ||
2096 : | if (_invalidSeekTime) { | ||
2097 : | _invalidSeekTime = false; | ||
2098 : | _invalidSeekRecovery = true; | ||
2099 : | seek(playheadTime); | ||
2100 : | } else { | ||
2101 : | doUpdateTime(); | ||
2102 : | } | ||
2103 : | } | ||
2104 : | |||
2105 : | /** | ||
2106 : | * <p>Wrapper for <code>NetStream.close()</code>. Never call | ||
2107 : | * <code>NetStream.close()</code> directly, always call this | ||
2108 : | * method because it does some other housekeeping.</p> | ||
2109 : | * | ||
2110 : | * @private | ||
2111 : | */ | ||
2112 : | private function closeNS(updateCurrentPos:Boolean):Void { | ||
2113 : | //ifdef DEBUG | ||
2114 : | //debugTrace("closeNS()"); | ||
2115 : | //endif | ||
2116 : | if (_ns != null && _ns != undefined) { | ||
2117 : | if (updateCurrentPos) { | ||
2118 : | clearInterval(_updateTimeIntervalID); | ||
2119 : | _updateTimeIntervalID = 0; | ||
2120 : | doUpdateTime(); | ||
2121 : | _currentPos = _ns.time; | ||
2122 : | } | ||
2123 : | delete _ns.onStatus; | ||
2124 : | _ns.onStatus = null; | ||
2125 : | _ns.close(); | ||
2126 : | _ns = null; | ||
2127 : | } | ||
2128 : | } | ||
2129 : | |||
2130 : | /** | ||
2131 : | * <p>We do a brief timer before entering BUFFERING state to avoid | ||
2132 : | * quick switches from BUFFERING to PLAYING and back.</p> | ||
2133 : | * | ||
2134 : | * @private | ||
2135 : | */ | ||
2136 : | private function doDelayedBuffering():Void { | ||
2137 : | //ifdef DEBUG | ||
2138 : | //debugTrace("doDelayedBuffering()"); | ||
2139 : | //endif | ||
2140 : | switch (_state) { | ||
2141 : | case LOADING: | ||
2142 : | case RESIZING: | ||
2143 : | // if loading or resizing, still at beginning so keep whirring, might go into buffering state | ||
2144 : | break; | ||
2145 : | case PLAYING: | ||
2146 : | // still in that playing state, let's go to buffering | ||
2147 : | clearInterval(_delayedBufferingIntervalID); | ||
2148 : | _delayedBufferingIntervalID = 0; | ||
2149 : | setState(BUFFERING); | ||
2150 : | break; | ||
2151 : | default: | ||
2152 : | // any other state, bail and kill timer | ||
2153 : | clearInterval(_delayedBufferingIntervalID); | ||
2154 : | _delayedBufferingIntervalID = 0; | ||
2155 : | break; | ||
2156 : | } | ||
2157 : | } | ||
2158 : | |||
2159 : | /** | ||
2160 : | * Wrapper for <code>NetStream.pause()</code>. Never call | ||
2161 : | * <code>NetStream.pause()</code> directly, always call this | ||
2162 : | * method because it does some other housekeeping. | ||
2163 : | * | ||
2164 : | * @private | ||
2165 : | */ | ||
2166 : | private function _pause(doPause:Boolean):Void { | ||
2167 : | //ifdef DEBUG | ||
2168 : | //debugTrace("_pause(" + doPause + ")"); | ||
2169 : | //endif | ||
2170 : | _ns.pause(doPause); | ||
2171 : | } | ||
2172 : | |||
2173 : | /** | ||
2174 : | * Wrapper for <code>NetStream.play()</code>. Never call | ||
2175 : | * <code>NetStream.play()</code> directly, always call this | ||
2176 : | * method because it does some other housekeeping. | ||
2177 : | * | ||
2178 : | * @private | ||
2179 : | */ | ||
2180 : | private function _play():Void { | ||
2181 : | //ifdef DEBUG | ||
2182 : | //var debugString:String = "_play(" | ||
2183 : | //if (arguments.length > 0) { | ||
2184 : | // debugString += arguments[0]; | ||
2185 : | // if (arguments.length > 1) { | ||
2186 : | // debugString += ", " + arguments[1]; | ||
2187 : | // } | ||
2188 : | //} | ||
2189 : | //debugString += ")"; | ||
2190 : | //debugTrace(debugString); | ||
2191 : | //debugTrace("_ncMgr.getStreamName() = " + _ncMgr.getStreamName()); | ||
2192 : | //endif | ||
2193 : | _startingPlay = true; | ||
2194 : | switch (arguments.length) { | ||
2195 : | case 0: | ||
2196 : | _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : 0, -1); | ||
2197 : | break; | ||
2198 : | case 1: | ||
2199 : | _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : arguments[0], -1); | ||
2200 : | break; | ||
2201 : | case 2: | ||
2202 : | _ns.play(_ncMgr.getStreamName(), (_isLive) ? -1 : arguments[0], arguments[1]); | ||
2203 : | break; | ||
2204 : | default: | ||
2205 : | throw new Error("bad args to _play"); | ||
2206 : | } | ||
2207 : | } | ||
2208 : | |||
2209 : | /** | ||
2210 : | * Wrapper for <code>NetStream.seek()</code>. Never call | ||
2211 : | * <code>NetStream.seek()</code> directly, always call | ||
2212 : | * this method because it does some other housekeeping. | ||
2213 : | * | ||
2214 : | * @private | ||
2215 : | */ | ||
2216 : | private function _seek(time:Number):Void { | ||
2217 : | //ifdef DEBUG | ||
2218 : | //debugTrace("_seek(" + time + ")"); | ||
2219 : | //endif | ||
2220 : | if (_metadata.audiodelay != undefined && time + _metadata.audiodelay < _streamLength) { | ||
2221 : | time += _metadata.audiodelay; | ||
2222 : | } | ||
2223 : | _ns.seek(time); | ||
2224 : | _invalidSeekTime = false; | ||
2225 : | _bufferState = BUFFER_EMPTY; | ||
2226 : | _sawSeekNotify = false; | ||
2227 : | } | ||
2228 : | |||
2229 : | /** | ||
2230 : | * Gets whether connected to a stream. If not, then calls to APIs | ||
2231 : | * <code>play() with no args</code>, <code>stop()</code>, | ||
2232 : | * <code>pause()</code> and <code>seek()</code> will throw | ||
2233 : | * exceptions. | ||
2234 : | * | ||
2235 : | * @see #stateResponsive | ||
2236 : | * @private | ||
2237 : | */ | ||
2238 : | private function isXnOK():Boolean { | ||
2239 : | if (_state == LOADING) return true; | ||
2240 : | if (_state == CONNECTION_ERROR) return false; | ||
2241 : | if (_state != DISCONNECTED) { | ||
2242 : | if ( _ncMgr == null || _ncMgr == undefined || | ||
2243 : | _ncMgr.getNetConnection() == null || | ||
2244 : | _ncMgr.getNetConnection() == undefined || | ||
2245 : | !_ncMgr.getNetConnection().isConnected ) { | ||
2246 : | setState(DISCONNECTED); | ||
2247 : | return false; | ||
2248 : | } | ||
2249 : | return true; | ||
2250 : | } | ||
2251 : | return false; | ||
2252 : | } | ||
2253 : | |||
2254 : | /** | ||
2255 : | * Kicks off autoresize process | ||
2256 : | * | ||
2257 : | * @private | ||
2258 : | */ | ||
2259 : | private function startAutoResize() { | ||
2260 : | switch (_state) { | ||
2261 : | case DISCONNECTED: | ||
2262 : | case CONNECTION_ERROR: | ||
2263 : | // autoresize will happen later automatically | ||
2264 : | return; | ||
2265 : | default: | ||
2266 : | _autoResizeDone = false; | ||
2267 : | if (stateResponsive && _videoWidth != undefined && _videoHeight != undefined) { | ||
2268 : | // do it now! | ||
2269 : | doAutoResize(); | ||
2270 : | } else { | ||
2271 : | // do it on an interval, it won't happen until we are | ||
2272 : | // back in a responsive state | ||
2273 : | clearInterval(_autoResizeIntervalID); | ||
2274 : | _autoResizeIntervalID = setInterval(this, "doAutoResize", AUTO_RESIZE_INTERVAL); | ||
2275 : | break; | ||
2276 : | } | ||
2277 : | } | ||
2278 : | } | ||
2279 : | |||
2280 : | /** | ||
2281 : | * <p>Does the actual work of resetting the width and height.</p> | ||
2282 : | * | ||
2283 : | * <p>Called on an interval which is stopped when width and height | ||
2284 : | * of the <code>Video</code> object are not zero. Finishing the | ||
2285 : | * resize is done in another method which is either called on a | ||
2286 : | * interval set up here for live streams or on a | ||
2287 : | * NetStream.Play.Stop event in <code>rtmpOnStatus</code> after | ||
2288 : | * stream is rewound if it is not a live stream. Still need to | ||
2289 : | * get a http solution.</p> | ||
2290 : | * | ||
2291 : | * @private | ||
2292 : | */ | ||
2293 : | private function doAutoResize():Void { | ||
2294 : | //ifdef DEBUG | ||
2295 : | //debugTrace("doAutoResize(), _video.width = " + _video.width + ", _video.height = " + _video.height); | ||
2296 : | //endif | ||
2297 : | |||
2298 : | if (_autoResizeIntervalID > 0) { | ||
2299 : | switch (_state) { | ||
2300 : | case RESIZING: | ||
2301 : | case LOADING: | ||
2302 : | break; | ||
2303 : | case DISCONNECTED: | ||
2304 : | case CONNECTION_ERROR: | ||
2305 : | // autoresize will happen later automatically | ||
2306 : | clearInterval(_autoResizeIntervalID); | ||
2307 : | _autoResizeIntervalID = 0; | ||
2308 : | return; | ||
2309 : | default: | ||
2310 : | if (!stateResponsive) { | ||
2311 : | // keep trying until we get into a responsive state | ||
2312 : | return; | ||
2313 : | } | ||
2314 : | } | ||
2315 : | if ( _video.width != _prevVideoWidth || _video.height != _prevVideoHeight || | ||
2316 : | _bufferState >= BUFFER_FULL || _ns.time > AUTO_RESIZE_PLAYHEAD_TIMEOUT ) { | ||
2317 : | // if have not received metadata yet, slight delay to avoid race condition in player | ||
2318 : | // but there may not be any metadata, so cannot wait forever | ||
2319 : | if (_hiddenForResize && _metadata == null && _hiddenForResizeMetadataDelay < AUTO_RESIZE_METADATA_DELAY_MAX) { | ||
2320 : | //ifdef DEBUG | ||
2321 : | //debugTrace("Delaying for metadata: " + _hiddenForResizeMetadataDelay); | ||
2322 : | //endif | ||
2323 : | _hiddenForResizeMetadataDelay++; | ||
2324 : | return; | ||
2325 : | } | ||
2326 : | _videoWidth = _video.width; | ||
2327 : | _videoHeight = _video.height; | ||
2328 : | clearInterval(_autoResizeIntervalID); | ||
2329 : | _autoResizeIntervalID = 0; | ||
2330 : | } else { | ||
2331 : | // keep trying until our size is set | ||
2332 : | return; | ||
2333 : | } | ||
2334 : | } | ||
2335 : | // do not need to do autoresize, but DO need to signal readyness | ||
2336 : | if ((!_autoSize && !_aspectRatio) || _autoResizeDone) { | ||
2337 : | setState(_cachedState); | ||
2338 : | return; | ||
2339 : | } | ||
2340 : | //ifdef DEBUG | ||
2341 : | //debugTrace("Actually doing autoResize, _videoWidth = " + _videoWidth + ", _videoHeight = " + _videoHeight); | ||
2342 : | //endif | ||
2343 : | _autoResizeDone = true; | ||
2344 : | if (_autoSize) { | ||
2345 : | _video._width = _videoWidth; | ||
2346 : | _video._height = _videoHeight; | ||
2347 : | } else if (_aspectRatio) { | ||
2348 : | var newWidth:Number = (_videoWidth * height / _videoHeight); | ||
2349 : | var newHeight:Number = (_videoHeight * width / _videoWidth); | ||
2350 : | if (newHeight < height) { | ||
2351 : | _video._height = newHeight; | ||
2352 : | } else if (newWidth < width) { | ||
2353 : | _video._width = newWidth; | ||
2354 : | } | ||
2355 : | } | ||
2356 : | if (_hiddenForResize) { | ||
2357 : | _hiddenRewindPlayheadTime = playheadTime; | ||
2358 : | if (_state == LOADING) { | ||
2359 : | _cachedState = PLAYING; | ||
2360 : | } | ||
2361 : | if (!_ncMgr.isRTMP()) { | ||
2362 : | _pause(true); | ||
2363 : | _seek(0); | ||
2364 : | clearInterval(_finishAutoResizeIntervalID); | ||
2365 : | _finishAutoResizeIntervalID = setInterval(this, "finishAutoResize", FINISH_AUTO_RESIZE_INTERVAL); | ||
2366 : | } else if (!_isLive) { | ||
2367 : | _currentPos = 0; | ||
2368 : | _play(0, 0); | ||
2369 : | setState(RESIZING) | ||
2370 : | } else if (_autoPlay) { | ||
2371 : | clearInterval(_finishAutoResizeIntervalID); | ||
2372 : | _finishAutoResizeIntervalID = setInterval(this, "finishAutoResize", FINISH_AUTO_RESIZE_INTERVAL); | ||
2373 : | } else { | ||
2374 : | finishAutoResize(); | ||
2375 : | } | ||
2376 : | } else { | ||
2377 : | dispatchEvent({type:"resize", x:_x, y:_y, width:_width, height:_height}); | ||
2378 : | } | ||
2379 : | } | ||
2380 : | |||
2381 : | /** | ||
2382 : | * <p>Makes video visible, turns on sound and starts | ||
2383 : | * playing if live or autoplay.</p> | ||
2384 : | */ | ||
2385 : | private function finishAutoResize():Void { | ||
2386 : | //ifdef DEBUG | ||
2387 : | //debugTrace("finishAutoResize()"); | ||
2388 : | //endif | ||
2389 : | clearInterval(_finishAutoResizeIntervalID); | ||
2390 : | _finishAutoResizeIntervalID = 0; | ||
2391 : | if (stateResponsive) return; | ||
2392 : | _visible = __visible; | ||
2393 : | _sound.setVolume(_volume); | ||
2394 : | _hiddenForResize = false; | ||
2395 : | //ifdef DEBUG | ||
2396 : | //debugTrace("_autoPlay = " + _autoPlay); | ||
2397 : | //endif | ||
2398 : | dispatchEvent({type:"resize", x:_x, y:_y, width:_width, height:_height}); | ||
2399 : | if (_autoPlay) { | ||
2400 : | if (_ncMgr.isRTMP()) { | ||
2401 : | if (!_isLive) { | ||
2402 : | _currentPos = 0; | ||
2403 : | _play(0); | ||
2404 : | } | ||
2405 : | if (_state == RESIZING) { | ||
2406 : | setState(LOADING); | ||
2407 : | _cachedState = PLAYING; | ||
2408 : | } | ||
2409 : | } else { | ||
2410 : | _pause(false); | ||
2411 : | _cachedState = PLAYING; | ||
2412 : | } | ||
2413 : | } else { | ||
2414 : | setState(STOPPED); | ||
2415 : | } | ||
2416 : | } | ||
2417 : | |||
2418 : | /** | ||
2419 : | * <p>Creates <code>NetStream</code> and does some basic | ||
2420 : | * initialization.</p> | ||
2421 : | * | ||
2422 : | * @private | ||
2423 : | */ | ||
2424 : | private function _createStream():Void { | ||
2425 : | //ifdef DEBUG | ||
2426 : | //debugTrace("_createStream()"); | ||
2427 : | //endif | ||
2428 : | _ns = new NetStream(_ncMgr.getNetConnection()); | ||
2429 : | _ns.mc = this; | ||
2430 : | if (_ncMgr.isRTMP()) { | ||
2431 : | _ns.onStatus = function(info:Object):Void { this.mc.rtmpOnStatus(info); }; | ||
2432 : | } else { | ||
2433 : | _ns.onStatus = function(info:Object):Void { this.mc.httpOnStatus(info); }; | ||
2434 : | } | ||
2435 : | _ns.onMetaData = function (info:Object) { this.mc.onMetaData(info); }; | ||
2436 : | _ns.onCuePoint = function (info:Object) { this.mc.onCuePoint(info); }; | ||
2437 : | _ns.setBufferTime(_bufferTime); | ||
2438 : | } | ||
2439 : | |||
2440 : | /** | ||
2441 : | * <p>Does initialization after first connecting to the server | ||
2442 : | * and creating the stream. Will get the stream duration from | ||
2443 : | * the <code>INCManager</code> if it has it for us.</p> | ||
2444 : | * | ||
2445 : | * <p>Starts resize if necessary, otherwise starts playing if | ||
2446 : | * necessary, otherwise loads first frame of video. In http case, | ||
2447 : | * starts progressive download in any case.</p> | ||
2448 : | * | ||
2449 : | * @private | ||
2450 : | */ | ||
2451 : | private function _setUpStream():Void { | ||
2452 : | //ifdef DEBUG | ||
2453 : | //debugTrace("_setUpStream()"); | ||
2454 : | //endif | ||
2455 : | |||
2456 : | _video.attachVideo(_ns); | ||
2457 : | this.attachAudio(_ns); | ||
2458 : | |||
2459 : | // INCManager MIGHT have gotten the stream length, width and height for | ||
2460 : | // us. If its length is null, undefined or < 0, then it did not. | ||
2461 : | if ( !isNaN(_ncMgr.getStreamLength()) && _ncMgr.getStreamLength() >= 0 ) { | ||
2462 : | _streamLength = _ncMgr.getStreamLength(); | ||
2463 : | } | ||
2464 : | if ( !isNaN(_ncMgr.getStreamWidth()) && _ncMgr.getStreamWidth() >= 0 ) { | ||
2465 : | _videoWidth = _ncMgr.getStreamWidth(); | ||
2466 : | } else { | ||
2467 : | _videoWidth = undefined; | ||
2468 : | } | ||
2469 : | if ( !isNaN(_ncMgr.getStreamHeight()) && _ncMgr.getStreamHeight() >= 0 ) { | ||
2470 : | _videoHeight = _ncMgr.getStreamHeight(); | ||
2471 : | } else { | ||
2472 : | _videoHeight = undefined; | ||
2473 : | } | ||
2474 : | |||
2475 : | // resize immediately if height and width set above | ||
2476 : | if ((_autoSize || _aspectRatio) && _videoWidth != undefined && _videoHeight != undefined) { | ||
2477 : | _prevVideoWidth = undefined; | ||
2478 : | _prevVideoHeight = undefined; | ||
2479 : | doAutoResize(); | ||
2480 : | } | ||
2481 : | |||
2482 : | // just start if static, start resize otherwise | ||
2483 : | if ((!_autoSize && !_aspectRatio) || (_videoWidth != undefined && _videoHeight != undefined)) { | ||
2484 : | if (_autoPlay) { | ||
2485 : | if (!_ncMgr.isRTMP()) { | ||
2486 : | _cachedState = BUFFERING; | ||
2487 : | _play(); | ||
2488 : | } else if (_isLive) { | ||
2489 : | _cachedState = BUFFERING; | ||
2490 : | _play(-1); | ||
2491 : | } else { | ||
2492 : | _cachedState = BUFFERING; | ||
2493 : | _play(0); | ||
2494 : | } | ||
2495 : | } else { | ||
2496 : | _cachedState = STOPPED; | ||
2497 : | if (_ncMgr.isRTMP()) { | ||
2498 : | _play(0, 0); | ||
2499 : | } else { | ||
2500 : | _play(); | ||
2501 : | _pause(true); | ||
2502 : | _seek(0); | ||
2503 : | } | ||
2504 : | } | ||
2505 : | } else { | ||
2506 : | _hiddenForResize = true; | ||
2507 : | _hiddenForResizeMetadataDelay = 0; | ||
2508 : | __visible = _visible; | ||
2509 : | _visible = false; | ||
2510 : | _volume = _sound.getVolume(); | ||
2511 : | _sound.setVolume(0); | ||
2512 : | _play(0); | ||
2513 : | if (_currentPos > 0) { | ||
2514 : | _seek(_currentPos); | ||
2515 : | _currentPos = 0; | ||
2516 : | } | ||
2517 : | } | ||
2518 : | clearInterval(_autoResizeIntervalID); | ||
2519 : | _autoResizeIntervalID = setInterval(this, "doAutoResize", AUTO_RESIZE_INTERVAL); | ||
2520 : | } | ||
2521 : | |||
2522 : | /** | ||
2523 : | * <p>ONLY CALL THIS WITH RTMP STREAMING</p> | ||
2524 : | * | ||
2525 : | * <p>Only used for rtmp connections. When we pause or stop, | ||
2526 : | * setup an interval to call this after a delay (see property | ||
2527 : | * <code>idleTimeout</code>). We do this to spare the server from | ||
2528 : | * having a bunch of extra xns hanging around, although this needs | ||
2529 : | * to be balanced with the load that creating connections puts on | ||
2530 : | * the server, and keep in mind that FCS can be configured to | ||
2531 : | * terminate idle connections on its own, which is a better way to | ||
2532 : | * manage the issue.</p> | ||
2533 : | * | ||
2534 : | * @private | ||
2535 : | */ | ||
2536 : | private function doIdleTimeout():Void | ||
2537 : | { | ||
2538 : | //ifdef DEBUG | ||
2539 : | //debugTrace("Closing NetConnection NOW"); | ||
2540 : | //endif | ||
2541 : | clearInterval(_idleTimeoutIntervalID); | ||
2542 : | _idleTimeoutIntervalID = 0; | ||
2543 : | close(); | ||
2544 : | } | ||
2545 : | |||
2546 : | /** | ||
2547 : | * Dumps all queued commands without executing them | ||
2548 : | * | ||
2549 : | * @private | ||
2550 : | */ | ||
2551 : | private function flushQueuedCmds():Void { | ||
2552 : | //ifdef DEBUG | ||
2553 : | //debugTrace("flushQueuedCmds()"); | ||
2554 : | //endif | ||
2555 : | while (_cmdQueue.length > 0) _cmdQueue.pop(); | ||
2556 : | } | ||
2557 : | |||
2558 : | /** | ||
2559 : | * Executes as many queued commands as possible, obviously | ||
2560 : | * stopping when state becomes unresponsive. | ||
2561 : | * | ||
2562 : | * @private | ||
2563 : | */ | ||
2564 : | private function execQueuedCmds():Void { | ||
2565 : | //ifdef DEBUG | ||
2566 : | //debugTrace("execQueuedCmds()"); | ||
2567 : | //endif | ||
2568 : | while ( _cmdQueue.length > 0 && (stateResponsive || _state == CONNECTION_ERROR) && | ||
2569 : | ( (_cmdQueue[0].url != null && _cmdQueue[0].url != undefined) || | ||
2570 : | (_state != DISCONNECTED && _state != CONNECTION_ERROR) ) ) { | ||
2571 : | //ifdef DEBUG | ||
2572 : | //debugTrace("Exec Queued Command!"); | ||
2573 : | //endif | ||
2574 : | var nextCmd:Object = _cmdQueue.shift(); | ||
2575 : | _cachedState = _state; | ||
2576 : | _state = EXEC_QUEUED_CMD; | ||
2577 : | switch (nextCmd.type) { | ||
2578 : | case PLAY: | ||
2579 : | this.play(nextCmd.url, nextCmd.isLive, nextCmd.time); | ||
2580 : | break; | ||
2581 : | case LOAD: | ||
2582 : | this.load(nextCmd.url, nextCmd.isLive, nextCmd.time); | ||
2583 : | break; | ||
2584 : | case PAUSE: | ||
2585 : | this.pause(); | ||
2586 : | break; | ||
2587 : | case STOP: | ||
2588 : | this.stop(); | ||
2589 : | break; | ||
2590 : | case SEEK: | ||
2591 : | this.seek(nextCmd.time); | ||
2592 : | break; | ||
2593 : | } // switch | ||
2594 : | } | ||
2595 : | } | ||
2596 : | |||
2597 : | private function queueCmd( type:Number, url:String, isLive:Boolean, time:Number):Void { | ||
2598 : | //ifdef DEBUG | ||
2599 : | //debugTrace("queueCmd(" + type + ", " + url + ", " + isLive + ", " + time + ")"); | ||
2600 : | //endif | ||
2601 : | _cmdQueue.push( {type:type, url:url, isLive:false, time:time} ); | ||
2602 : | } | ||
2603 : | |||
2604 : | //ifdef DEBUG | ||
2605 : | //function debugTrace(s:String):Void { | ||
2606 : | //if (_parent != null && _parent != undefined) { | ||
2607 : | // _parent.debugTrace(s); | ||
2608 : | //} | ||
2609 : | //} | ||
2610 : | //endif | ||
2611 : | |||
2612 : | } // class mx.video.VideoPlayer |
cvs-admin | ViewVC Help |
Powered by ViewVC 1.0.0 |