Home » Code Samples, Flash, FMS, Tutorials

Flash Media Server Streaming Speed Testing [Part 2] – Detect Upload, Download, and Latency Speeds, and Port Connection

12 May 2009 | 14,412 views | 4 Comments


This tutorial is the second in a series of three. Part one of this tutorial outlined what is needed to detect a users upload, download, and latency between a Flash client and the Flash Media Server. Part 2 adds on the ability to detect which ports are available while connecting.

Again, Bandwidth detection is most important when connecting your users to the correctly compressed media to be streamed, recorded, or delivered via a Flash Media Server. This second example will walk you through the process of being able to detect a users download bandwidth, upload bandwidth, and the latency between the client computer and the host server, along with iterating through all of the available ports that allow you to connect to the Flash Media Server. All of the code is Actionscript 2.0, and I’ve setup a Flash Media Server 3.5 to host the server side scripting. I use Influxis as my Flash Media Server host for all of these tutorials and smaller examples.

First, we will add our NetConnManager object which will use the NetConnManager.as exteranal Actionscript file. This is the first part of our Flash Actionscript code.

import NetConnManager;

var startTime:Number = getTimer();
var ncm:NetConnManager = new NetConnManager();
var ncmListener:Object = new Object();

ncmListener.ncConnected = function(evt:Object) {
	trace("["+Math.round(getTimer()-startTime)+"ms] successfully connected using "+evt.protocol+":"+evt.port);
	conn_stat.text += "["+Math.round(getTimer()-startTime)+"ms] successfully connected using "+evt.protocol+":"+evt.port+newline;
};
ncmListener.ncFailedToConnect = function(evt:Object) {
	trace("failed to connect after "+evt.timeout+" ms");
	conn_stat.text += "failed to connect after "+evt.timeout+" ms"+newline;
};
ncm.addEventListener("ncConnected", ncmListener);
ncm.addEventListener("ncFailedToConnect", ncmListener);

ncm.connect("your.rtmphost.com", "your_application_name");

In order to get the above to work correctly with your own Flash Media Server, you simply need to edit the following line.

ncm.connect("your.rtmphost.com", "your_application_name");

The following code is the same as part one of this series of tutorials. If you have not read part one, you can find it here.

function connectMe() {
	rec_nc = new NetConnection();
	rec_nc.onStatus = function(info) {
		trace("Level: " + info.level + " Code: " + info.code);
		if (info.code == "NetConnection.Connect.Success") {
			trace("connected to: "+this.uri);
			conn_stat.text += "connected to: "+this.uri+newline;
			startTest(rec_nc);
		} else if (info.code == "NetConnection.Connect.Failed" || info.code == "NetConnection.Connect.Closed") {
			trace("no connection to app");
			conn_stat.text += "error connection failed"+newline;
		}
	};
	rec_nc.connect("rtmp://y9cq49zks4.rtmphost.com/bwcheck/");
}

function startTest(nc) {
	_global.bwInfo = new BandwidthInfo(nc);
	_global.bwInfo.start();
}

for (i=0; i<1000; i++) {
	data += "C->S";
}
function BandwidthInfo(nc) {
	this.nc = nc;
	this.maxLength = 10;
	this.bwInHistory = new Array(this.maxLength);
	this.bwInCtr = 0;
	this.bwOutHistory = new Array(this.maxLength);
	this.bwOutCtr = 0;
	this.pingHistory = new Array(this.maxLength);
	this.headIn = 0;
	this.headOut = 0;
	this.headPing = 0;
	this.bBWOutStop = false;
	this.bBWInStop = false;
	this.onBWInTimeout = function() {
		clearInterval(this.bwInfoTimeout);
		this.bwInfoTimeout = null;
		delete this.bwInfoTimeout;
		this.bBWInStop = true;
		
		if (this.bwInCtr == 0) {
			conn_stat.text += "unable to receive data from server."+newline;
			this.abort("unable to receive data from server.");
			return;
		}
		
		this.stop();
	};
	this.onBWOutTimeout = function() {
		clearInterval(this.bwInfoTimeout);
		this.bwInfoTimeout = null;
		delete this.bwInfoTimeout;
		this.bBWOutStop = true;
		
		if (this.bwOutCtr == 0) {
			conn_stat.text += "unable to receive data from server"+newline;
			this.abort("unable to send data to server");
			return;
		}
		
		trace("testing bandwidth from server");
		conn_stat.text += "testing bandwidth from server"+newline;
		this.bwInfoTimeout = setInterval(this, "onBWInTimeout", 5*1000);
		this.serverToClient();
	};
	this.clientToServer = function() {
		this.time = getTimer();
		size = 0;
		bwinfo = this;
		this.nc.ack = function(pingVal) {
			if (!bwinfo.bBWOutStop) {
				bwinfo.bwOutHistory[bwinfo.headOut++%bwinfo.maxLength] = Math.floor(size/(getTimer()-bwinfo.time)*1000);
				bwinfo.pingHistory[bwinfo.headPing++%bwinfo.maxLength] = pingVal;
				bwinfo.nc.call("recData", 0, data);
				size += 4000;
				bwinfo.bwOutCtr++;
			}
		};
		this.nc.call("recData", 0, data);
		this.nc.call("recData", 0, data);
	};
	this.serverToClient = function() {
		this.time = getTimer();
		size = 0;
		bwinfo = this;
		nc.onEcho = function() {
			if (!bwinfo.bBWInStop) {
				bwinfo.bwInHistory[bwinfo.headIn++%bwinfo.maxLength] = Math.floor(size/(getTimer()-bwinfo.time)*1000);
				this.call("echoData", 0, 0);
				size += 4000;
				bwinfo.bwInCtr++;
			}
		};
		nc.call("echoData", 0, 0);
		nc.call("echoData", 0, 0);
	};
	this.start = function() {
		conn_stat.text += "testing upload speed"+newline;
		trace("testing upload speed");
		clearInterval(this.bwInfoTimeout);
		this.bwInfoTimeout = null;
		delete this.bwInfoTimeout;
		this.bwInfoTimeout = setInterval(this, "onBWOutTimeout", 5*1000);
		this.clientToServer();
	};
	this.stop = function() {
		this.nc = null;
		var ping_rtt = 0;
		var bw_out = 0;
		var bw_in = 0;
		for (var i = 0; i<this.maxLength && i<this.bwOutCtr; i++) {
			ping_rtt = Math.max(ping_rtt, this.pingHistory[i]);
		}
		for (var i = 0; i<this.maxLength && i<this.bwOutCtr; i++) {
			bw_out += this.bwOutHistory[i];
		}
		bw_out /= Math.min(this.maxLength, this.bwOutCtr);
		bw_out = Math.round((bw_out/1024)*8);
		for (var i = 0; i<this.maxLength && i<this.bwInCtr; i++) {
			bw_in += this.bwInHistory[i];
		}
		bw_in /= Math.min(this.maxLength, this.bwInCtr);
		bw_in = Math.round((bw_in/1024)*8);
		var s;
		s = "bandwidth results:\n";
		s += "   upstream: "+bw_out+" kbps\n";
		s += "   downstream: "+bw_in+" kbps\n";
		s += "   latency: "+ping_rtt+" ms\n";
		if (ping_rtt>1000) {
			s += "network appears to have a very high delay\n\n";
		}
		if ((bw_in>500) && (bw_out>200)) {
			s += "connection supports high quality speed\n\n";
		} else if ((bw_in>=200) && (bw_out>=100)) {
			s += "connection supports good quality speed\n\n";
		} else if ((bw_in>100) && (bw_out>80)) {//(bw_in<250) && (bw_out<80) && 
			s += "connection supports mid quality speed\n\n";
		} else if ((bw_in>20) && (bw_out>15)) {
			s += "connection supports slow quality speed\n\n";
		} else {
			s += "connection supports very slow quality speed\n\n";
		}
		trace(s);
		conn_stat.text += s+newline;
	};
	this.abort = function(reason) {
		conn_stat.text += "failed "+reason+newline;
		trace("failed "+reason);
	};
}

connectMe();

Next, lets setup our NetConnManager.as file. This external Actionscript class allows the ncm object to iterate through all of the available ports on the Flash Media Server, and lets you know which port it has connected successfully on the server. This is useful if you are dealing with a firewall that has blocked the default port of 1935.

import mx.events.EventDispatcher;
class NetConnManager extends Object {
	//EventDispatcher needs these
	var addEventListener:Function;
	var removeEventListener:Function;
	var dispatchEvent:Function;
	var dispatchQueue:Function;
	//Constants
	private var k_DEFAULTCONNLIST = [{protocol:"rtmp", port:1935}, {protocol:"rtmp", port:443}, {protocol:"rtmpt", port:80}, {protocol:"rtmps", port:443}];
	private var k_TIMEOUT:Number = 60000;
	//Variables
	private var m_connList:Array;
	private var m_serverName:String;
	private var m_appName:String;
	private var m_streamName:String;
	private var m_connListCounter:Number;
	private var m_flashComConnectTimeOut:Number;
	private var m_validNetConnection:NetConnection;
	//Constructor
	function NetConnManager() {
		EventDispatcher.initialize(this);
	}
	//Public
	//Initiates all the connection attempts.
	//Note: If no connection list is passed, the default list is used
	function connect(p_serverName:String, p_appName:String, p_connList:Array) {
		m_serverName = p_serverName;
		m_appName = p_appName;
		m_connList = (p_connList != undefined) ? p_connList : k_DEFAULTCONNLIST;
		//Set a timeout function, just in case we never connect successfully
		clearInterval(m_flashComConnectTimeOut);
		m_flashComConnectTimeOut = setInterval(this, "onFlashComConnectTimeOut", k_TIMEOUT, k_TIMEOUT);
		//Creates a NetConnection for each of the protocols/ports listed in the m_connList list.
		//Connection attempts occur at intervals of 1.5 seconds. The first connection to succeed
		//will be used, all the others will be closed.
		for (var i = 0; i<m_connList.length; i++) {
			this["nc"+i] = new NetConnection();
			this["nc"+i].owner = this;
			this["nc"+i].connIndex = i;
			this["nc"+i].onStatus = function(info) {
				this.pending = false;
				this.owner.m_validNetConnection = this.owner["nc"+this.connIndex];
				if (info.code == "NetConnection.Connect.Success") {
					clearInterval(this.owner.m_flashComConnectTimeOut);
					this.owner.dispatchEvent({type:"ncConnected", nc:this.owner.m_validNetConnection, protocol:this.owner.m_connList[this.connIndex].protocol, port:this.owner.m_connList[this.connIndex].port});
					for (var i = 0; i<this.owner.m_connList.length; i++) {
						if (i == this.connIndex) {
							continue;
						}
						if (this.owner["nc"+i].pending) {
							clearInterval(this.owner["ncInt"+i]);
							this.owner["nc"+i].onStatus = null;
							this.owner["nc"+i].close();
							this.owner["nc"+i] = null;
							delete this.owner["nc"+i];
						}
					}
				} else {
					trace(this.owner.m_connList[this.connIndex].protocol+": "+this.owner.m_connList[this.connIndex].port+", onStatus: "+info.code+" : "+info.description);
				}
			};
			this["nc"+i].pending = true;
		}
		m_connListCounter = 0;
		nextConnect();
	}
	//Public
	//Returns the active connection or null if it does not exist.
	function getActiveConnection(Void):NetConnection {
		return m_validNetConnection == undefined ? null : m_validNetConnection;
	}
	function closeConnections() {
		m_validNetConnection.close();
	}
	//Private
	//Walks through the connection list to try every protocol.
	private function nextConnect(Void):Void {
		clearInterval(this["ncInt"+m_connListCounter]);
		this["nc"+m_connListCounter].connect(m_connList[m_connListCounter].protocol+"://"+m_serverName+":"+m_connList[m_connListCounter].port+"/"+m_appName);
		if (m_connListCounter<(m_connList.length-1)) {
			m_connListCounter++;
			this["ncInt"+m_connListCounter] = setInterval(this, "nextConnect", 1500);
		}
	}
	//Private
	//Cleans up all conenctions if none have succeeded by the timeout interval
	private function onFlashComConnectTimeOut(timeout:Number):Void {
		clearInterval(m_flashComConnectTimeOut);
		this.dispatchEvent({type:"ncFailedToConnect", timeout:timeout});
		for (var i = 0; i<m_connList.length; i++) {
			if (this["nc"+i].pending) {
				clearInterval(this["ncInt"+i]);
				this["nc"+i].onStatus = null;
				this["nc"+i].close();
				this["nc"+i] = null;
				delete this["nc"+i];
			}
		}
	}
}

We now have our Flash file ready, but we still need our application setup on the Flash Server. Create a new application on your Flash Media Server, and name it however you want. That application name then goes into the connect function above, along with the url for the server.

Next, we want to create the server side code that will allow our final swf to call server side functions in order to return the correct upload, download, and latency values.

application.onAppStart = function (info){
	for ( i = 0; i < 500; i++ ) {
		data += "S->C";
	}
	Client.prototype.recData = function(data) {
		this.ping();
		var v = this.getStats();
		this.call("ack", 0, v.ping_rtt);
	}
	Client.prototype.echoData = function() {
		this.call("onEcho", 0, data);
	};
	Client.prototype.getBWInfo = function() {
		return this.getStats();
	};
	Client.prototype.onConnTimeout = function(){
		clearInterval( this.connTimeout );
		this.connTimeout = null;
		application.disconnect(this);
	}
}
application.onConnect = function(client_obj, id) {
	application.acceptConnection(client_obj);
}

And, there you have it. These scripts allow you to check the users upload and download bandwidth capabilities, check how slow the server is allowing you to connect using latency detection, and provides you with the correct port that may be available from within your current network.

Download the source

Finally, be sure to have a look at my next tutorial using these same techniques. In part three, I will be adding the functionality to check multiple servers using this same code.

Here are the links for this tutorial in its entirety:

Flash Media Server Streaming Speed Testing [Part 1] – Detect Upload, Download, and Latency Speeds

Flash Media Server Streaming Speed Testing [Part 2] – Detect Upload, Download, and Latency Speeds, and Port Connection

Flash Media Server Streaming Speed Testing [Part 3] – Compare Multiple Server Resources, Port Connections, Detect Upload, Download, and Latency Speed

  • @Eric

    Thanks, glad you found it useful.

    And you are right. I updated the code in the article. Thx!

  • @Eric

    Thanks, glad you found it useful.

    And you are right. I updated the code in the article. Thx!

  • Eric

    Your article is very useful.
    I found a minor bug in the first piece of your code:

    ncm.addEventListener(“ncConnected”, ncmListener);
    ncm.addEventListener(“ncConnected”, ncmListener);

    I believe the second statement should be:
    ncm.addEventListener(“ncFailedToConnect”, ncmListener);

  • Eric

    Your article is very useful.
    I found a minor bug in the first piece of your code:

    ncm.addEventListener(“ncConnected”, ncmListener);
    ncm.addEventListener(“ncConnected”, ncmListener);

    I believe the second statement should be:
    ncm.addEventListener(“ncFailedToConnect”, ncmListener);