Home » Code Samples, Flash, FMS, Tutorials

Flash Bandwidth & Port Detection

29 October 2008 | 10,814 views | 3 Comments


Being able to detect a users bandwidth can be an important part of serving them streaming video. Using their bandwidth you can stream a video that will play well using their connection speeds.

The first step is to create our NetConnection.as file. This is used by our bandwidth checking script to connect to our Flash Media Server via RTMP, select an available port, and talk with our bw_check.asc to report the users available bandwidth.

import mx.events.EventDispatcher;
class NCManager 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 NCManager() {
		trace("NCManager initialized");
		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].onBWDone = function(p_bw) {
				this.owner.dispatchEvent({type:"ncBandWidth", kbps:p_bw});
			};
			this["nc"+i].onBWCheck = function(counter) {
				return ++counter;
			};
			this["nc"+i].onStatus = function(info) {
				this.pending = false;
				this.owner.m_validNetConnection = this.owner["nc"+this.connIndex];
				if (info.code == "NetConnection.Connect.Success") {
					// We have a successfull connection
					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 stream length for a given stream name.
	// Note: requires matching server function to be present on the server.
	function getStreamLength(streamName:String) {
		var res = new Object();
		res.owner = this;
		res.name = streamName;
		res.onResult = function(p_length) {
			this.owner.dispatchEvent({type:"ncStreamLength", length:p_length, name:res.name});
		};
		m_validNetConnection.call("getStreamLength",res,streamName);
	}
	// Public
	// Calls a server-side function to initiate a bandwdith check.
	// Note: requires matching server function to be present on the server.
	function getBandWidth(Void):Void {
		m_validNetConnection.call("checkBandwidth",null);
	}
	// Public
	// Returns the active connection or null if it does not exist.
	function getActiveConnection(Void):NetConnection {
		trace(m_validNetConnection == undefined ? null : m_validNetConnection);
		return m_validNetConnection == undefined ? null : m_validNetConnection;
	}
	function closeConnections() {
		trace("CLOSING= "+m_validNetConnection);
		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];
			}
		}
	}
}

The second step is to create our FMS server side code. The bwcheck.asc file sends packets at a size of about 16Kb from the FMS and calculates the response latency over the users network.

application.onConnect = function(p_client, p_autoSenseBW) {
	this.acceptConnection(p_client);
	if (p_autoSenseBW)
		this.calculateClientBw(p_client);
	else
		p_client.call("onBWDone");
}
Client.prototype.getStreamLength = function(p_streamName) {
	return Stream.length(p_streamName);
}
Client.prototype.checkBandwidth = function() {
	application.calculateClientBw(this);
}
application.calculateClientBw = function(p_client) {
	p_client.payload = new Array();
	for (var i=0; i<1200; i++){
		p_client.payload[i] = Math.random();//16K approx
	}
	var res = new Object();
	res.latency = 0;
	res.cumLatency = 1;
	res.bwTime = 0;
	res.count = 0;
	res.sent = 0;
	res.client = p_client;
	var stats = p_client.getStats();
	var now = (new Date()).getTime()/1;
	res.pakSent = new Array();
	res.pakRecv = new Array();
	res.beginningValues = {b_down:stats.bytes_out, b_up:stats.bytes_in, time:now};
	res.onResult = function(p_val) {
		var now = (new Date()).getTime()/1;
		this.pakRecv[this.count] = now;
		this.count++;
		var timePassed = (now - this.beginningValues.time);
		if (this.count == 1) {
			this.latency = Math.min(timePassed, 800);
			this.latency = Math.max(this.latency, 10);
		}
		if ( this.count == 2 && (timePassed<2000)) {
			this.pakSent[res.sent++] = now;
			this.cumLatency++;
			this.client.call("onBWCheck", res, this.client.payload);
		} else if ( this.sent == this.count ) {	
			//see if we need to normalize latency
			if ( this.latency >= 100 ) { 
				if (  this.pakRecv[1] - this.pakRecv[0] > 1000 ) {
					this.latency = 100;
				}
			}
			delete this.client.payload;
			//got back responses for all the packets compute the bandwidth
			var stats = this.client.getStats();
			var deltaDown = (stats.bytes_out - this.beginningValues.b_down)*8/1000;
			var deltaTime = ((now - this.beginningValues.time) - (this.latency * this.cumLatency) )/1000;
			if ( deltaTime <= 0 )
				deltaTime = (now - this.beginningValues.time)/1000;
			
			var kbitDown = Math.round(deltaDown/deltaTime);
			trace("onBWDone: kbitDown = " + kbitDown + ", deltaDown= " + deltaDown + ", deltaTime = " + deltaTime + ", latency = " + this.latency + "KBytes " + (stats.bytes_out - this.beginningValues.b_down)/1024) ;
			this.client.call("onBWDone", null, kbitDown,  deltaDown, deltaTime, this.latency );
		}
	}
	res.pakSent[res.sent++] = now;
	p_client.call("onBWCheck", res, "");
	res.pakSent[res.sent++] = now;
	p_client.call("onBWCheck", res, p_client.payload);
}

Third step. Bring everything together within our flash file, and print our results to text fields on the stage.

stop();

import NCManager;

var ns:NetStream;
var fileName:String;
var k_STARTTIME:Number = getTimer();

//server info
var k_SERVERNAME:String = "server_name";
var k_APPNAME:String = "application_folder_name";
var k_ASC_PATH:String = "rtmp://"+k_SERVERNAME+"/"+k_APPNAME+"/bwcheck.asc";

nc = new NetConnection();
nc.connect(k_ASC_PATH,true);
nc.onStatus = function(info) {
	trace("Level: "+info.level+" Code: "+info.code);
	if (info.code == "NetConnection.Connect.Success") {
		trace("loaded: "+this.uri);
	}
};

//the variable p_bw returned by bwcheck.asc holds a value equal to the detected download speed in kilobits per second
//for best results we should serve video that is encoded at a rate less than or equal to kbitsDown
NetConnection.prototype.onBWDone = function(p_bw) {
	trace("BANDWIDTH= "+p_bw);
	bandwidth.text = "Bandwidth= "+p_bw
	//close the Netconnection to bwcheck
	this.close();
};

NetConnection.prototype.onBWCheck = function() {
	return ++counter;
};

var ncm:NCManager = new NCManager();
var ncmListener:Object = new Object();

//fires when it has found a successfull connection
ncmListener.ncConnected = function(evt:Object) {
	trace("["+Math.round(getTimer()-k_STARTTIME)+"ms] Successfully connected using "+evt.protocol+":"+evt.port);
	port.text = "connected using "+evt.protocol+":"+evt.port
};

//fires when it has timed-out trying to find a good connection
ncmListener.ncFailedToConnect = function(evt:Object) {
	trace("Failed to connect after "+evt.timeout+" milliseconds.");
};

//fires when streamlength has been detected
ncmListener.ncStreamLength = function(evt:Object) {
	trace("["+Math.round(getTimer()-k_STARTTIME)+"ms] Streamlength of "+evt.name+" is "+evt.length+" seconds.");
};

//set listeners on the NCM instance
ncm.addEventListener("ncConnected",ncmListener);
ncm.addEventListener("ncFailedToConnect",ncmListener);
ncm.addEventListener("ncBandWidth",ncmListener);
ncm.addEventListener("ncStreamLength",ncmListener);

//call the connect method on NCM instance
ncm.connect(k_SERVERNAME,k_APPNAME);

Click here to see your bandwidth results.

Download the source

This tutorial has been updated, and is available in the following 3-part series:

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

  • http://www.derekentringer.com Derek J Entringer

    Thanks Kev. I’ve been working with Flash for around 10 years now.

  • Kev Man

    Very impressive and daunting.
    20+ years NLE A/V 2D and 3D Design and Animation.
    Started learning AS3 in April of 2011.
    Totally differnt world.  Havent slept since.
    How many years of studying did it take to be able to figure all of this out?

  • SandraMillhouse

    Thanks for adding the other two parts.