Creating your first desktop instant messenger using Adobe AIR, Flash Media Server, and WebORB.NET

 

 


Requirements

Prerequisite knowledge

General experience building applications with Adobe Flex or Adobe AIR is suggested. It is also recommended that you have some knowledge of development techniques using .NET and an understanding of how Flash Media Server works.

 

User level

Intermediate

Required products

Flash Media Server (Download trial)

Flex Builder (Download trial)

Adobe AIR

Sample files

  open_messenger_code.zip (1937 KB)

Additional Requirements

 

WebORB for .NET version 3.3 or later

Whether you are interested in building your own branded instant messenger client or you're just looking to create a presence for your brand and users, building a desktop instant messenger is the cornerstone to establishing your brand as a household name. Many of us use instant messenger (IM) applications and GUI-based messaging clients constantly, and have come to rely on them as a daily form of communication. Popular IM clients include those available from AOL, MSN, Adium, Meebo, and Yahoo!—to name just a few.

This article describes how you can take advantage of the interoperability of Adobe Flash with a .NET back end using WebORB .NET (C#).

I've included sample files with working examples of the following concepts:

  • Using Adobe Flash Media Server for delivery of audio and video
  • Data messaging with WebORB .NET 3.3 version with MSMQ

Use the files available in the downloadable sample to follow along with this article and as the basis for building your own projects. The sample project described here can be extended on most popular back ends (see the following note about Adobe LiveCycle Data Services ES for more details).

A note about Adobe LiveCycle Data Services ES

Because WebORB uses almost identical syntax for their subscribers and producers, it would be fairly straightforward to port this solution over to an Adobe LiveCycle Data Services ES solution. The Java-based LiveCycle Data Services ES solution allows Flex clients to easily connect to Java EE-based servers and can manipulate data in a variety of ways. LiveCycle is growing to include the widest range of services possible with minimal complexity. The service registry makes it easy for developers to find the services already in existence.

Since the release of Flex Builder 2 update, the LiveCycle solution has incorporated Flex Data Services (FDS) to become what is now LiveCycle Data Services ES. LiveCycle still provides out-of-the box solutions for monitoring and high performance (over AMF and RTMP) remote procedure calls (RPC) using NetStream or RemoteObject calls once attributed to FDS.

 

Analyzing the functionality

 

Whenever you begin any development project, it is necessary to do some preplanning before you can actually build your application. To get started, you'll need to have a clear understanding of how the data will flow and the application will be triggered (see Figure 1).

 

 

The two main triggers for this sample application include a trigger that occurs when a user wishes to chat with another user, and a trigger that occurs when a user enters or leaves the chat application. Additionally, there is an initial trigger that is invoked upon connection that basically just pushes data back up to the client. In an instant messaging (IM) application such as this, that data includes the user's buddy list.

It is also critical in the planning stage to map out how the data will flow from Client A to Client B through WebORB and Flash Media Server (see Figure 2).

 

 

Let's talk through the functionality of the application. In a real-world example, User A connects to the system and their buddy list is retrieved from the database or service. (Note that this part of the project is outside the scope of this article.) User A's buddy list is then used to push data to all buddies that are currently online in order to update their buddy lists with User A's online status. Then User A's buddy list is sent to User A via a NetConnection call.

If User A wants to chat with one of their online buddies, they can simply double-click on the buddy's username on the DataGrid. Double-clicking invokes a new chat window to appear, which keeps a reference to the main buddy list. The buddy list window manages all the other open chat windows for that user through a WinManager object (see Figure 3).

 

 

This functionality should be familiar to most people who have used instant messaging applications in the past. By controlling the chat windows through the user's buddy list, a relationship is maintained between each user's set of chat sessions. Of course, since so many people have previously used chat applications, it also raises the bar for your own IM solution—because users expect a level of performance and set of features that they've experienced when using other chat applications.

Now that you've sketched out the basic plan for how the system works, it is time to delve into the code. The next section of this article explains how to control the opening and closing of the chat window using a WinManager object in Adobe AIR.

 

Using Adobe AIR to control chat windows

 

When you build an Adobe AIR application that includes dynamically created windows, it quickly becomes clear that good management must be integral to the design. As I mentioned in the previous section, users have grown accustomed to the way instant messaging applications work and they will expect the same experience when using your application. As you examine the code used to create this functionality, follow along using the sample files available for download. Your first task will be to generate the new chat window dynamically.

In this example, the IMWinManager class is used whenever a window is created, deleted, referenced, or accessed in any way. References to each new window, which are all instances of Window, are kept in a WinManager object.

 

Creating a new window

The code below generates a new chat window:

 

public function getWin(queue:String,owner:MainWindow):WinIM{ //does the current window exist? if (this.aWins[queue]==null){ //doesn't exist so create window var win:WinIM = new WinIM(); win.init(queue,owner); aWins[queue]=win; win.open(); win.addEventListener(Event.CLOSING,closeWinCall);// return win; } return this.aWins[queue]; }

 

In the code example above, getWin is a simple method that looks for a window reference, as described here:

 

this.aWins[queue]

 

If the window reference cannot be found (==null), then Adobe AIR creates a new window—accomplished with this line of code:

var win:WinIM = new WinIM()

 

Removing and deleting a window

When a user clicks the Close button, the chat window is closed. As the Close button receives the click event, the window manager (ImWinManager) needs to be notified so that the reference can be removed.

In the previous code example, every time a new window is created in the getWin method, a listener event is also added. The listener event is set to listen to Event.CLOSING. When this event is fired by clicking the Close button, the listener is alerted and that causes the chat window's reference to be cleared out of the WinManager object using this.aWins[title]=null, and by applying the removeWin method.

 

Closing all windows

When the user quits the chat application and closes their buddy window, all other chat windows should close simultaneously. You need to capture the event that indicates that the main window (the buddy list) has been closed and then ensure that any other chat windows close along with it.

When a user first logs on to the system, an event is created exactly like the one found above using Event.CLOSING. When the user logs off and the CLOSING event gets fired, it triggers a clearWins method within the ImWinManager class. The clearWins method contains a for loop that basically iterates through each item within the aWins object, closing each chat window that is referenced:

 

for(var i:String in this.aWins) removeWin(i,this.aWins[i]._buddy,false)

 

Now that you've dynamically created your chat windows, set up management for their references, and added functionality that closes all windows when a user logs off, you're ready for the next step. The next section of this article describes how to set up WebORB to handle user connections and messages.

 

Configuring WebORB

 

The first order of business when working with WebORB is to make sure that your project is pointing to the location where you installed WebORB. This is the path where WebORB may be located on your system:

C:\Inetpub\wwwroot\weborb30\WEB-INF\flex\services-config.xml

If you are using a remote server, Adobe AIR will require you to change this line:

 

<endpoint uri="rtmp://{server.name}:2037" />

 

to this:

 

<endpoint uri="rtmp://SERVER:2037" />

 

Although the browser can resolve to the correct server name with this reference:

 

{server.name}

 

the Adobe AIR application will not, unless you've updated the line as shown above.

Once the Adobe AIR project is pointing to WebORB, the next step is to update the XML file. If you are following along with the sample files for this project, open the WebORB samples folder and locate the XML file. You need to modify this file in order to override the existing example within WebORB and point to your custom DLL.

Locate the CallbackDemo folder inside WebORB, which may be found at this location:

C:\Inetpub\wwwroot\weborb30\Applications\CallbackDemo

Open the app.config XML file in the above directory and change this line of code:

 

<application-handler>Weborb.Examples.ClientCallback.AppHandler </application-handler>

 

to this:

 

<application-handler>Weborb.Examples.ClientCallback.AppHandler2 </application-handler>

 

It is important to also note that you can open the messaging-config.xml located here:

C:\Inetpub\wwwroot\weborb30\WEB-INF\flex\messaging-config.xml

and change the way WebORB serves out MSMQ messages (as desired).

In this example you are using the ExampleDestination channel. If it suits your project, you can update the XML file to change how your data is delivered and stored.

For example, you could update this line:

 

<deliverPastMessages>-1</deliverPastMessages>

 

to this:

 

<deliverPastMessages>+1</deliverPastMessages>

 

By entering a positive number, you enable all specified number of previous messages to be sent. A value of –1 sends all messages. MSMQ has a few limitations, such as a 4 MB message limitation and a 1.4–1.6 GB limit to the total messages stored on the disk. Later in this article you'll see how to add functionality to empty the message queue when both users have disconnected from the chat application.

 

Handling user connections and messages with WebORB server-side C# and MSMQ

When User A logs in to the IM application, an appConnect method is invoked. The appConnect method is shown below. When User A connects, the appConnect method accesses and uses User A's buddy list to assimilate each users' status. It also uses User A's buddy list to iterate through each buddy in the list to send User A's new online status to their buddies.

Once the appConnect function has iterated through all of the buddies, and each of their status states has been collected, this data is sent back to the User A's buddy list via an invoke call. The appConnect function also stores User A's username in memory.

Here is the appConnect method:

 

public override bool appConnect(IConnection conn, object[] parms) { // get the connections for the scope (scope comes from the base class) IEnumerator<IConnection> connections = scope.getConnections(); Object[] args = new Object[2]; args.SetValue(conn.getClient().getId(), 0); String inName = fixName(Convert.ToString(parms[0])); ArrayList buddies = getBuddies(inName); Hashtable budds = new Hashtable(); int aTot = buddies.Count; // iterate over the connections String statIn = "offline"; Int32 uid = -1; for (int i = 0; i < aTot; i++) { if (Convert.ToString(buddies[i]) == inName) { statIn = "online"; uid = Convert.ToInt32(conn.getClient().getId()); }else{ statIn = "offline"; uid = -1; } Hashtable temp = new Hashtable(); temp.Add("uname", buddies[i]); temp.Add("stat", statIn); temp.Add("value", uid); budds[buddies[i]] = temp; } while (connections.MoveNext()) { IConnection connection = connections.Current; // invoke client side function via the client connection // check to see if user on buddy list for (int i = 0; i < aTot; i++) { String uName = Convert.ToString(connection.getClient().getAttribute("uName")); if (uName.IndexOf(Convert.ToString(buddies[i])) != -1) { if (connection is IServiceCapableConnection) { ((IServiceCapableConnection)connection).invoke("clientConnected", new object[3] { inName, "online", conn.getClient().getId() }); Hashtable temp = new Hashtable(); temp.Add("uname", buddies[i]); temp.Add("stat", "online"); temp.Add("value", Convert.ToInt32(connection.getClient().getId())); budds[buddies[i]] = temp; } break; } } } //notify client about his ID args.SetValue(budds, 1); if (conn is IServiceCapableConnection) { ((IServiceCapableConnection)conn).invoke("setClientID", args); conn.getClient().setAttribute("uName", inName); } return true; }

 

All of the message queues in the chat application are created by appending the currently online users' names in alphabetical order. This strategy makes it easy to manage the chat windows on the client side, as well as the MSMQ queues on the server side. MSMQ does allow messages to be stored indefinitely, but there are some limitations with this, including a 1.5 GB limit for all queues. To avoid any problems with file size, there are a couple of items you must address.

A little C# code is required to clean up any unneeded queues and to change the user's status on the remaining connected users' buddy lists. When User A disconnects from the IM application, a disconnect function is called. The function appDisconnect uses your "UserName + UserName" convention to find any remaining MSMQ queues. If the other user participating in a chat has now also logged off the system, the function removes the queue, since both users are now offline. This approach removes unneeded queues and avoids potentially exceeding the queue size limit.

Here's the appDisconnect function:

 

public override void appDisconnect(IConnection conn) { IEnumerator<IConnection> connections = scope.getConnections(); Object[] args = new Object[] { conn.getClient().getId() }; String dName = Convert.ToString(conn.getClient().getAttribute("uName")).ToLower(); ArrayList Msmqs = getBuddies(dName); //send an update to those users on the list below int aTot = Msmqs.Count; try { if (scope.getClients().Count <= 1) { //if there are no connections left close all MSMQ queues // trace("no connections left close MSMQ's"); for (int i = 0; i < aTot; i++)removeMSMQue(getQueName(dName, Convert.ToString(Msmqs[i]))); } else { ArrayList existingQueues = new ArrayList(); while (connections.MoveNext()) { IConnection connection = connections.Current; String uName = Convert.ToString(connection.getClient().getAttribute("uName")).ToLower(); string newPath = getQueName(dName, uName); //create an array of queues that could be in use existingQueues.Add(getQueName(dName, uName)); //TODO: check to see if user to be called is on the buddy list if (connection is IServiceCapableConnection) ((IServiceCapableConnection)connection).invoke("clientDisconnected", args); } //compare names found in connection with the Msmqs names for users //remove queues that are non existant int qtot = existingQueues.Count; for (int j = 0; j < aTot; ++j) { Boolean notUser = true; string path4delete = Convert.ToString(Msmqs[j]); for (int i = 0; i < qtot; ++i) { //identify if msmq has a connected user w/ name if (Convert.ToString(existingQueues[i]) == path4delete) notUser = false; } //if connected user not found above then delete que if (notUser) removeMSMQue(path4delete); } } } catch (Exception objException) { trace("(E) an Error has occured w/ =" + objException.Message); } }

 

Setting up the Flex/Adobe AIR WebORB connection and controlling messaging

 

 

Now you're ready to set up the connection between WebORB and Adobe AIR. The first thing you'll need to do is make sure your Adobe AIR project uses the services-config.xml that I mentioned earlier in the "Configuring WebORB" section. Access your project's properties in Adobe AIR. Under compiler, make sure it lists the full path to the WebORB services-config.xml file, which should look something like this:

 

-services \\YourServer\inetpub\wwwroot\weborb30\WEB-INF\flex\services-config.xml

 

You'll also need to point to the WebORB component (weborb.swc), which defines the consumer and producer for WebORB messaging. This file should be located in weborbassets\wdm inside the directory where you installed WebORB. Make sure you add weborb.swc as one of your build path libraries in the Library path tab of the AIR Build Path dialog box (see Figure 4).

 

 

Using NetConnection with WebORB

This next section discusses creating a new NetConnection to communicate between the Adobe AIR application and WebORB. To connect to WebORB using a NetConnection you will need to do the following:

  1. Create a NetConnection.
  2. Connect the NetConnection to an application on the server to allow for Remote Method Invocation (RMI).

The following example uses CallbackDemo to connect WebORB to the Adobe AIR application:

 

var uri:String = ServerConfig.getChannel( "weborb-rtmp" ).endpoint; wOrb_nc = new NetConnection(); wOrb_nc.client = this; wOrb_nc.objectEncoding = ObjectEncoding.AMF0; wOrb_nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); wOrb_nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); wOrb_nc.connect( uri + "/CallbackDemo",usrName);

 

Once you've successfully created the connection, you can make calls to the server. The following code example shows how to make a call to a (C#) CreateMSMessage function:

 

wOrb_nc.call("CreateMSMessage",null, WorbConManager._uName, touser, MSMQname); C# CreateMSMessage method public void CreateMSMessage(String fromuser, String touser, String MSMQname) { trace("-->" + fromuser + "," + touser + "," + MSMQname); //LOOK FOR USER and pass request on IEnumerator<IConnection> connections = scope.getConnections(); while (connections.MoveNext()) { IConnection connection = connections.Current; // invoke client side function via the client connection String uName = Convert.ToString(connection.getClient().getAttribute("uName")); if (uName.IndexOf(Convert.ToString(touser)) != -1) { if (connection is IServiceCapableConnection) ((IServiceCapableConnection)connection).invoke("messageRequestIN", new object[2] { fromuser, MSMQname }); break; } } }

 

As you read through this code, notice that the C# method is a call to the messageRequestIN function. The messageRequestIN function is a public function in the same scope as the NetConnection and is used to open an instant message chat window, if a window isn't already open.

 

Using destinations for messaging with WebORB

As each user logs in and double-clicks another user's name to invoke a chat, a window should appear and the two users should be able to send messages back and forth. Whenever a chat is initiated, a consumer and producer connection is made to a queue, which allows messages to be received and sent. The next code example shows how to accomplish this.

Notice that in the following code examples, an instance of a channelSet is required to keep the correct consumer reference and scope. Here are the connectAsProducer and connectAsConsumer functions:

 

private function connectAsProducer( subTopic:String):void{ producer = new WeborbProducer(); producer.destination = "ExampleDestination"; producer.subqueue = subTopic; // send a meaningless message so the queue is created producer.send( new AsyncMessage( "Hi" ) ); } private function connectAsConsumer(subTopic:String):void{ //note that webOrb does require a ChannelSet instance. var cs:ChannelSet= new ChannelSet(); var uri:String = ServerConfig.getChannel("weborb-rtmp").uri; var channel:WeborbMessagingChannel = new WeborbMessagingChannel(subTopic, uri ); channel.addEventListener( MessageEvent.MESSAGE, _parent.receivedMessage ); cs.addChannel( channel ); consumer = new WeborbConsumer(); consumer.channelSet=cs consumer.destination = "ExampleDestination"; consumer.subqueue = subTopic; consumer.addEventListener( MessageEvent.MESSAGE, _parent.receivedMessage ); consumer.subscribe(); }

 

After you add these functions and create the connection to the queue, users of the IM application can now interact freely in the chat window (see Figure 5).

 

 

Now this is exciting: You've just added all the basic text messaging functionality to the chat application. The next section looks at Flash Media Server and discusses how it can be used to serve streaming audio and video between users participating in the IM application.

 

Streaming audio and video using Flash Media Server

 

 

This section examines how you can connect your project to Flash Media Server to stream both audio and video content. As each new chat window is created, a new NetConnection is made to Flash Media Server. These NetConnections allow streaming video and audio to be published and consumed.

This example uses the FmsConManager class to manage the connections and facilitate streaming to and from Flash Media Server. Each new chat window that is invoked in the application has the ability to do the following:

  • Extending NetConnection with FMSnetConnection
  • Create and remove a FMSnetConnection
  • Create and remove a NetStream
  • Send and receive requests from Flash Media Server

Extending NetConnection with FMSnetConnection

When any user first connects to Flash Media Server, within the application.onConnect method, a call on that user's connection is made to invoke a function called "initUser":

 

p_client.call("initUser", null,publishers.users);

 

In turn the client must route this call to the method initUser and in this example it is done by creating a class called FmsConManager that extends NetConnection class. The example also makes use of a custom dispatcher called CustomDispatcher, which is used to dispatch the servers call on initUser to any listeners on CustomDispatcher's static variable INIT_USER:

 

public function initUser(args:Array):void{ //dispatch event to listening objects var init:CustomDispatcher = new CustomDispatcher(CustomDispatcher.INIT_USER,args); dispatchEvent(init); }

 

MediaManager is the only listener in this example to the above event and it simply proxies the handler to the instance of WinIM :

 

nc.addEventListener(CustomDispatcher.INIT_USER,mainIM.initUser);

 

The WinIM instance will have a public function initUser with an event parameter of CustomDispatcher :

 

public function initUser(e:CustomDispatcher):void{ if (e._args==null)return; var tot:int= e._args.length; for (var i:int=0;i<tot;++i){ var uname:String=e._args[i].uname; if (_parent.uName==uname && e._args[i].isVcasting){ medMan.startCamera(videoLocal,uname); }else if(buddy==uname && e._args[i].isVcasting){ medMan.subscribeStream(uname,videoFMS) } } }

The initUser method will receive the event and extract an array of objects from a _args variable. The array's objects are then compared to _parent.uName and, if it is the same and isVcasting is true, then the startCamera method is invoked, within MediaManager, to have the user start publishing his stream. On the other hand, if uName is not the same but is equal to the buddy, then the user will subscribe to the buddy's stream using subscribeStream, also within MediaManager.

 

Creating and removing a NetConnection

Here you set up the chat window's functionality to create and remove a NetConnection. In FmsConManager, a call is made to get the FMSnetConnection, which extends the NetConnection class to dispatch calls made from the NetConnection. The get_NC method is shown below. If the specified NetConnection already exists, this line of code returns a reference to it:

 

if (a_NC[queue]!=null)return a_NC[queue]

Otherwise, if the NetConnection does not exist yet, the get_NC method creates a new NetConnection and uses the property of the queue name to assign it as an item of the a_NC object. Here is the get_NC method:

public function get_NC(title:String ):FMSnetConnection{ if (a_NC[title]!=null)return a_NC[title]; var _nc:FMSnetConnection = new FMSnetConnection(); _nc.objectEncoding = flash.net.ObjectEncoding.AMF0; _nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); _nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler); _nc.connect("rtmp://localhost/OpenMessenger/"+title); a_NC[title]=_nc; return _nc; }

You also need to set up handling for the event NET_STATUS. By adding event handling, the application will receive feedback on connection events, which you can use to present a message to the users of the application in case the connection fails or other connection errors occur.

In this next example, the netStatusHandler is set up to look for the events NetConnection.Connect.Success or NetConnection.Connect.Failed. The netStatusHandler function can later be updated to display connection information to the user, as desired:

private function netStatusHandler(event:NetStatusEvent):void { switch (event.info.code) { case "NetConnection.Connect.Success": break; case "NetConnection.Connect.Failed": break ; } }

When you removing a NetConnection, the delete_NC method finds the NetConnection object via the object reference within a_NC and closes it, using the following code:

public function delete_NC(queue:String):void{ if (a_NC[queue]==null)return; a_NC[queue].close(); delete a_NC[queue]; a_NC[queue]=null; }

Creating and removing a NetStream

The process for creating a NetStream is almost identical to the process you used above to create the NetConnection in the method get_NC. For this example, you are using the get_NS method to create and remove a NetStream. The FmsConManager class includes a get_NS method that checks to see if a NetStream with the specified name exists within the a_NS object. If a NetStream object with the specified name does exist, get_NS returns the reference to the NetStream object. Otherwise, get_NS creates a new NetStream and creates an item, with a reference to the new NetStream, within the a_NS object.

Here's the get_NS method:

 

public function get_NS(queue:String):NetStream{ if (a_NS[queue]!=null)return a_NS[queue]; var _ns:NetStream = new NetStream(a_NC[queue]); _ns.addEventListener(NetStatusEvent.NET_STATUS, netStatusStreamHandler); a_NS[queue]=_ns; return _ns }

 

As you review the code example above, you can see that it is very similar to the get_NC method in the earlier example. Both methods use a NET_STATUS event listener to get notifications that allow them to determine the status of things, such as whether the stream exists or a play request failed.

You also have to add functionality to remove the NetConnection because you'll want to remove the connection when a user closes the chat window. To remove the NetConnection, use the method delete_NS to find the NetStream object via the object reference within a_NS and close it.

Here's the delete_NS method:

 

public function delete_NS(queue:String):void{ if (a_NS[buddy]!=null){ a_NS[buddy].close(); a_NS[buddy]=null; } }

 

Sending and receiving requests from Flash Media Server

Now turn your attention to the server side. On the Flash Media Server you have a simple main.asc file that accepts any connecting user without authentication, sends the status of the stream, and routes messages to appropriate users. When User A begins publishing their video, a method is invoked on Flash Media Server, which then sends a message to User B which will automatically trigger user B to subscribe to said stream.

When a user clicks the button to begin sharing audio and video in the IM application, a call is made to Flash Media Server. The included example invokes serverCall, a server-side client method, using _NC.call("serverCall ",null,args), which is then routed to the connected user, while also storing the stream status into a remote shared object (RSO). The RSO is then used when a user connects to retrieve the existing stream status. This example allows the initiating user's streams to be correctly set up, if needed:

 

function updateVid(p_client,args){ trace(" == updateVid "+args); var publishers = application.vid_so.getProperty("publishers"); var tot=publishers.users.length; var users=publishers.users; for (var i=0;i<tot;++i){ if (p_client.username == users[i].uname){ users[i].isVcasting=args.isVcasting; break; } } application.vid_so.setProperty("publishers", users); // application.vid_so.send("updateVid",args); }

 

After the serverCall call is made to Flash Media Server, regardless of whether or not User B accepts or declines User A's request to receive the audio and video, a NetConnection will be created for User A to Flash Media Server. The NetConnection is created with the OpenMessenger application using the following URL, as shown in an earlier code example:

 

rmtp://localconnection/OpenMessenger/userAuserB

 

This in turn creates an instance, named userAuserB, of the Flash Media Server application OpenMessenger. For the purposes of clarity and to keep this example simple, a single stream is created for each user: userA.flv and userB.flv.

At this point, whenever a user receives a request to view or add a video, the application connects to and attaches to a local video display object, and then publishes to a stream that is referenced by the username. Publishing a stream is accomplished using the MediaManager class with the method startCamera, which starts playback of an audio and/or video stream.

Here's the startCamera method:

 

public function startCamera(vid:VidComp,myName:String):void{ //start streaming camera OUT out_ns+myname //create netstream try { camera = Camera.getCamera(); camera.addEventListener("status",CameraStatus); mic = Microphone.getMicrophone(); vid.attachCamera(camera); }catch (e:Object){ Alert.show("Check WebCamera, it may be incorrectly installed.", "(E)WebCamera Error"); return; } publishStream (myName); FmsConManager.serverCall(nc,"updateVid",null,[{isVcasting:true}]); }

 

In the code example above, the startCamera method tries to create a camera and microphone reference which it will use to create the NetStream object. If the attempt to create the NetStream object is successful, it then attaches the device references before publishing the streaming media under the user's name, which is held in the variable, myName.

After User B accepts the request to view User A's stream, a subscribeStream method is invoked, as shown below:

 

public function subscribeStream(buddy:String,vid:VidComp):void{ var ns:NetStream=FMS.get_NS(buddy+"_in",nc); vid.attachNetStream(ns); ns.play(buddy,-1); }

 

Since the request to share media was accepted, a NetStream object is created using User A's username as a reference. Next it attaches the new NetStream object to a custom videoComponent to begin playback. At this point, User B is now able to view and/or hear User A's stream by playing the buddy.flv file, which is actually userA.flv, as I stated earlier.

Note: The custom video component is included with the sample files and simply extends the UIComponent object.

Now users can see, hear, view, and chat with one another, and they are able to communicate while having an experience comparable to using other IM applications (see Figure 6).

 

 

Where to go from here

When you create new user experiences, both online and offline, the possibilities are endless and usability is key. To take this project further, a next obvious step would include adding document sharing and saving the text chat to a file directory, using the drag-and-drop API in Adobe AIR. You could also experiment with adding other functionality, such as copying and saving the video chat files by pulling the FLV files off the server.

As I stated earlier, there are many other avenues to explore when developing a chat system that shares audio and video streams among users. One idea involves building a pure Flash Media Server solution where messages use the same real-time messaging protocol that Flash Media Server uses. Also consider that WebORB, like LiveCycle Data Services ES, offers a remote shared object capability. You could modify this project to use functionality available in WebORB to replace the MSMQ back end.

As with all projects, do some preplanning to identify your project's needs and technical requirements, and also determine whether or not your solution must work within existing infrastructure. With the technologies now available, almost anything you can imagine is possible.

If you'd like to learn more about the concepts discussed in this article, check out these online resources:

 

 

 

This work is licensed under a Creative Commons Attribution-Noncommercial 3.0 Unported License.

 


 

More Like This

RTMFP multicast streaming using VOD assets