Scripting Collaborative Applications with Flash Communication Server MX
August 2, 2002
When Macromedia's new Flash Communication Server MX hit the streets last month, its streaming media features were the focus of attention, and rightly so. Injecting a Webcam widget into a Flash application--one that you can share with anyone else who hits the Web page that embeds that Flash app--takes literally a few seconds and no lines of code. With a bit of server-side scripting, video feeds can be captured and played back on demand.
As David Doull said in his Thinking in Flash Weblog, "video and audio chatting is something different," as is the asynchronous audio/video messaging this technology enables. By way of example, Doull points to Dream Domain, where you can pilot an insect drone (which, if you like, you can genetically mutate) through a landscape of virtual rooms, each dotted with text and Webcam messages dropped by visitors.
Of course, for the white male hackers who mainly visit the site, the video messages found there are, predictably, glimpses into the mirror: white male hackers smoking cigarettes, pretending to be attacked by aliens, pressing eyeballs close to the lens. Still, as bandwidth grows and AV-equipped PCs become the rule rather than the exception, there is no question that audio and video will augment text as first-class datatypes, routinely created, exchanged, and used by everyone. At first this will mean more silly home movie-lets. Eventually, however, we'll learn how to use AV clips appropriately, to enhance business and social communication. When we look back, we'll remember that FlashComm server helped make that vision real.
But coupled with Flash MX, this product adds up to much more than an AV toolkit. FlashComm's RTMP (Real-Time Messaging Protocol) isn't just for streaming media. It supports pub/sub event distribution networks that can be wired up one to one or one to many. These events can be produced by AV streams, but also by any of the GUI widgets in Flash's arsenal. What you build with FlashComm are applications whose interfaces are shared in real-time--or, thanks to built-in persistence, asynchronously--across networked PCs among groups of people. Presence, the killer feature of IM, is woven into the fabric. Client-side and server-side code share common APIs and, crucially, both use ECMAScript (more popularly, JavaScript) to achieve a level of rapid-development productivity that Jabber (for example) lacks.
Unlike Jabber, the FlashComm experience doesn't come free. The server, which runs on Windows (soon Linux and Solaris as well), is $500 for the 10-users personal edition, and $4,500 for the 500-users professional edition. Add $500 for the Flash MX development kit, and that's $1,000 to get your feet wet, $5,000 for anything serious. You can try both for 30 days, but the ante's a bit steep for longer term experimentation. For certain corporate and educational customers that won't be a problem. And for users of FlashComm applications, the Flash 6 player is free and on a fast track to ubiquity. There are also more affordable hosting options offered by, for example, mediatemple.net. But in general, FlashComm's price tag will limit the number of developers who explore its distributed-event style of Internet programming, as will the so-far-proprietary nature of RTMP.
Other commercial and proprietary ventures are headed down similar paths. Groove's shared spaces, KnowNow's event-routing technology, and Kenamea's application network are notable examples. I regard these, and now FlashComm too, as infrastructure that complements the Web services movement. So far, the Web services plumbing is mainly about connecting services to services, machines to machines. Evolving in parallel are new ways to connect services to people, and people to one another. Plenty of this is also happening at the grassroots level, as for example Weblog developers push the envelope with innovative hacks like TrackBack and MT-RefSearch. I can't say whether and how FlashComm (or the technology inspired by it) will intersect with the grassroots Internet. I am certain, though, that the ideas it embodies will change how we think about, and create, collaborative software. So, let's explore how it works.
FlashComm 101
The FlashComm server, available for Windows (and soon, Linux and Solaris) is a compact,
easy-to-install engine with a Web-based administrative interface that (of course)
uses
Flash. To jump-start things, you'll want to install the FlashComm components as well. These are paired client-side and server-side objects.
The client parts are movie clips sourced by Flash MX from a local components.fla file
into
an application's .fla file, and thence into the published .swf application. The server
parts, which are just ECMAscript files, live on the server under
/flashcom/scriptlib/
.
An application's .html and .swf files are published to a Web-visible directory under,
for
example, /wwwroot/flashcom/applications
. Each application also has a parallel
and non-Web-visible directory under /flashcom/applications/
, where persistent
objects (if the application creates them) are stored.
In addition to the components, you'll want to download and install the Flash remoting
components if you don't already have them. These are mainly used with ColdFusion MX
and not required for FlashComm to work, but the included NetConnection debugger is
a handy
tool to have. Without this kit, you'll get errors when you try to #include
"NetDebug.as"
as the documentation suggests. As Mike Chambers, Macromedia's Flash
community manager, pointed out to me, the server can, in any case, access remote Web
services using the same NetServices APIs built into ColdFusion MX's Flash remoting
gateway.
Here's how easy things can be using the FlashComm components. I began by dragging a SimpleConnect component onto the Flash MX stage. SimpleConnect combines a login widget with a wrapper that binds the user identity it creates to any of the other widgets. Then I dragged more components onto the stage: PeopleList, Chat, UserColor, and AVPresence. I assigned instance names to each of these, and in SimpleConnect's properties pane, I made two entries:
Application Directory rtmp://myserver/mychat Communication Components [people_mc,chat_mc,colors_mc,av_mc]
Then I loaded up http://myserver/flashcom/applications/mychat/mychat.html
from three different computers. In each instance, I logged in as a different person,
selected a color for each identity, and typed a chat message. It was a fully functional
chat, with presence shown as a changing list of names in the PeopleList widget, and
a
persistent transcript. When I activated the AV stream on my Webcam-equipped PC, it
appeared
on all three stations. This is stunning leverage, on the order of what Hypercard and
then
Visual Basic 1.0 achieved.
What's equally impressive is the transparency of the whole system. Hypercard XFCNs and Visual Basic VBXs, the component technologies of that era, were opaque to the scripter. With FlashComm, both client-side and server-side components are themselves made of script, and thus fully extensible. To find out how, I decided to add a few features. First, I added a button to the application, which enables the user to clear the chat transcript. This is a feature implemented in the server part of the chat component. A few lines of ECMAScript empty the persistence-backed shared object that remembers the transcript on the server, then broadcasts a message to all clients telling them to empty their transient local transcripts.
Extending a Communication Component
On the first try I dragged a pushbutton onto the stage, called it ClearHistory, and wired it to this function. I added to the Actions for Frame 1 of the movie:
function onClearHistory() { client_nc = new NetConnection(); client_nc.connect("rtmp://myserver/mychat"); client_nc.call("FCChat.chat_mc.clearHistory"); }
This worked, but it was the wrong approach for lots of reasons. The function should be part of a modified Chat component, not wired to a stand-alone button. It unnecessarily opened an extra connection to the server, instead of sharing the one that Chat and the other widgets were multiplexing through SimpleConnect. And it needlessly hardcoded the instance name of the Chat component.
The right approach, I concluded, was to clone the Chat component and add a ClearHistory button to it. Since I'm a complete Flash novice, I admit I fumbled around quite a bit trying to figure out how. Thanks to some helpful guidance from Mike Chambers I muddled through. (It helps if you unlock the widgets you're trying to edit!) Once I opened up the Chat component, I saw that it already had the needed function, along with a namespace prefix:
FCChatClass.prototype.clearHistory = function() { this.nc.call( this.prefix + "clearHistory", null ); }
All I had to do was add a ClearHistory button to the Chat component, and name its
click
handler clearHistory
.
The next idea was to add a label called clearedBy
to the Chat component, in
order to record who last cleared the transcript. There are probably a dozen ways to
do this.
The one I came up with uses a transient shared object as a messaging channel between
the
server and all the connected clients.
On the client side, in the Chat component's connect()
method, I added:
this.clearedBy_so = // create the shared object SharedObject.getRemote(this.prefix + "clearedBy", this.nc.uri,false); this.clearedBy_so.clearHistory = function (who) // declare callback function { gChat.clearedBy.text = "by: " + who; } this.clearedBy_so.connect(this.nc); // connect the object
The first client to call SharedObject.getRemote
creates an object on the
server which, in this case, ends up being called FCChat.chat_mc.clearedBy
. (If
the third argument to SharedObject.getRemote
is true, the object persists to
the file /flashcom/applications/mychat/sharedobjects/_definst_/fcchat.chat_mc.clear
edBy
.) Subsequent clients connect to that same object.
In the server-side Chat component's constructor, I connected it to the
clearedBy_so
shared object like so:
this.clearedBy_so = SharedObject.get( this.prefix + "clearedBy", false );
Then, in the server-side Chat component's clearHistory
method--the one
invoked remotely by the function I'd wired to the ClearHistory button--I added:
user = this.getClientGlobalStorage(client).username; this.clearedBy_so.send( "clearHistory", "[" + username + "]" );
This code unpacks the username from the per-client data managed by the server, and
passes
it to the clearHistory
function attached to the client-side
clearedBy_so
shared object, which calls the local function that assigns the
passed username to the clearedBy
label.
Synchronized Persistent Objects
The PeopleList component is built using a transient shared object. That's appropriate for a real-time presence indicator, but suppose you also want to log and count the users of your chat application. Exploring this idea gave me a chance to try out persistent shared objects. I started by dragging a ListBox--an ordinary Flash UI component, not a special Communication Component--onto the Chat stage.
Here's the client-side code to create a persistent shared object in which to remember all the users who have signed in.
users_so = SharedObject.getRemote(this.prefix + "users", this.nc.uri, true); users_so.connect(this.nc);
On the server, as before, I mirrored this connection in the Chat constructor:
this.users_so = SharedObject.get( this.prefix + "users", true );
In the server's connect()
method, I added:
this.users = this.users_so.getProperty("users"); // unpack the shared object if ( this.users == null ) this.users = new Array; found = 0; for (i in this.users) // look for our user { if ( this.users[i][0] == cglobal.username ) // if found { this.users[i][1]++; // increment visit count found = 1; } } if (! found ) // otherwise { this.users.push([cglobal.username,1]); } // record first visit this.users_so.setProperty("users", this.users); // save shared object
All clients connected to this shared object automatically receive a synchronization message when its data changes. Back on the client side, here's the handler I wrote for that synch message:
users_so.onSync = function (list) { users_mc.removeAll(); // clear the ListBox for (i in users_so.data.users) // and refill it { users_mc.addItem ( users_so.data.users[i][0] + " (" + users_so.data.users[i][1] + ")" ); } };
The result is a listbox, on each client, that counts visitors and visits like so:
Jon (3) Doug (4) Gus (1)
Something Different, Something New
The demo applications included with FlashComm do a good job of sketching further possibilities. I particularly like the presentation example. A speaker sends an AV stream to an audience, along with a slide show (which is itself an embedded Flash movie). As the speaker advances through the slides, the audience stations are, by default, synched. However, they can optionally decouple and view slides independently. Audience members can ask questions using chat, or by sending an AV stream to the speaker. It doesn't take much imagination to spin out lots of useful variations on this theme.
Flash MX and the FlashComm server together deliver event-driven peer networking, streaming-media services, a productive scripting environment that targets networked teams of people, and powerful components that embody the essential tools of collaboration. We've seen all these ingredients before, but Macromedia has combined them to create something different and new: a killer framework for the rapid development of collaborative software.
Editor's Note: For many more details on client-side ActionScript programming, see ActionScript: The Definitive Guide. The first edition covers Flash 5, but 99 percent of it still applies to Flash MX. The revised Flash MX edition is due out in the fourth quarter of 2002. For anyone wanting to learn the JavaScript syntax for server-side programming, check out JavaScript: The Definitive Guide, 4th Edition.