Flash Media Server, Flex Builder 2, and ANT

Developing Flex 2 applications that talk with Flash Media Server is really fun.  I’m sure Adobe will tell you that Flex Data Services are the way to go, but that is overkill for a lot of the things I want to do.  If you want to create a set of widgets that support multiuser activity such as chat and sharing of real-time data, you don’t need the Enterprise data services that are FDS.  While Flash Media Server has real-time video and audio, I use it strictly for the real-time data via Remote SharedObjects , and the server-side scripting done in case-sensitive JavaScript.  The persistent data of Remote SharedObjects with a simple, yet flexible coding model makes them a very powerful way to get multi-user Flex 2 applications.  Combined with some simple server-side logic to maintain “data authority” as I like to call it, and you’ve got a really fun way to create real-time data, mult-user apps.

What are the alternatives?

XMLSocket ?  Parsing XML is more programmer friendly now with E4X, ECMAScript for XML , in ActionScript 3.  XMLSocket servers do not enforce XML, however… they are merely string servers that run on ports of 1024 or higher, thus making them flexible.  The cons?  They run on ports 1024 or higher… ONLY.  Client got a firewall?  Tough.  There are not a lot of big name XMLSocket hosting services.  Those that are out there are extremely expensive for what you get.  While the hosting might not be expensive, the license fee’s are.  A lot of the open source solutions aren’t finished.  Those that are assume you know PHP, Ruby, Perl, or Python well enough to finish it.  Assuming I could, where do I host these servers?  My host isn’t going to dig me having a PHP script stuck in a while loop…

FDS?  Sorry, can’t afford 60k for fun side projects that have the potential to go to extremely small commercial markets.

Some other TCP/IP binary server?  I’d like to create things today instead of getting burnt out writing something from scratch in ActionScript 3’s binary Socket API.

It’d be nice if Adobe would release a Remote SharedObject server, complete with server-side JavaScript on a totally different pricing and license scheme than Flash Media Server or Flex Data Services are.  There are a lot of use developing mulit-user Flash Player applications on FMS that have no need for real-time streaming video and audio.  I think the same crackheads that ran pre-Flex 2’s business unit took over Flashcom’s.

At any rate, there is no denying FMS is elegant to code with.  With a little work via the Observer pattern , you can implement FMS server-side components into Cairngorm 2 .  For ever server-side component you create, you merely create a client side equivalent as an Observer.  It listens for NetConnection or Remote SharedObject sync events and dispatches events.  These events run Commands which in turn set data… just like normal.  If your Views are bound to said data, boom, you’ve got a nice, clean separation.

One thing that sucks about the work flow, though, is:

  • you have to redeploy your .asc files, your server-side code (basically JavaScript written server-side components)
  • you have to restart your app via the Communication App Inspector, basically a small app written in Flash
  • you then toggle back to Eclipse (Flex Builder 2 )

That sucks.  The better way is to use some ANT tasks I’ve created along with a special SWF.  This SWF connects to the FMS Admin server, reloads the app, and then immediately quits.  I go into more detail below, or you can just download the source.  You could re-write the ActionScript 2 into ActionScript 3, but AS2 is just quicker than AS3 for this type of stuff.

Bottom line, you just double-click an ANT task in Eclipse, it does all that for you, and you don’t have to leave Eclipse.  That’s so hot!

First, the ANT tasks.

<project name=”DnDTools” default=”Deploy FCS” basedir=”.”>
   
    <description>
    by Jesse R. Warden
    jesterxl@jessewarden.com
    https://www.jessewarden.com
    jesse@universalmind.com
    http://www.universalmind.com
   
    A series of tasks to work with Flashcom.
    ************************************************   
       
    Start Flashcom will write out a bat file, and run it to start
    Flashcom as well as the Flashcom Admin service.
   
    Stop Flashcom stops both the Flashcom service and the
    Flashcom Admin service.
   
    Deploy FCS does a few things:
    * copies your .asc files to the FCS applications directory
    * writes a configuration XML file for the FCS App Reloader
    * runs the FCS App Reloader to reload your application instance on the FCS server
   
    </description>
   
    <!– ************************************************ –>
    <!– set global properties for this build –>
    <property name=”bin” value=”bin” />
    <property name=”localFCSDir” value=”../flashcom”  />
    <property name=”deployFCSDir” value=”C:\Program Files\Macromedia\Flash Communication Server MX\applications\fcsappdirectory” />
    <property name=”FCSAppReloader” value=”FCS_App_Reloader.exe” />
    <property name=”FCSAdminHost” value=”localhost” />
    <property name=”FCSAdminPort” value=”1111″ />
    <property name=”FCSAppName” value=”fcsappdirectory” />
    <property name=”FCSAdminUser” value=”myUsername” />
    <property name=”FCSAdminPassword” value=”myPassword” />
    <property name=”appWaitTime” value=”3″ />
    <property name=”bat” location=”bat” />
   
    <!– Copy files into the FCS app directory, write a config xml, and reload an FCS app –>
    <target name=”Deploy FCS”>
       
        <!– ************************************************ –>
        <!– Copy new FCS files to the applications directory –>
        <echo message=”Copying Flashcom files to application directory…” level=”info” />
        <copy file=”${localFCSDir}/Application.xml” todir=”${deployFCSDir}” />
        <copy file=”${localFCSDir}/asc/main.asc” todir=”${deployFCSDir}” />
        <copy file=”${localFCSDir}/asc/Chat.asc” todir=”${deployFCSDir}” />
       
        <!– ************************************************ –>
        <!– Write the config xml file for the FCS Reloader App –>
        <echo message=”Writing FCSAppReloader configuration xml file…” level=”info” />
        <!– Host –>
        <echo message=”&lt;config host=’${FCSAdminHost}’ “
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
        <!– Port –>
        <echo message=”port=’${FCSAdminPort}’ “
            append=”true”
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
        <!– App Name –>
        <echo message=”appname=’${FCSAppName}’ “
            append=”true”
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
        <!– Username –>
        <echo message=”username=’${FCSAdminUser}’ “
            append=”true”
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
        <!– Password –>
        <echo message=”password=’${FCSAdminPassword}’ “
            append=”true”
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
        <!– Hang Time –>
        <echo message=”hangtime=’${appWaitTime}’ /&gt;”
            append=”true”
            file=”${localFCSDir}/fcs_app_reloader_config.xml” />
       
        <!– ************************************************ –>
        <!– Launch the FCS Reloader App –>
        <echo message=”Re-loading the FCS application…” level=”info” />
        <exec executable=”${FCSAppReloader}”
            dir=”${localFCSDir}/”
            spawn=”false”
            resolveexecutable=”true” />
       
       
    </target>
   
    <target name=”Start Flashcom”>
       
        <echo message=”Starting Flashcom…” level=”info” />
        <mkdir dir=”${bat}” />
        <echo file=”${bat}/start_fcs.bat”>%SYSTEMROOT%\system32\net.exe start &quot;FlashCom&quot;
               
        </echo>
        <exec executable=”start_fcs.bat”
            dir=”${bat}”
            spawn=”false”
            resolveexecutable=”true” />
       
    </target>
   
    <target name=”Stop Flashcom”>
       
        <echo message=”Stopping Flashcom…” level=”info” />
        <mkdir dir=”${bat}” />
       
        <echo file=”${bat}/stop_fcs.bat”>%SYSTEMROOT%\system32\net.exe stop &quot;FlashCom&quot;
       
        </echo>   
        <echo message=”%SYSTEMROOT%\system32\net.exe stop &quot;FlashComAdmin&quot;”
            append=”true”
            file=”${bat}/stop_fcs.bat” />
       
        <exec executable=”stop_fcs.bat”
                    dir=”${bat}”
                    spawn=”false”
                    resolveexecutable=”true” />
       
    </target>
   
</project>

Now, the ActionScript 2 for the FCS Reloader App:

/*

FCS App Reloader
by Jesse R. Warden
jesterxl@jessewarden.com
http://www.jessewarden.com
jesse@universalmind.com
http://www.universalmind.com

This is release under a Creative Commons license.
More information can be found here:

http://creativecommons.org/licenses/by/2.5/

v 1.0.0
This app connects to your FCS server, and reloads
an application instance.  The typical use case is to
launch this app from ANT in Eclipse.

The alternative is to:
- toggle to the Communication App Inspector
- click the app instance and click "reload"
- if it's not there, type in it's name, and then click "load"
- toggle back to Eclipse

Now, you can just click once on your ANT task, and you're done!

This API assumes Flash Communication Server 1.0 and Flash Player 6.
I tested this in Flash Player 8, and the API should work
in Flash Media Server 2 per the livedocs.

*/
import mx.utils.Delegate;

function init()
{
	Stage.align = "TL";
	Stage.scaleMode = "noScale";
	Stage.addListener(this);
	
	createTextField("debug_txt", 0, 0, 0, 100, 100);
	debug_txt.background = true;
	debug_txt.backgroundColor = 0xFFFFFF;
	debug_txt.border = true;
	debug_txt.borderColor = 0x000000;
	debug_txt.html = true;
	debug_txt.multiline = true;
	debug_txt.selectable = true;
	debug_txt.wordWrap = true;
	var fmt:TextFormat = new TextFormat();
	fmt.font = "Verdana";
	fmt.size = 11;
	debug_txt.setTextFormat(fmt);
	debug_txt.setNewTextFormat(fmt);
	onResize();
	
	debugHeader();
	debug("Loading config xml...");
	
	NETCONNECTION_CALL_SUCCESS = "NetConnection.Call.Success";
	CONFIG_XML_FILE = "fcs_app_reloader_config.xml";
	
	TIMEOUT_WIDTH = 100;
	TIMEOUT_HEIGHT = 12;
	config_xml = new XML();
	config_xml.ignoreWhite = true;
	config_xml.owner = this;
	config_xml.onLoad = Delegate.create(this, onConfigLoaded);
	config_xml.load(CONFIG_XML_FILE);
}
function onConfigLoaded(success:Boolean):Void
{
	debugHeader();
	if(success == true)
	{
		debug("Config XML loaded, parsing...");
		var at:Object 		= config_xml.firstChild.attributes
		theHost				= (at.host == null) ? "localhost" : at.host;
		thePort				= (at.port == null) ? "1111" : at.port;
		theAppName 			= at.appname;
		theUsername 		= at.username;
		thePassword			= at.password;
		theHangTime			= (at.hangtime == null) ? 3 : parseInt(at.hangtime);
		if(isNaN(theHangTime) || theHangTime < 0 || theHangTime == null) theHangTime = 3;
		var tabs:String = "\t\t\t\t";
		debug("host: " 			+ theHost);
		debug("port: " 			+ thePort);
		debug("app name: " 		+ theAppName);
		debug("user: " 			+ theUsername);
		debug("password: " 		+ thePassword);
		debug("hang time: " 	+ theHangTime);
		
		connect();
	}
	else
	{
		debug("Failed to find " + CONFIG_XML_FILE + ", aborting.");
		lightFuse();
	}
}
function connect()
{
	debugHeader();
	debug("Connecting to FCS Admin...");
	
	nc = new NetConnection();
	nc.owner = this;
	nc.onStatus = function(info)
	{
		if(info.code == "NetConnection.Connect.Success")
		{
			debugHeader();
			debug("Successfully connected!");
			this.owner.onConnected();
		}
		else
		{
			debugHeader();
			debug("Problem connecting to FCS Admin, aborting.");
			this.owner.lightFuse();
		}
	};
	nc.connect("rtmp://" + theHost + ":" + thePort + "/" + theAppName,
		   theUsername,
		   thePassword);
}

function onConnected()
{
	//getActiveInstances();
	reloadApp("flex2test");
}
function reloadApp(str:String)
{
	debugHeader();
	debug("Reloading app...");
	nc.call("reloadApp",
		      new Responder(this,
				    reloadApp_result,
				    reloadApp_fault),
				    str);
}
function reloadApp_result(o)
{
	debugHeader();
	if(o.code == NETCONNECTION_CALL_SUCCESS)
	{
		debug("App successfully rebooted, exiting.");
	}
	else
	{
		debug("Failed to reboot the app!");
		debugProps(o);
		debugProps(o.level);
		debugProps(o.error);
		debugProps(o.code);
		debug(o.description);
		debugProps(o.description);
		debug("Exiting.");
	}
	
	//debugProps(o);
	lightFuse();
}
function reloadApp_fault(o)
{
	debugHeader();
	debug("reloadApp_fault");
	debugProps(o);
	lightFuse();
}

/*
function getActiveInstances()
{
	debugHeader();
	debug("getActiveInstances...");
	nc.call("getActiveInstances",
	      new Responder(this,
			    getActiveInstances_result,
			    getActiveInstances_fault));
}

function getActiveInstances_result(o)
{
	debugHeader();
	debug("getActiveInstances_result");
	if(o.code == NETCONNECTION_CALL_SUCCESS)
	{
		debug(o.data.length + " active instances.");
		debugProps(o.data);
	}
}

function getActiveInstances_fault(o)
{
	debugHeader();
	
	debug("getActiveInstances_fault");
	debugProps(o);
}
*/
function lightFuse()
{
	nc.onStatus = null;
	nc.close();
	nc = null;
	
	createEmptyMovieClip("timeout_mc", 1);
	timeout_mc.st = getTimer();
	timeout_mc.timeout = 3; // seconds
	timeout_mc.onEnterFrame = function()
	{
		var ct:Number = getTimer();
		var et:Number = ct - this.st;
		if(et > (this.timeout * 1000))
		{
			this._parent.boom();
		}
		else
		{
			var p:Number = et / (this.timeout * 1000);
			var w:Number = TIMEOUT_WIDTH * p;
			
		}
		timeout_mc.clear();
		timeout_mc.lineStyle(0, 0x000000);
		timeout_mc.lineTo(TIMEOUT_WIDTH, 0);
		timeout_mc.lineTo(TIMEOUT_WIDTH, TIMEOUT_HEIGHT);
		timeout_mc.lineTo(0, TIMEOUT_HEIGHT);
		timeout_mc.lineTo(0, 0);
		
		timeout_mc.moveTo(1, 1);
		timeout_mc.beginFill(0x666666);
		timeout_mc.lineTo(w, 1);
		timeout_mc.lineTo(w, TIMEOUT_HEIGHT - 1);
		timeout_mc.lineTo(1, TIMEOUT_HEIGHT - 1);
		timeout_mc.lineTo(1, 1);
		
		timeout_mc.endFill();
	};
	timeout_mc.onEnterFrame(); // draw immediately
	onResize();
}
function boom()
{
	delete timeout_mc.onEnterFrame;
	timeout_mc.removeMovieClip();
	fscommand("quit");
}

function onResize()
{
	debug_txt._x = 0;
	debug_txt._y = 0;
	debug_txt._width = Math.max(12, Stage.width);
	debug_txt._height = Math.max(12, Stage.height);
	
	var sx:Number = Math.max(6, Stage.width);
	var sy:Number = 6;
	timeout_mc._x = sx - (timeout_mc._width) - 6;
	timeout_mc._y = sy;
}
function debug(o)
{
	trace(o);
	debug_txt.htmlText += o + "";
}

function debugHeader()
{
	//debug("---------------");
}

function debugProps(o)
{
	if(o instanceof Array)
	{
		var len:Number = o.length;
		for(var i:Number = 0; i<len; i++)
		{
			trace(o[i]);
		}
	}
	else
	{
		for(var p in o)
		{
			debug(p + ": " + o[p]);
		}
	}
}

init();


Source Files – ZIP

11 Replies to “Flash Media Server, Flex Builder 2, and ANT”

  1. …yeah, but no one hosts it yet like Influxis.com & Mediatemple.net do for Flashcom. Like I said, I want to build things, I don’t want to start a hosting buisniess out of my house.

    I’ve tested Red5 in the past with an earlier build and she works. While the above problem can be solved with getting Web Hosting services on board with hosting Red5, the server-side in Java part blows. Hopefully they’ll add server-side scripting support in the future for those of us who just want to do simple things.

  2. And WebObjects beta coming very soon with flex 2 messaging. Although Adobe probably dont want to acknowledge it, I think its a viable alternative. C# very much like AS3, for flash developers to jump into, and very quick to develope and there is a free IDE for it, and windows xp comes with IIS localhost for testing and it will plug into any IIS host. Pitty c# isnt really cross platform. RSO really are great small and quick though!!

  3. BTW – Red5 is for windows too :)

    But as Jesse says, I don’t think any major hosting companies are going to be offering Red5 hosting any tinme soon (boo…)

  4. Hi Jesse,

    Nice job explaining how one can use FCS with Flex2. Of course as someone already pointed out, you can use Red5 for this. I guarantee that there will be some affordable Red5 hosting solutions available very soon. The fact that we are a new project and not yet at the 1.0 release is the reason for the current lack of hosting.

    Also, while I prefer to write the server-side piece in Java, our next release will also support scripting in JavaScript, Ruby and Python. This release (0.6) should be out on Monday of next week.

    Another clarification, Red5 will run on any platform that can run Java 5.

    Take care,
    Chris Allen
    Red5 Co-Project Manager

  5. Just to clarify, Flex Data Services Express (FDS) is free, which for what I understand, the only restriction is single cpu servers, which should be enough for a side/fun project.

    Cheers.

  6. To be fair, yes, Adobe has a great developer license for FDS. The problem is, if it goes commercial, you’ll need deep pockets.

    Furthermore the server-side is written in Java. You cant use something lighter weight like JavaScript. I’ve read their API, but it’s not made for multi-user apps with messaging; it’s made for apps that want to distribute data; basically a wonderful integration into your data source with fallbacks to polling. Well put together, but again, wrong solution.

  7. Jesse –

    Nice writeup. It’s great to see this work being done with Flex2 and FMS. Just wanted to note the free FMS Developer Edition is now usable in commercial applications.

    Also – the FMS Professional Edition has the concept of license profiles that let you configure your server for use as a streaming A/V server or an application server. For example, you can choose to have 150 Connections and unlimited bandwidh or up to 2500 connections with a 25Mbps cap. It’s all the same price and you can change profiles at any time. More here. Jesse – Not sure if this is in line with what you meant by a ‘shared object server’.

    -steve.
    FMS team
    adobe systems

Comments are closed.