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=”<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}’ />”
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 "FlashCom"
</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 "FlashCom"
</echo>
<echo message=”%SYSTEMROOT%\system32\net.exe stop "FlashComAdmin"”
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:
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()
{
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.");
}
lightFuse();
}
function reloadApp_fault(o)
{
debugHeader();
debug("reloadApp_fault");
debugProps(o);
lightFuse();
}
function lightFuse()
{
nc.onStatus = null;
nc.close();
nc = null;
createEmptyMovieClip("timeout_mc", 1);
timeout_mc.st = getTimer();
timeout_mc.timeout = 3; 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(); 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()
{
}
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