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.video.*; | ||
9 : | |||
10 : | /** | ||
11 : | * <p>Creates <code>NetConnection</code> for <code>VideoPlayer</code>, a | ||
12 : | * helper class for that user facing class.</p> | ||
13 : | * | ||
14 : | * <p>NCManager supports a subset of SMIL to handle multiple streams | ||
15 : | * for multiple bandwidths. NCManager assumes any URL that does not | ||
16 : | * being with "rtmp://" and does not end with ".flv" is a SMIL url. | ||
17 : | * See SMILParser for more on SMIL support.</p> | ||
18 : | * | ||
19 : | * @see SMILParser | ||
20 : | */ | ||
21 : | |||
22 : | class mx.video.NCManager implements INCManager { | ||
23 : | |||
24 : | #include "ComponentVersion.as" | ||
25 : | |||
26 : | // my VideoPlayer | ||
27 : | private var _owner:VideoPlayer; | ||
28 : | |||
29 : | // server connection info | ||
30 : | private var _contentPath:String; | ||
31 : | private var _protocol:String; | ||
32 : | private var _serverName:String; | ||
33 : | private var _portNumber:String; | ||
34 : | private var _wrappedURL:String; | ||
35 : | private var _appName:String; | ||
36 : | private var _streamName:String; | ||
37 : | private var _streamLength:Number; | ||
38 : | private var _streamWidth:Number; | ||
39 : | private var _streamHeight:Number; | ||
40 : | private var _streams:Array; | ||
41 : | private var _isRTMP:Boolean; | ||
42 : | private var _smilMgr:SMILManager; | ||
43 : | private var _bitrate:Number; | ||
44 : | |||
45 : | /** | ||
46 : | * <p>fallbackServerName is exposed in two ways:</p> | ||
47 : | * | ||
48 : | * <p>User can supply second <meta base> in smil and that base | ||
49 : | * attr will be taken as the fallbackServerName (note that only | ||
50 : | * the server name will be taken from this and not the application | ||
51 : | * name or anything else).</p> | ||
52 : | * | ||
53 : | * <p>The second way is the user can directly set this by | ||
54 : | * accessing the ncMgr property in FLVPlayback or VideoPlayer and | ||
55 : | * set fallbackServerName property directly.</p> | ||
56 : | */ | ||
57 : | public var fallbackServerName:String; | ||
58 : | |||
59 : | // interval for xn timeout | ||
60 : | private var _timeoutIntervalId:Number; | ||
61 : | private var _timeout:Number; | ||
62 : | |||
63 : | /** | ||
64 : | * Default connection timeout in milliseconds. | ||
65 : | * | ||
66 : | * @see #getTimeout() | ||
67 : | * @see #setTimeout() | ||
68 : | */ | ||
69 : | public var DEFAULT_TIMEOUT:Number = 60000; | ||
70 : | |||
71 : | // bandwidth detection stuff | ||
72 : | public var _payload:Number; | ||
73 : | private var _autoSenseBW:Boolean; | ||
74 : | |||
75 : | // info on successful xn | ||
76 : | private var _nc:NetConnection; | ||
77 : | private var _ncUri:String; | ||
78 : | |||
79 : | // info on mult xns we try | ||
80 : | public var _tryNC:Array; | ||
81 : | private var _tryNCIntervalId:Number; | ||
82 : | |||
83 : | // List of connections tried by connectRTMP, in order tried | ||
84 : | private static var RTMP_CONN:Array = [ | ||
85 : | { protocol: "rtmp:/", port:"1935" } | ||
86 : | , { protocol: "rtmp:/", port:"443" } | ||
87 : | , { protocol: "rtmpt:/", port:"80" } | ||
88 : | , { protocol: "rtmps:/", port:"443" } | ||
89 : | ]; | ||
90 : | |||
91 : | // Counter that tracks the next type to try in RTMP_CONN. | ||
92 : | private var _connTypeCounter:Number; | ||
93 : | |||
94 : | public function NCManager() { | ||
95 : | initNCInfo(); | ||
96 : | initOtherInfo(); | ||
97 : | |||
98 : | // intervals | ||
99 : | _timeoutIntervalId = 0; | ||
100 : | _tryNCIntervalId = 0; | ||
101 : | |||
102 : | // actually calls setter | ||
103 : | _timeout = DEFAULT_TIMEOUT; | ||
104 : | } | ||
105 : | |||
106 : | private function initNCInfo():Void { | ||
107 : | _isRTMP = undefined; | ||
108 : | _serverName = undefined; | ||
109 : | _wrappedURL = undefined; | ||
110 : | _portNumber = undefined; | ||
111 : | _appName = undefined; | ||
112 : | } | ||
113 : | |||
114 : | private function initOtherInfo():Void { | ||
115 : | _contentPath = undefined; | ||
116 : | _streamName = undefined; | ||
117 : | _streamLength = undefined; | ||
118 : | _streamWidth = undefined; | ||
119 : | _streamHeight = undefined; | ||
120 : | _streams = undefined; | ||
121 : | _autoSenseBW = false; | ||
122 : | |||
123 : | _payload = 0; | ||
124 : | _connTypeCounter = 0; | ||
125 : | cleanConns(); | ||
126 : | } | ||
127 : | |||
128 : | /* | ||
129 : | * @see INCManager#getTimeout() | ||
130 : | */ | ||
131 : | public function getTimeout():Number { | ||
132 : | return _timeout; | ||
133 : | } | ||
134 : | |||
135 : | /* | ||
136 : | * @see INCManager#setTimeout() | ||
137 : | */ | ||
138 : | public function setTimeout(t:Number):Void { | ||
139 : | _timeout = t; | ||
140 : | if (_timeoutIntervalId != 0) { | ||
141 : | clearInterval(_timeoutIntervalId); | ||
142 : | _timeoutIntervalId = setInterval(this, "_onFCSConnectTimeOut", _timeout); | ||
143 : | } | ||
144 : | } | ||
145 : | |||
146 : | /** | ||
147 : | * For RTMP streams, returns value calculated from autodetection, | ||
148 : | * not value set via setBitrate(). | ||
149 : | * | ||
150 : | * @see INCManager#getBitrate() | ||
151 : | */ | ||
152 : | public function getBitrate():Number { | ||
153 : | return _bitrate; | ||
154 : | } | ||
155 : | |||
156 : | /** | ||
157 : | * This value is only used with progressive download (HTTP), with | ||
158 : | * RTMP streaming uses autodetection. | ||
159 : | * | ||
160 : | * @see INCManager#getBitrate() | ||
161 : | */ | ||
162 : | public function setBitrate(b:Number):Void { | ||
163 : | if (_isRTMP == undefined || !_isRTMP) { | ||
164 : | _bitrate = b; | ||
165 : | } | ||
166 : | } | ||
167 : | |||
168 : | /** | ||
169 : | * @see INCManager#getVideoPlayer() | ||
170 : | */ | ||
171 : | public function getVideoPlayer():VideoPlayer { | ||
172 : | return _owner; | ||
173 : | } | ||
174 : | |||
175 : | /** | ||
176 : | * @see INCManager#setVideoPlayer() | ||
177 : | */ | ||
178 : | public function setVideoPlayer(v:VideoPlayer):Void { | ||
179 : | _owner = v; | ||
180 : | } | ||
181 : | |||
182 : | /** | ||
183 : | * @see INCManager#getNetConnection() | ||
184 : | */ | ||
185 : | public function getNetConnection():NetConnection { | ||
186 : | return _nc; | ||
187 : | } | ||
188 : | |||
189 : | /** | ||
190 : | * @see INCManager#getStreamName() | ||
191 : | */ | ||
192 : | public function getStreamName():String { | ||
193 : | return _streamName; | ||
194 : | } | ||
195 : | |||
196 : | /** | ||
197 : | * @see INCManager#isRTMP() | ||
198 : | */ | ||
199 : | public function isRTMP():Boolean { | ||
200 : | return _isRTMP; | ||
201 : | } | ||
202 : | |||
203 : | /** | ||
204 : | * @see INCManager#getStreamLength() | ||
205 : | */ | ||
206 : | public function getStreamLength():Number { | ||
207 : | return _streamLength; | ||
208 : | } | ||
209 : | |||
210 : | /** | ||
211 : | * @see INCManager#getStreamWidth() | ||
212 : | */ | ||
213 : | public function getStreamWidth():Number { | ||
214 : | return _streamWidth; | ||
215 : | } | ||
216 : | |||
217 : | /** | ||
218 : | * @see INCManager#getStreamHeight() | ||
219 : | */ | ||
220 : | public function getStreamHeight():Number { | ||
221 : | return _streamHeight; | ||
222 : | } | ||
223 : | |||
224 : | /** | ||
225 : | * @see INCManager#connectToURL() | ||
226 : | */ | ||
227 : | public function connectToURL(url:String):Boolean { | ||
228 : | //ifdef DEBUG | ||
229 : | //debugTrace("connectToURL(" + url + ")"); | ||
230 : | //endif | ||
231 : | |||
232 : | // init | ||
233 : | initOtherInfo(); | ||
234 : | _contentPath = url; | ||
235 : | if (_contentPath == null || _contentPath == undefined || _contentPath == "") { | ||
236 : | throw new VideoError(VideoError.INVALID_CONTENT_PATH); | ||
237 : | } | ||
238 : | |||
239 : | // parse URL to determine what to do with it | ||
240 : | var parseResults:Object = parseURL(_contentPath); | ||
241 : | if (parseResults.streamName == undefined || parseResults.streamName == "") { | ||
242 : | throw new VideoError(VideoError.INVALID_CONTENT_PATH, url); | ||
243 : | } | ||
244 : | |||
245 : | // connect to either rtmp or http or download and parse smil | ||
246 : | if (parseResults.isRTMP) { | ||
247 : | var canReuse:Boolean = canReuseOldConnection(parseResults); | ||
248 : | _isRTMP = true; | ||
249 : | _protocol = parseResults.protocol; | ||
250 : | _streamName = parseResults.streamName; | ||
251 : | _serverName = parseResults.serverName; | ||
252 : | _wrappedURL = parseResults.wrappedURL; | ||
253 : | _portNumber = parseResults.portNumber; | ||
254 : | _appName = parseResults.appName; | ||
255 : | if ( _appName == undefined || _appName == "" || | ||
256 : | _streamName == undefined || _streamName == "" ) { | ||
257 : | throw new VideoError(VideoError.INVALID_CONTENT_PATH, url); | ||
258 : | } | ||
259 : | _autoSenseBW = (_streamName.indexOf(",") != -1); | ||
260 : | return (canReuse || connectRTMP()); | ||
261 : | } else { | ||
262 : | if (parseResults.streamName.slice(-4).toLowerCase() == ".flv") { | ||
263 : | var canReuse:Boolean = canReuseOldConnection(parseResults); | ||
264 : | _isRTMP = false; | ||
265 : | _streamName = parseResults.streamName; | ||
266 : | return (canReuse || connectHTTP()); | ||
267 : | } else { | ||
268 : | _smilMgr = new SMILManager(this); | ||
269 : | return _smilMgr.connectXML(parseResults.streamName); | ||
270 : | } | ||
271 : | } | ||
272 : | } | ||
273 : | |||
274 : | /** | ||
275 : | * @see INCManager#connectAgain() | ||
276 : | */ | ||
277 : | public function connectAgain():Boolean | ||
278 : | { | ||
279 : | //ifdef DEBUG | ||
280 : | //debugTrace("connectAgain()"); | ||
281 : | //endif | ||
282 : | |||
283 : | var slashIndex:Number = _appName.indexOf("/"); | ||
284 : | if (slashIndex < 0) { | ||
285 : | // return the appName and streamName back to original form | ||
286 : | // so we can start this process all over again with the | ||
287 : | // fallback server if necessary | ||
288 : | slashIndex = _streamName.indexOf("/"); | ||
289 : | if (slashIndex >= 0) { | ||
290 : | _appName += "/"; | ||
291 : | _appName += _streamName.slice(0, slashIndex); | ||
292 : | _streamName = _streamName.slice(slashIndex + 1); | ||
293 : | } | ||
294 : | return false; | ||
295 : | } | ||
296 : | |||
297 : | var newStreamName = _appName.slice(slashIndex + 1); | ||
298 : | newStreamName += "/"; | ||
299 : | newStreamName += _streamName; | ||
300 : | _streamName = newStreamName; | ||
301 : | _appName = _appName.slice(0, slashIndex); | ||
302 : | close(); | ||
303 : | _payload = 0; | ||
304 : | _connTypeCounter = 0; | ||
305 : | cleanConns(); | ||
306 : | connectRTMP(); | ||
307 : | return true; | ||
308 : | } | ||
309 : | |||
310 : | /** | ||
311 : | * @see INCManager#reconnect() | ||
312 : | */ | ||
313 : | public function reconnect():Void | ||
314 : | { | ||
315 : | //ifdef DEBUG | ||
316 : | //debugTrace("reconnect()"); | ||
317 : | //endif | ||
318 : | |||
319 : | if (!_isRTMP) { | ||
320 : | throw new Error("Cannot call reconnect on an http connection"); | ||
321 : | } | ||
322 : | _nc.onStatus = function(info:Object):Void { this.mc.reconnectOnStatus(this, info); }; | ||
323 : | _nc.onBWDone = function():Void { this.mc.onReconnected(); }; | ||
324 : | //ifdef DEBUG | ||
325 : | //debugTrace("_ncUri = " + _ncUri); | ||
326 : | //endif | ||
327 : | _nc.connect(_ncUri, false); | ||
328 : | } | ||
329 : | |||
330 : | /** | ||
331 : | * dispatches reconnect event, called by | ||
332 : | * <code>NetConnection.onBWDone</code> | ||
333 : | * | ||
334 : | * @private | ||
335 : | */ | ||
336 : | public function onReconnected():Void { | ||
337 : | delete _nc.onStatus; | ||
338 : | delete _nc.onBWDone; | ||
339 : | _owner.ncReconnected(); | ||
340 : | } | ||
341 : | |||
342 : | /** | ||
343 : | * @see INCManager#close | ||
344 : | */ | ||
345 : | public function close():Void { | ||
346 : | if (_nc) { | ||
347 : | _nc.close(); | ||
348 : | } | ||
349 : | } | ||
350 : | |||
351 : | /** | ||
352 : | * Called by <code>SMILManager</code> when done. | ||
353 : | * | ||
354 : | * @see INCManager#helperDone() | ||
355 : | */ | ||
356 : | public function helperDone(helper:Object, success:Boolean) { | ||
357 : | if (helper != _smilMgr) return; | ||
358 : | |||
359 : | if (!success) { | ||
360 : | _nc = undefined; | ||
361 : | _owner.ncConnected(); | ||
362 : | delete _smilMgr; | ||
363 : | return; | ||
364 : | } | ||
365 : | |||
366 : | // success! | ||
367 : | |||
368 : | // grab width and height | ||
369 : | _streamWidth = _smilMgr.width; | ||
370 : | _streamHeight = _smilMgr.height; | ||
371 : | |||
372 : | // get correct streamname | ||
373 : | var parseResults:Object; | ||
374 : | var url:String = _smilMgr.baseURLAttr[0]; | ||
375 : | |||
376 : | if (url != undefined && url != "") { | ||
377 : | parseResults = parseURL(url); | ||
378 : | _isRTMP = parseResults.isRTMP; | ||
379 : | _streamName = parseResults.streamName; | ||
380 : | if (_isRTMP) { | ||
381 : | _protocol = parseResults.protocol; | ||
382 : | _serverName = parseResults.serverName; | ||
383 : | _portNumber = parseResults.portNumber; | ||
384 : | _wrappedURL = parseResults.wrappedURL; | ||
385 : | _appName = parseResults.appName; | ||
386 : | if (_appName == undefined || _appName == "") { | ||
387 : | throw new VideoError(VideoError.INVALID_XML, "Base RTMP URL must include application name: " + url); | ||
388 : | } | ||
389 : | if (_smilMgr.baseURLAttr.length > 1) { | ||
390 : | var parseResults:Object = parseURL(_smilMgr.baseURLAttr[1]); | ||
391 : | if (parseResults.serverName != undefined) { | ||
392 : | fallbackServerName = parseResults.serverName; | ||
393 : | } | ||
394 : | } | ||
395 : | } | ||
396 : | } | ||
397 : | _streams = _smilMgr.videoTags; | ||
398 : | for (var i:Number = 0; i < _streams.length; i++) { | ||
399 : | url = _streams[i].src; | ||
400 : | parseResults = parseURL(url); | ||
401 : | if (_isRTMP == undefined) { | ||
402 : | _isRTMP = parseResults.isRTMP; | ||
403 : | if (_isRTMP) { | ||
404 : | _protocol = parseResults.protocol; | ||
405 : | if (_streams.length > 1) { | ||
406 : | throw new VideoError(VideoError.INVALID_XML, "Cannot switch between multiple absolute RTMP URLs, must use meta tag base attribute."); | ||
407 : | } | ||
408 : | _serverName = parseResults.serverName; | ||
409 : | _portNumber = parseResults.portNumber; | ||
410 : | _wrappedURL = parseResults.wrappedURL; | ||
411 : | _appName = parseResults.appName; | ||
412 : | if (_appName == undefined || _appName == "") { | ||
413 : | throw new VideoError(VideoError.INVALID_XML, "Base RTMP URL must include application name: " + url); | ||
414 : | } | ||
415 : | } | ||
416 : | } else if ( _streamName != undefined && _streamName != "" && | ||
417 : | !parseResults.isRelative && _streams.length > 1 ) { | ||
418 : | throw new VideoError(VideoError.INVALID_XML, "When using meta tag base attribute, cannot use absolute URLs for video or ref tag src attributes."); | ||
419 : | } | ||
420 : | _streams[i].parseResults = parseResults; | ||
421 : | } | ||
422 : | _autoSenseBW = _streams.length > 1; | ||
423 : | |||
424 : | if (!_autoSenseBW) { | ||
425 : | if (_streamName != undefined) { | ||
426 : | _streamName += _streams[0].parseResults.streamName; | ||
427 : | } else { | ||
428 : | _streamName = _streams[0].parseResults.streamName; | ||
429 : | } | ||
430 : | _streamLength = _streams[0].dur; | ||
431 : | } | ||
432 : | if (_isRTMP) { | ||
433 : | connectRTMP(); | ||
434 : | } else { | ||
435 : | if (_autoSenseBW) bitrateMatch(); | ||
436 : | connectHTTP(); | ||
437 : | _owner.ncConnected(); | ||
438 : | } | ||
439 : | } | ||
440 : | |||
441 : | /** | ||
442 : | * matches bitrate with stream | ||
443 : | * | ||
444 : | * @private | ||
445 : | */ | ||
446 : | private function bitrateMatch():Void { | ||
447 : | var whichStream:Number; | ||
448 : | if (isNaN(_bitrate)) { | ||
449 : | whichStream = 0; | ||
450 : | } else { | ||
451 : | for (var j:Number = 0; j < _streams.length; j++) { | ||
452 : | if (isNaN(_streams[j].bitrate) || _bitrate <= _streams[j].bitrate) { | ||
453 : | whichStream = j; | ||
454 : | break; | ||
455 : | } | ||
456 : | } | ||
457 : | } | ||
458 : | if (isNaN(whichStream)) { | ||
459 : | throw new VideoError(VideoError.NO_BITRATE_MATCH); | ||
460 : | } | ||
461 : | if (_streamName != undefined) { | ||
462 : | _streamName += _streams[whichStream].src; | ||
463 : | } else { | ||
464 : | _streamName = _streams[whichStream].src; | ||
465 : | } | ||
466 : | _streamLength = _streams[whichStream].dur; | ||
467 : | } | ||
468 : | |||
469 : | /** | ||
470 : | * <p>Parses URL to determine if it is http or rtmp. If it is rtmp, | ||
471 : | * breaks it into pieces to extract server URL and port, application | ||
472 : | * name and stream name. If .flv is at the end of an rtmp URL, it | ||
473 : | * will be stripped off.</p> | ||
474 : | * | ||
475 : | * @private | ||
476 : | */ | ||
477 : | private function parseURL(url:String):Object { | ||
478 : | //ifdef DEBUG | ||
479 : | //debugTrace("parseURL()"); | ||
480 : | //endif | ||
481 : | |||
482 : | var parseResults = new Object(); | ||
483 : | |||
484 : | // get protocol | ||
485 : | var startIndex:Number = 0; | ||
486 : | var endIndex:Number = url.indexOf(":/", startIndex); | ||
487 : | if (endIndex >= 0) { | ||
488 : | endIndex += 2; | ||
489 : | parseResults.protocol = url.slice(startIndex, endIndex); | ||
490 : | parseResults.isRelative = false; | ||
491 : | } else { | ||
492 : | parseResults.isRelative = true; | ||
493 : | } | ||
494 : | |||
495 : | if ( parseResults.protocol != undefined && | ||
496 : | ( parseResults.protocol == "rtmp:/" || | ||
497 : | parseResults.protocol == "rtmpt:/" || | ||
498 : | parseResults.protocol == "rtmps:/" ) ) { | ||
499 : | parseResults.isRTMP = true; | ||
500 : | |||
501 : | startIndex = endIndex; | ||
502 : | |||
503 : | if (url.charAt(startIndex) == '/') { | ||
504 : | startIndex++; | ||
505 : | // get server (and maybe port) | ||
506 : | var colonIndex:Number = url.indexOf(":", startIndex); | ||
507 : | var slashIndex:Number = url.indexOf("/", startIndex); | ||
508 : | if (slashIndex < 0) { | ||
509 : | if (colonIndex < 0) { | ||
510 : | parseResults.serverName = url.slice(startIndex); | ||
511 : | } else { | ||
512 : | endIndex = colonIndex; | ||
513 : | parseResults.portNumber = url.slice(startIndex, endIndex); | ||
514 : | startIndex = endIndex + 1; | ||
515 : | parseResults.serverName = url.slice(startIndex); | ||
516 : | } | ||
517 : | return parseResults; | ||
518 : | } | ||
519 : | if (colonIndex >= 0 && colonIndex < slashIndex) { | ||
520 : | endIndex = colonIndex; | ||
521 : | parseResults.serverName = url.slice(startIndex, endIndex); | ||
522 : | startIndex = endIndex + 1; | ||
523 : | endIndex = slashIndex; | ||
524 : | parseResults.portNumber = url.slice(startIndex, endIndex); | ||
525 : | } else { | ||
526 : | endIndex = slashIndex; | ||
527 : | parseResults.serverName = url.slice(startIndex, endIndex); | ||
528 : | } | ||
529 : | startIndex = endIndex + 1; | ||
530 : | } | ||
531 : | |||
532 : | // handle wrapped RTMP servers bit recursively, if it is there | ||
533 : | if (url.charAt(startIndex) == '?') { | ||
534 : | var subURL = url.slice(startIndex + 1); | ||
535 : | var subParseResults = parseURL(subURL); | ||
536 : | if (subParseResults.protocol == undefined || !subParseResults.isRTMP) { | ||
537 : | throw new VideoError(VideoError.INVALID_CONTENT_PATH, url); | ||
538 : | } | ||
539 : | parseResults.wrappedURL = "?"; | ||
540 : | parseResults.wrappedURL += subParseResults.protocol; | ||
541 : | if (subParseResults.server != undefined) { | ||
542 : | parseResults.wrappedURL += "/"; | ||
543 : | parseResults.wrappedURL += subParseResults.server; | ||
544 : | } | ||
545 : | if (subParseResults.wrappedURL != undefined) { | ||
546 : | parseResults.wrappedURL += "/?"; | ||
547 : | parseResults.wrappedURL += subParseResults.wrappedURL; | ||
548 : | } | ||
549 : | parseResults.appName = subParseResults.appName; | ||
550 : | parseResults.streamName = subParseResults.streamName; | ||
551 : | return parseResults; | ||
552 : | } | ||
553 : | |||
554 : | // get application name | ||
555 : | endIndex = url.indexOf("/", startIndex); | ||
556 : | if (endIndex < 0) { | ||
557 : | parseResults.appName = url.slice(startIndex); | ||
558 : | return parseResults; | ||
559 : | } | ||
560 : | parseResults.appName = url.slice(startIndex, endIndex); | ||
561 : | startIndex = endIndex + 1; | ||
562 : | |||
563 : | // check for instance name to be added to application name | ||
564 : | endIndex = url.indexOf("/", startIndex); | ||
565 : | if (endIndex < 0) { | ||
566 : | parseResults.streamName = url.slice(startIndex); | ||
567 : | return parseResults; | ||
568 : | } | ||
569 : | parseResults.appName += "/"; | ||
570 : | parseResults.appName += url.slice(startIndex, endIndex); | ||
571 : | startIndex = endIndex + 1; | ||
572 : | |||
573 : | // get flv name | ||
574 : | parseResults.streamName = url.slice(startIndex); | ||
575 : | |||
576 : | } else { | ||
577 : | // is http, just return the full url received as streamName | ||
578 : | parseResults.isRTMP = false; | ||
579 : | parseResults.streamName = url; | ||
580 : | } | ||
581 : | return parseResults; | ||
582 : | } | ||
583 : | |||
584 : | /** | ||
585 : | * <p>Compares connection info with previous NetConnection, | ||
586 : | * will reuse existing connection if possible. | ||
587 : | */ | ||
588 : | private function canReuseOldConnection(parseResults:Object):Boolean { | ||
589 : | // no reuse if no prior connection | ||
590 : | if (_nc == undefined || _nc == null) return false; | ||
591 : | |||
592 : | // http connection | ||
593 : | if (!parseResults.isRTMP) { | ||
594 : | // can reuse if prev connection was http | ||
595 : | if (!_isRTMP) return true; | ||
596 : | // cannot reuse if was rtmp--close | ||
597 : | _owner.close(); | ||
598 : | _nc = null; | ||
599 : | initNCInfo(); | ||
600 : | return false; | ||
601 : | } | ||
602 : | |||
603 : | // rtmp connection | ||
604 : | if (_isRTMP) { | ||
605 : | if ( parseResults.serverName == _serverName && parseResults.appName == _appName && | ||
606 : | parseResults.protocol == _protocol && parseResults.portNumber == _portNumber && | ||
607 : | parseResults.wrappedURL == _wrappedURL ) { | ||
608 : | return true; | ||
609 : | } | ||
610 : | // cannot reuse this rtmp--close | ||
611 : | _owner.close(); | ||
612 : | _nc = null; | ||
613 : | } | ||
614 : | |||
615 : | initNCInfo(); | ||
616 : | return false; | ||
617 : | } | ||
618 : | |||
619 : | /** | ||
620 : | * <p>Handles creating <code>NetConnection</code> instance for | ||
621 : | * progressive download of FLV via http.</p> | ||
622 : | * | ||
623 : | * @private | ||
624 : | */ | ||
625 : | private function connectHTTP():Boolean { | ||
626 : | //ifdef DEBUG | ||
627 : | //debugTrace("connectHTTP()"); | ||
628 : | //endif | ||
629 : | |||
630 : | _nc = new NetConnection(); | ||
631 : | _nc.connect(null); | ||
632 : | return true; | ||
633 : | } | ||
634 : | |||
635 : | /** | ||
636 : | * <p>Top level function for creating <code>NetConnection</code> | ||
637 : | * instance for progressive download of FLV via rtmp. Actually | ||
638 : | * tries to create several different connections using different | ||
639 : | * protocols and ports in a pipeline, so multiple connection | ||
640 : | * attempts may be occurring simultaneously, and will use the | ||
641 : | * first one that connects successfully.</p> | ||
642 : | * | ||
643 : | * @private | ||
644 : | */ | ||
645 : | private function connectRTMP():Boolean { | ||
646 : | //ifdef DEBUG | ||
647 : | //debugTrace("connectRTMP()"); | ||
648 : | //endif | ||
649 : | |||
650 : | // setup timeout | ||
651 : | clearInterval(_timeoutIntervalId); | ||
652 : | _timeoutIntervalId = setInterval(this, "_onFCSConnectTimeOut", _timeout); | ||
653 : | |||
654 : | _tryNC = new Array(); | ||
655 : | for (var i:Number = 0; i < RTMP_CONN.length; i++) { | ||
656 : | //ifdef DEBUG | ||
657 : | //debugTrace("Creating connection " + i); | ||
658 : | //endif | ||
659 : | _tryNC[i] = new NetConnection(); | ||
660 : | _tryNC[i].mc = this; | ||
661 : | _tryNC[i].pending = false; | ||
662 : | _tryNC[i].connIndex = i; | ||
663 : | _tryNC[i].onBWDone = function(p_bw:Number):Void { | ||
664 : | this.mc.onConnected(this, p_bw); | ||
665 : | } | ||
666 : | _tryNC[i].onBWCheck = function():Number { | ||
667 : | return ++this.mc._payload; | ||
668 : | } | ||
669 : | _tryNC[i].onStatus = function(info:Object):Void { this.mc.connectOnStatus(this, info); }; | ||
670 : | } | ||
671 : | |||
672 : | nextConnect(); | ||
673 : | return false; | ||
674 : | } | ||
675 : | |||
676 : | /** | ||
677 : | * <p>Does work of trying to open rtmp connections. Called either | ||
678 : | * by <code>connectRTMP</code> or on an interval set up in | ||
679 : | * that method.</p> | ||
680 : | * | ||
681 : | * <p>For creating rtmp connections.</p> | ||
682 : | * | ||
683 : | * @see #connectRTMP() | ||
684 : | * @private | ||
685 : | */ | ||
686 : | private function nextConnect():Void { | ||
687 : | //ifdef DEBUG | ||
688 : | //debugTrace("nextConnect()"); | ||
689 : | //endif | ||
690 : | |||
691 : | clearInterval(_tryNCIntervalId); | ||
692 : | _tryNCIntervalId = 0; | ||
693 : | var protocol:String; | ||
694 : | var port:String; | ||
695 : | if (_connTypeCounter == 0) { | ||
696 : | protocol = _protocol; | ||
697 : | if (_portNumber != undefined) { | ||
698 : | port = _portNumber; | ||
699 : | } else { | ||
700 : | for (var i:Number = 0; i < RTMP_CONN.length; i++) { | ||
701 : | if (protocol == RTMP_CONN[i].protocol) { | ||
702 : | port = RTMP_CONN[i].port; | ||
703 : | break; | ||
704 : | } | ||
705 : | } | ||
706 : | } | ||
707 : | } else { | ||
708 : | protocol = RTMP_CONN[_connTypeCounter].protocol; | ||
709 : | port = RTMP_CONN[_connTypeCounter].port; | ||
710 : | } | ||
711 : | var xnURL:String = protocol + ((_serverName == undefined) ? "" : "/" + _serverName + ":" + port + "/") + ((_wrappedURL == undefined) ? "" : _wrappedURL + "/") + _appName; | ||
712 : | //ifdef DEBUG | ||
713 : | //debugTrace( "_tryNC[" + _connTypeCounter + "] connecting to room: " + xnURL ); | ||
714 : | //endif | ||
715 : | _tryNC[_connTypeCounter].pending = true; | ||
716 : | _tryNC[_connTypeCounter].connect( xnURL, _autoSenseBW); | ||
717 : | if (_connTypeCounter < (RTMP_CONN.length-1)) { | ||
718 : | _connTypeCounter++; | ||
719 : | _tryNCIntervalId = setInterval(this, "nextConnect", 1500); | ||
720 : | } | ||
721 : | } | ||
722 : | |||
723 : | /** | ||
724 : | * <p>Stops all intervals, closes all unneeded connections, and other | ||
725 : | * cleanup related to the <code>connectRTMP</code> strategy of | ||
726 : | * pipelining connection attempts to different protocols and | ||
727 : | * ports.</p> | ||
728 : | * | ||
729 : | * <p>For creating rtmp connections.</p> | ||
730 : | * | ||
731 : | * @see #connectRTMP() | ||
732 : | * @private | ||
733 : | */ | ||
734 : | public function cleanConns() { | ||
735 : | //ifdef DEBUG | ||
736 : | //debugTrace("cleanConns()"); | ||
737 : | //endif | ||
738 : | |||
739 : | clearInterval(_tryNCIntervalId); | ||
740 : | _tryNCIntervalId = 0; | ||
741 : | if (_tryNC != undefined) { | ||
742 : | for (var i:Number = 0; i < _tryNC.length; i++) { | ||
743 : | if (_tryNC[i] != undefined) { | ||
744 : | //ifdef DEBUG | ||
745 : | //debugTrace("_tryNC[" + i + "] = " + _tryNC[i]); | ||
746 : | //endif | ||
747 : | delete _tryNC[i].onStatus; | ||
748 : | if (_tryNC[i].pending) { | ||
749 : | _tryNC[i].onStatus = function(info:Object):Void { this.mc.disconnectOnStatus(this, info); }; | ||
750 : | } else { | ||
751 : | delete _tryNC[i].onStatus; | ||
752 : | _tryNC[i].close(); | ||
753 : | } | ||
754 : | } | ||
755 : | delete _tryNC[i]; | ||
756 : | } | ||
757 : | delete _tryNC; | ||
758 : | } | ||
759 : | } | ||
760 : | |||
761 : | /** | ||
762 : | * <p>Starts another pipelined connection attempt with | ||
763 : | * <code>connectRTMP</code> with the fallback server.</p> | ||
764 : | * | ||
765 : | * <p>For creating rtmp connections.</p> | ||
766 : | * | ||
767 : | * @see #connectRTMP() | ||
768 : | * @private | ||
769 : | */ | ||
770 : | private function tryFallBack():Void { | ||
771 : | //ifdef DEBUG | ||
772 : | //debugTrace("tryFallBack()"); | ||
773 : | //endif | ||
774 : | |||
775 : | if (_serverName == fallbackServerName || fallbackServerName == undefined || fallbackServerName == null) { | ||
776 : | //ifdef DEBUG | ||
777 : | //debugTrace("Already tried to fall back!"); | ||
778 : | //endif | ||
779 : | //it's not connected | ||
780 : | delete _nc; | ||
781 : | _nc = undefined; | ||
782 : | _owner.ncConnected(); | ||
783 : | } else { | ||
784 : | _connTypeCounter = 0; | ||
785 : | cleanConns(); | ||
786 : | _serverName = fallbackServerName; | ||
787 : | //ifdef DEBUG | ||
788 : | //debugTrace("connect: " + _serverName); | ||
789 : | //endif | ||
790 : | connectRTMP(); | ||
791 : | } | ||
792 : | } | ||
793 : | |||
794 : | /** | ||
795 : | * <p>Starts another pipelined connection attempt with | ||
796 : | * <code>connectRTMP</code> with the fallback server.</p> | ||
797 : | * | ||
798 : | * <p>For creating rtmp connections.</p> | ||
799 : | * | ||
800 : | * @see #connectRTMP() | ||
801 : | * @private | ||
802 : | */ | ||
803 : | public function onConnected(p_nc:NetConnection, p_bw:Number):Void | ||
804 : | { | ||
805 : | //ifdef DEBUG | ||
806 : | //debugTrace("onConnected()"); | ||
807 : | //endif | ||
808 : | |||
809 : | // avoid timeout | ||
810 : | clearInterval(_timeoutIntervalId); | ||
811 : | _timeoutIntervalId = 0; | ||
812 : | |||
813 : | // ditch these now unneeded functions and listeners | ||
814 : | delete p_nc.onBWDone; | ||
815 : | delete p_nc.onBWCheck; | ||
816 : | delete p_nc.onStatus; | ||
817 : | |||
818 : | // store pointers to the successful xn and uri | ||
819 : | _nc = p_nc; | ||
820 : | _ncUri = _nc.uri; | ||
821 : | |||
822 : | if (_autoSenseBW) { | ||
823 : | _bitrate = p_bw * 1024; | ||
824 : | |||
825 : | if (_streams != undefined) { | ||
826 : | bitrateMatch(); | ||
827 : | } else if (_streamName.indexOf(",") != -1) { | ||
828 : | var sSplit:Array = _streamName.split(","); | ||
829 : | // remove leading and trailing whitespace from string | ||
830 : | for (var i:Number = 0; i < sSplit.length; i+=2) { | ||
831 : | var sName = stripFrontAndBackWhiteSpace(sSplit[i]); | ||
832 : | if (i + 1 < sSplit.length) { | ||
833 : | // If we have less bw than the next threshold or if | ||
834 : | // there isn't another threshold (last string) | ||
835 : | if (p_bw <= Number(sSplit[i+1])) { | ||
836 : | _streamName = sName; | ||
837 : | break; | ||
838 : | } | ||
839 : | } else { | ||
840 : | _streamName = sName; | ||
841 : | break; | ||
842 : | } | ||
843 : | } // for | ||
844 : | } | ||
845 : | } | ||
846 : | |||
847 : | // strip off .flv if included | ||
848 : | if (_streamName.slice(-4).toLowerCase() == ".flv") { | ||
849 : | _streamName = _streamName.slice(0, -4); | ||
850 : | } | ||
851 : | |||
852 : | // if we need to get the stream length from the server, do it here | ||
853 : | if (!_owner.isLive && _streamLength == undefined) { | ||
854 : | var res:Object = new Object(); | ||
855 : | res.mc = this; | ||
856 : | res.onResult = function(length:Number) { this.mc.getStreamLengthResult(length); }; | ||
857 : | _nc.call("getStreamLength", res, _streamName); | ||
858 : | } else { | ||
859 : | _owner.ncConnected(); | ||
860 : | } | ||
861 : | } | ||
862 : | |||
863 : | /** | ||
864 : | * netStatus event listener when connecting | ||
865 : | * | ||
866 : | * @private | ||
867 : | */ | ||
868 : | public function connectOnStatus(target:NetConnection, info:Object):Void { | ||
869 : | //ifdef DEBUG | ||
870 : | //debugTrace("_tryNC["+target.connIndex+"].onStatus: " + info.code); | ||
871 : | //var stuff; | ||
872 : | //for (stuff in info) { | ||
873 : | // debugTrace("info[" + stuff + "] = " + info[stuff]); | ||
874 : | //} | ||
875 : | //endif | ||
876 : | target.pending = false; | ||
877 : | |||
878 : | if (info.code == "NetConnection.Connect.Success") { | ||
879 : | //ifdef DEBUG | ||
880 : | //debugTrace( "Connection " + RTMP_CONN[target.connIndex].protocol + | ||
881 : | // ":" + RTMP_CONN[target.connIndex].port + " succeeded!" ); | ||
882 : | //endif | ||
883 : | _nc = _tryNC[target.connIndex]; | ||
884 : | _tryNC[target.connIndex] = undefined; | ||
885 : | cleanConns(); | ||
886 : | } else if ( ( (info.code == "NetConnection.Connect.Failed") || | ||
887 : | (info.code == "NetConnection.Connect.Rejected") ) && | ||
888 : | ( target.connIndex == (RTMP_CONN.length - 1) ) ) { | ||
889 : | // Try rearranging the app URL, then the fallbackServer | ||
890 : | if (!connectAgain()) { | ||
891 : | tryFallBack(); | ||
892 : | } | ||
893 : | } else { | ||
894 : | //ifdef DEBUG | ||
895 : | //debugTrace( RTMP_CONN[target.connIndex].protocol + ":" + | ||
896 : | // RTMP_CONN[target.connIndex].port + | ||
897 : | // " onStatus:" + info.code); | ||
898 : | //endif | ||
899 : | } | ||
900 : | } | ||
901 : | |||
902 : | /** | ||
903 : | * netStatus event listener when reconnecting | ||
904 : | * | ||
905 : | * @private | ||
906 : | */ | ||
907 : | public function reconnectOnStatus(target:NetConnection, info:Object):Void | ||
908 : | { | ||
909 : | //ifdef DEBUG | ||
910 : | //debugTrace("reconnectOnStatus: " + info.code); | ||
911 : | //endif | ||
912 : | if ( (info.code == "NetConnection.Connect.Failed") || | ||
913 : | (info.code == "NetConnection.Connect.Rejected") ) { | ||
914 : | // Try the fallbackServer | ||
915 : | delete _nc; | ||
916 : | _nc = undefined; | ||
917 : | _owner.ncReconnected(); | ||
918 : | } | ||
919 : | } | ||
920 : | |||
921 : | /** | ||
922 : | * netStatus event listener for disconnecting extra | ||
923 : | * NetConnections that were opened in parallel | ||
924 : | * | ||
925 : | * @private | ||
926 : | */ | ||
927 : | public function disconnectOnStatus(target:NetConnection, info:Object):Void | ||
928 : | { | ||
929 : | //ifdef DEBUG | ||
930 : | //debugTrace("disconnectOnStatus: " + info.code); | ||
931 : | //endif | ||
932 : | if (info.code == "NetConnection.Connect.Success") { | ||
933 : | delete target.onStatus; | ||
934 : | //ifdef DEBUG | ||
935 : | //debugTrace("Closing myself"); | ||
936 : | //endif | ||
937 : | target.close(); | ||
938 : | } | ||
939 : | } | ||
940 : | |||
941 : | /** | ||
942 : | * Responder function to receive streamLength result from | ||
943 : | * server after making rpc | ||
944 : | * | ||
945 : | * @private | ||
946 : | */ | ||
947 : | public function getStreamLengthResult(length:Number):Void { | ||
948 : | //ifdef DEBUG | ||
949 : | //debugTrace("getStreamLengthResult(" + length + ")"); | ||
950 : | //endif | ||
951 : | _streamLength = length; | ||
952 : | _owner.ncConnected(); | ||
953 : | } | ||
954 : | |||
955 : | /** | ||
956 : | * <p>Called on interval to timeout all connection attempts.</p> | ||
957 : | * | ||
958 : | * <p>For creating rtmp connections.</p> | ||
959 : | * | ||
960 : | * @see #connectRTMP() | ||
961 : | * @private | ||
962 : | */ | ||
963 : | public function _onFCSConnectTimeOut():Void | ||
964 : | { | ||
965 : | //ifdef DEBUG | ||
966 : | //debugTrace("_onFCSConnectTimeOut()"); | ||
967 : | //endif | ||
968 : | cleanConns(); | ||
969 : | _nc = undefined; | ||
970 : | if (!connectAgain()) { | ||
971 : | _owner.ncConnected(); | ||
972 : | } | ||
973 : | } | ||
974 : | |||
975 : | private static function stripFrontAndBackWhiteSpace(p_str:String):String | ||
976 : | { | ||
977 : | var i:Number; | ||
978 : | var l:Number = p_str.length; | ||
979 : | var startIndex:Number = 0 | ||
980 : | var endIndex:Number = l; | ||
981 : | for (i = 0; i < l; i++) { | ||
982 : | switch (p_str.charCodeAt(i)) { | ||
983 : | case 9: // tab | ||
984 : | case 10: // new line | ||
985 : | case 13: // carriage return | ||
986 : | case 32: // space | ||
987 : | continue; | ||
988 : | } | ||
989 : | startIndex = i; | ||
990 : | break; | ||
991 : | } | ||
992 : | |||
993 : | for (i = l; i >= 0; i--) { | ||
994 : | switch (p_str.charCodeAt(i)) { | ||
995 : | case 9: // tab | ||
996 : | case 10: // new line | ||
997 : | case 13: // carriage return | ||
998 : | case 32: // space | ||
999 : | continue; | ||
1000 : | } | ||
1001 : | endIndex = i + 1; | ||
1002 : | break; | ||
1003 : | } | ||
1004 : | |||
1005 : | if (endIndex <= startIndex) { | ||
1006 : | return ""; | ||
1007 : | } | ||
1008 : | return p_str.slice(startIndex, endIndex); | ||
1009 : | } | ||
1010 : | |||
1011 : | //ifdef DEBUG | ||
1012 : | //public function debugTrace(s:String):Void | ||
1013 : | //{ | ||
1014 : | // if (_owner != undefined) { | ||
1015 : | // _owner.debugTrace("#NCManager# " + s); | ||
1016 : | // } | ||
1017 : | //} | ||
1018 : | //endif | ||
1019 : | } // class mx.video.NCManager |
cvs-admin | ViewVC Help |
Powered by ViewVC 1.0.0 |