/*****************************************************
*
* Copyright 2009 Adobe Systems Incorporated. All Rights Reserved.
*
*****************************************************
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
*
* The Initial Developer of the Original Code is Adobe Systems Incorporated.
* Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems
* Incorporated. All Rights Reserved.
*
* Contributor(s): Akamai Technologies
*
*****************************************************/
package org.osmf.net
{
CONFIG::LOGGING
{
import org.osmf.logging.Logger;
}
import __AS3__.vec.Vector;
import flash.errors.IOError;
import flash.events.AsyncErrorEvent;
import flash.events.EventDispatcher;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.net.NetConnection;
import flash.utils.Timer;
import org.osmf.events.MediaError;
import org.osmf.events.MediaErrorCodes;
import org.osmf.events.NetConnectionFactoryEvent;
import org.osmf.media.URLResource;
import org.osmf.utils.URL;
/**
* Dispatched when the factory has successfully created and connected a NetConnection.
*
* @eventType org.osmf.events.NetConnectionFactoryEvent.CREATION_COMPLETE
*
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
[Event(name="creationComplete", type="org.osmf.events.NetConnectionFactoryEvent")]
/**
* Dispatched when the factory has failed to create and connect a NetConnection.
*
* @eventType org.osmf.events.NetConnectionFactoryEvent.CREATION_ERROR
*
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
[Event(name="creationError", type="org.osmf.events.NetConnectionFactoryEvent")]
/**
* The NetNegotiator class attempts to negotiate its way through firewalls and proxy
* servers, by trying multiple parallel connection attempts on differing port and protocol combinations.
* The first connection to succeed is kept and those still pending are shut down.
*
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
internal class NetNegotiator extends EventDispatcher
{
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion OSMF 1.0
*/
public function NetNegotiator():void
{
super();
}
/**
* @private
*/
public function createNetConnection(resource:URLResource, netConnectionURLs:Vector.<String>, netConnections:Vector.<NetConnection>):void
{
this.resource = resource;
this.netConnectionURLs = netConnectionURLs;
this.netConnections = netConnections;
var streamingResource:StreamingURLResource = resource as StreamingURLResource;
if ( streamingResource != null
&& streamingResource.connectionArguments != null
&& streamingResource.connectionArguments.length > 0)
{
this.netConnectionArguments = streamingResource.connectionArguments;
}
initializeConnectionAttempts();
tryToConnect(null);
}
/**
* Initializes properties and timers used during rtmp connection attempts.
* @private
*/
private function initializeConnectionAttempts():void
{
// Master timeout
timeOutTimer = new Timer(DEFAULT_TIMEOUT, 1);
timeOutTimer.addEventListener(TimerEvent.TIMER_COMPLETE, masterTimeout);
timeOutTimer.start();
// Individual attempt sequencer
connectionTimer = new Timer(CONNECTION_ATTEMPT_INTERVAL);
connectionTimer.addEventListener(TimerEvent.TIMER, tryToConnect);
connectionTimer.start();
// Initialize counters
failedConnectionCount = 0;
attemptIndex = 0;
}
/**
* Attempts to connect to FMS using a particular connection string
* @private
*/
private function tryToConnect(evt:TimerEvent):void
{
netConnections[attemptIndex].addEventListener(NetStatusEvent.NET_STATUS, onNetStatus, false, 0, true);
netConnections[attemptIndex].addEventListener(SecurityErrorEvent.SECURITY_ERROR, onNetSecurityError, false, 0, true);
netConnections[attemptIndex].addEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError, false, 0, true);
netConnections[attemptIndex].client = new NetClient();
try
{
var host:String = netConnectionURLs[attemptIndex];
var args:Array = [host];
if (netConnectionArguments != null)
{
for each (var arg:Object in netConnectionArguments)
{
args.push(arg);
}
}
CONFIG::LOGGING
{
logger.info("Attempting connection to " + netConnectionURLs[attemptIndex]);
}
NetConnection(netConnections[attemptIndex]).connect.apply(netConnections[attemptIndex], args);
attemptIndex++;
if (attemptIndex >= netConnectionURLs.length)
{
connectionTimer.stop();
}
}
catch (ioError:IOError)
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.IO_ERROR, ioError.message)
, netConnectionURLs[attemptIndex]
);
}
catch (argumentError:ArgumentError)
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.ARGUMENT_ERROR, argumentError.message)
, netConnectionURLs[attemptIndex]
);
}
catch (securityError:SecurityError)
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.SECURITY_ERROR, securityError.message)
, netConnectionURLs[attemptIndex]
);
}
}
/**
* Monitors status events from the NetConnections
* @private
*/
private function onNetStatus(event:NetStatusEvent):void
{
switch (event.info.code)
{
case NetConnectionCodes.CONNECT_INVALIDAPP:
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.NETCONNECTION_APPLICATION_INVALID, event.info.description)
, NetConnection(event.target).uri
);
break;
case NetConnectionCodes.CONNECT_REJECTED:
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.NETCONNECTION_REJECTED, event.info.description)
, NetConnection(event.target).uri
);
break;
case NetConnectionCodes.CONNECT_FAILED:
failedConnectionCount++;
CONFIG::LOGGING
{
if (failedConnectionCount < netConnectionURLs.length)
{
logger.info("NetConnection attempt failed: " + NetConnection(event.target).uri);
}
}
if (failedConnectionCount >= netConnectionURLs.length)
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.NETCONNECTION_FAILED)
, NetConnection(event.target).uri
);
}
break;
case NetConnectionCodes.CONNECT_SUCCESS:
CONFIG::LOGGING
{
if ( event.info.hasOwnProperty("data")
&& event.info.data.hasOwnProperty("version")
)
{
logger.info("FMS Version: " + event.info.data.version);
}
else
{
logger.info("FMS Version unknown");
}
}
shutDownUnsuccessfulConnections();
dispatchEvent
( new NetConnectionFactoryEvent
( NetConnectionFactoryEvent.CREATION_COMPLETE
, false
, false
, event.currentTarget as NetConnection
, resource
)
);
break;
}
}
/**
* Closes down all parallel connections in the netConnections vector which are not connected.
* Also shuts down the master timeout and attempt timers.
* @private
*/
private function shutDownUnsuccessfulConnections():void
{
timeOutTimer.stop();
connectionTimer.stop();
for (var i:int = 0; i < netConnections.length; i++)
{
var nc:NetConnection = netConnections[i];
if (!nc.connected)
{
nc.removeEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
nc.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onNetSecurityError);
nc.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, onAsyncError);
nc.close();
delete netConnections[i];
}
}
}
/**
* Handles a failed connection session and dispatches a CONNECTION_FAILED event
* @private
*/
private function handleFailedConnectionSession(mediaError:MediaError, url:String):void
{
CONFIG::LOGGING
{
logger.info("NetConnection attempt failed: " + url + " (" + mediaError.errorID + "): " + mediaError.message);
}
shutDownUnsuccessfulConnections();
dispatchEvent
( new NetConnectionFactoryEvent
( NetConnectionFactoryEvent.CREATION_ERROR
, false
, false
, null
, resource
, mediaError
)
);
}
/**
* Catches any netconnection net security errors
* @private
*/
private function onNetSecurityError(event:SecurityErrorEvent):void
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.SECURITY_ERROR, event.text)
, NetConnection(event.target).uri
);
}
/**
* Catches any async errors
* @private
*/
private function onAsyncError(event:AsyncErrorEvent):void
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.ASYNC_ERROR, event.text)
, NetConnection(event.target).uri
);
}
/**
* Catches the master timeout when no connections have succeeded within _timeout.
* @private
*/
private function masterTimeout(event:TimerEvent):void
{
handleFailedConnectionSession
( new MediaError(MediaErrorCodes.NETCONNECTION_TIMEOUT, "" + DEFAULT_TIMEOUT)
, ""
);
}
private var resource:URLResource;
private var netConnectionURLs:Vector.<String>;
private var netConnections:Vector.<NetConnection>;
private var netConnectionArguments:Vector.<Object>;
private var failedConnectionCount:int;
private var timeOutTimer:Timer;
private var connectionTimer:Timer;
private var attemptIndex:int;
private var mediaError:MediaError;
private static const DEFAULT_TIMEOUT:Number = 10000;
private static const CONNECTION_ATTEMPT_INTERVAL:Number = 50;
CONFIG::LOGGING private static const logger:org.osmf.logging.Logger = org.osmf.logging.Log.getLogger("org.osmf.net.NetNegotiator");
}
}