A Real-Time Component Framework (RTCF)

By Brian Lesser
August 1, 2011

Over the years I've worked on a few moderately complex applications that used Flash Media Server (FMS). Each time I had to create a small component framework so I could manage server-side objects and their resources. In late 2009 I started posting bits and pieces of code for a new framework on Google Code:

http://code.google.com/p/rtcf/

Since then I've added to it here and there and then started using it for a Television-Studio-in-the-Cloud project. For those that might be interested I thought I would write up the basic idea behind the framework even though it is far from ready for a first release.

Introducing RTCF

The Real Time Component Framework (RTCF) is designed to provide a light-weight mechanism to help developers build modular Flash Media Server (FMS) applications. Often an FMS developer must create client and server side objects that work together. For example a Flex developer might create a TextChat.mxml component in Flex as well as TextChat.asc object in FMS. The server-side object may be responsible for managing user permissions or keeping a history of each chat. While a 1:1 pairing of server and client-side components is not always necessary it is the most common use case and the one I'll write the most about.

RTCF makes it easy for a client-side component to access a component on the server via a proxy object. The proxy represents a component on the server and provides access to the server-side component's remote methods, streams, and shared objects.

proxyObjectsWithConnection.png
Figure 1. Client and Server components working across a single RTMP connection.

Figure 1 shows an application on a client that has connected to an instance of an application running on Flash Media Server via an RTMP connection. Each client-side component uses a proxy object to access methods, streams, and shared objects belonging to the server-side component. The configuration shown in figure 1 is possible with any edition of Flash Media Server that supports server-side scripting. While the figure only shows one client connected to the sever, many clients may connect to the same application instance so that they may share information belonging to individual server-side components.


RTMFP and Peer-to-Peer

Proxy objects may also share information via peer-to-peer connections. Figure 2 is a simplified illustration showing two clients that have connected to Flash Media Enterprise Server and to each other via RTMFP.

proxyObjectsP2P_2.png
Figure 2. Two clients connected via RTMFP to the server and to each other.

Figure 2 is somewhat over simplified. In fact many clients may connect to the server but may not always pass information directly between each other. If the clients have formed a P2P mesh information may be passed from client-to-client-to-client. In this sort of arrangement there may not be a server-side component at all or there may be cases where client-side components only communicate with server-side components. The illustration shows the case where client-side components communicate with both a server-side component and directly with each other via P2P.

As of this writing, peer-to-peer communications requires the Enterprise edition of Flash Media Server or the use of a cloud service from companies like Influxis.

Distribution

There are two client-side projects supplied with RTCF:


  • RTCF - contains the core classes that make connecting to applications and creating proxy objects possible.

  • RTCFSamples - contains sample FMS components and applications built using the Flex SDK


On the server the rtcf.zip file should be unzipped into FMS's scriptlib folder so that its contents reside in scriptlib/rtcf. You can check you have done this correctly by making sure there is a .../scrptlib/rtcf/components folder.

Core Classes

There are two main classes on the client and on the server. On the client the two classes are:


  • RTCFApplication - on the client represents an FMS application instance that the client is connected to and on the server takes the place of FMS's application object.

  • RTCFComponent - acts as a proxy for the server-side component. In other words it represents a component within the server-side application instance. For client/server scenarios it is somewhat like a RemoteObject that represents a remote service. In P2P applications proxy objects may be used to access resources other clients make available within a P2P mesh.


On the server the two classes are:

  • RTCFApplication - creates and manages server-side components and takes the place of FMS's application object.

  • RTCFComponent - the base class for all server-side components.


RTCF on the Server

Writing RTCF applications involves using or extending prebuilt RTCF components or writing your own server-side RTCF components. Prebuilt RTCF components are available as asc files in the scriptlib/rtcf/components directory. Prebuilt components are available for authenticating and authorizing users, logging events, managing users, text chat, and a simple video conference.

Custom components are asc files that are placed in one of two possible places: a subdirectory of the scriptlib/rtcf/components/custom directory or a subdirectory of your application's rtcf/components/custom directory. To start with, it is best to develop components within an application rather than in the server's scriptlib path. For example if you are creating a video conferencing component you might start with it in:
.../applications/myApp/rtcf/components/custom/videoConference/videoConference.asc
If the component is to be used in more than one application and has been well tested it could be moved into:
.../scriptlib/rtcf/components/custom/videoConference/videoConference.asc

Resources and Namespaces

Server-side components can be created in an application's main.asc file. Here's an example main.asc file that instantiates several prebuilt components:


// Load RTCF:
load("rtcf/framework/RTCF.asc");

// Create a some components to test with:

var anonymousAuth = application.rtcf.createComponent('anonymousAuth', 'auth.AnonymousAuth', 'auth/anonymousAuthentication');

var roomLimiter = application.rtcf.createComponent('roomLimiter', 'auth.RoomSizeLimiter', 'auth/roomSizeLimits');

var textLogger = application.rtcf.createComponent('textLogger', 'loggers.TextLogger', 'logs/mainTextLogger');

var userManager = application.rtcf.createComponent('userManager', 'managers.users.AnonymousUserManager', 'users/UserManager');

var simpleChat = application.rtcf.createComponent('chat', 'chat.SimpleTextChat', 'chat/SimpleTextChat/public');

Once RTCF has been loaded the framework attaches itself to the application object so it is always available via application.rtcf.

In each case the application.rtcf.createComponent method is called and passed three parameters:

  1. a short id that uniquely identifies a component instance and is used for routing remote method calls;
  2. the class of the component which is also the path within the rtcf/components folder to the component's asc file;
  3. the top-level resource path where the component will store shared objects, streams, and other resources.
In the simple chat example:
  1. chat is the short id
  2. chat.SimpleTextChat is the class path
  3. chat/SimpleTextChat/public is the component's resource path. All components streams and shared objects will exist within the resource path.
What Server-Side Components Do

A typical server-side component, like the video conference component, may do any of the following common tasks:


  • listen for client connection and disconnection events

  • create and manage resources for clients such as shared objects and streams

  • call methods on remote clients or make methods available on the server for a client-side component to call.


Listening for Application Events

Application events include events when an application starts up and shuts down as well as when clients attempt to connect, attempt to authenticate, and ask for authorization.

The most commonly used events are:


  • onAuthorizedConnect - indicates that the client has already been both authenticated and authorized to connect to the application.

  • onAuthorizedDisconnect - called when a previously authorized client disconnects.


Listening for these events allows a component to adapt to the coming and going of clients that are actually using the application and ignore clients that attempt, but fail to connect. A typical use of these events is to add information to a shared object so that other clients know a client has connected or to adapt in some way to another person being in a room. The classic example of this is when adding user information to a shared object as each client connects and disconnects means that a client-side component can display a real-time user list in a chat room. The list changes automatically as the shared object changes.

Handling the onAuthorizedConnect event also makes it possible to react to different types of clients connecting. For example a user may connect, another RTMP server may connect, or an instance of Flash Media Live Encoder (FMLE) may connect.

A user manager component of some type is almost always used in an rtcf-based application. In some cases it will create a public users shared object that can be used by other client-side components that need to know when regular users are coming or going. As a result, it is not always necessary for components to listen for onAuthorizedConnect and onAuthorizedDisconnect.

Multiple components can be used to authenticate and/or authorize connections from different types of clients.

Here's a partial code listing that shows how a component can listen for the onAuthorizedConnect and onAuthorizedDisconnect events:


try { var dummy = AnonymousUserManager; } catch ( e ) {

function AnonymousUserManager(){
// Do nothing. Use the init method instead.
}

// Required for JavaScript inheritance.
AnonymousUserManager.prototype = new RTCFComponent();

AnonymousUserManager.prototype.init = function(){
// Create a users list that everyone can see
this.users = this.getSharedObject("public/users");
// Let any client, regardless of roles, read the public directory
this.allowReadAccess("public");

// Listen to events to add and remove users from the user list
this.rtcf.addListener("onAuthorizedConnect", this);
this.rtcf.addListener("onAuthorizedDisconnect", this);
}

AnonymousUserManager.prototype.onAuthorizedConnect = function(client, user){
this.users.setProperty(client.id, user.properties);
}

AnonymousUserManager.prototype.onAuthorizedDisconnect = function(client){
this.users.setProperty(client.id, null);
}
} // end catch

Whenever a client is allowed to connect the user manager's onAuthorizedConnect method will be called. This is unlike ActionScript 3 (AS3) because when using rtcf on the server, the method name must always match the event name. Also, note it is addListener and not addEventListener as it is in AS3.

Components usually setup resources in their init method. Here's an example:


AnonymousUserManager.prototype.init = function(){
// Create a shared object to act as a user's list that everyone can see:
this.users = this.getSharedObject("public/users");

// Let any client, regardless of roles, read the public directory
this.allowReadAccess("public");

// ...
}

In this example the public/users shared object will actually be in the application's resource path:

rtcfResources/auth/anonymousAuthentication/public/users

In other words, every component has its own resource path where its resources are safely isolated from other components and where separate sets of access permissions can be imposed.

By default both read and write access to all shared object and stream directories are denied by rtcf. To enable access by role or for all clients use the RTCFComponent methods: allowReadAccess, allowWriteAccess. During development a common problem when accessing streams and shared objects will be forgetting to allow access. Check the FMS admin console for read or write denied errors. RTCF is designed with security in mind. Permissions are turned off by default.

RTCF on the Client

RTCF is designed to provide a simple API for use in Flash, Flex, and pure AS3 applications. It is designed to provide the infrastructure to build FMS components on. While sample Flex components are provided, the goal of RTCF is to help you build your own FMS components rather than to provide you with a complete set of real-time components. You can also extend the supplied samples.

There are two things you have to do when working with FMS applications:
1. connect to an application instance
2. create components that work with an application instance and manage their life cycle while the client connects, disconnects, reconnects, and disconnects again from the server.

Connecting to an Application Instance
Connecting to an application is done by creating an XML configuration file that defines a sequence of connection attempts the client should make to the server. FMS applications often require the client make several attempts to connect to the server and then choose the first connection that succeeds. Here is a short config.xml file:

<application name="RTCF Test Application">
  <RTCFApplications>
    <RTCFApplication id="lobby" 
                     name="rtcf" 
                     defaulthost="fms.ryerson.ca" 
                     defaultInstance="_definst_"
                     applicationIdentifier="bigSecret">
      <connectionSequence>
        <connection protocol="rtmfp" port="1935" host="192.168.0.149"/>
        <connection protocol="rtmpe" port="80" waitTime="400"/>
        <connection protocol="rtmpe" port="443" waitTime="300"/>
        <connection protocol="rtmpt" port="80" waitTime="500"/>
      </connectionSequence>
      <components>
        <component id="chat" classPath="tests.textChat"
                   resourcePath="chat/SimpleTextChat/public/" />
        <component id="rmiTester" classPath="tests.RMITester"
                   resourcePath="rmiTester/main/" />
      </components>
    </RTCFApplication>
  </RTCFApplications>
</application>

Assuming the config.xml file is loaded into an XML object the following code can be used to create an RTCFApplication:

var rtcfApplication:RTCFApplication = new RTCFApplication();
// It's a lobby so use the lobby RTCFApplication node from the xml:
rtcfApplication.config(configXML, "lobby");
// Listen for changes in the status/state of the application.
rtcfApplication.addEventListener(StatusEvent.STATUS, applicationStateChange);

Once the rtcfApplication is ready, try to connect to the server:

var credentials:Credentials = new Credentials();
credentials.displayName = loginForm.guestName;
rtcfApplication.connect(credentials);

To start with an RTCFApplication object is in a default (and unconnected) state. Whenever it changes state it broadcasts a flash.net.StatusEvent. An application can listen for the event so it knows how the connection attempt is going or when the server or client disconnects. Here's an oversimplified example of how you could change the visual state of a Flex application depending on changing status messages:


private function applicationStateChange(event: StatusEvent):void{
trace("state change: " + event.code);
if (event.code == "connected"){
// Change the visual state of the Flex application:
currentState = "connected";
}
else {
// Change the visual state of the Flex application:
currentState = "disconnected";
}
}

Working with Components
There are two important cases where we need to work with components. One is where the component has been pre-created on the server and given a unique id. The other case is where a component must be dynamically created on the server and on the client. In the config.xml file there were component definitions for a component with an id and without.


<components>
<component id="chat" classPath="tests.textChat"
resourcePath="chat/SimpleTextChat/public/" />
<component id="rmiTester" classPath="tests.RMITester"
resourcePath="rmiTester/main/" />
<component id="" classPath="tests.TextChat"
resourcePath="rmiTester/breakout/" />
</components>

Note that both components have an id. For now we only discuss the simpler case of a pre-existing component with an id on the server. However there are cases where components must be created dynamically.

Here's how to get an RTCFComponent with an id from an RTCFApplication object:

rtcfChatComponent = rtcfApplication.getComponent("chat");

Once you have a component you can use it to get resources related to the component on the server including:


  • remote shared objects

  • streams


For example, to get and use a remote shared object associated with a component:

chatMessagesSharedObject = rtcfChatComponent.getSharedObject("chatMessages");
chatMessagesSharedObject.client = this;
chatMessagesSharedObject.addEventListener(SyncEvent.SYNC, onSync);
chatMessagesSharedObject.connect();

Note that the shared object name "chatMessages" may actually refer to a shared object in a resource path like this: rtcfResources/SimpleTextChat/main/chatMessages. Server-side components manage their resources in their own folders but the client only needs to know the path of the shared object within the component's folders. The framework does the rest. Similarly notice that the connect call does not include a NetConnection reference. That's because the chatMessagesSharedObject in the example is actually an instance of RTCFSharedObject that wraps a shared object.

Here's how to call methods on a server-side component within the client:

rtcfChatComponent.someComponentMethod.addEventListener(ResultEvent.RESULT, handleResult);
rtcfChatComponent.someComponentMethod.addEventListener(FaultEvent.FAULT, handleFault);
rtcfChatComponent.someComponentMethod(params);

The code showed using the RTCFComponent as a proxy object and setting up the usual result and fault listeners before calling a remote method.

To receive calls from the server:

rtcfChatComponent.addEventListener("methodName", functionReference);

RTCF provides a similar approach when using streams:

chatStream = rtcfChatComponent.getSharedStream("chatMessages");
chatStream.client = this;
chatStream.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
chatStream.connect();
//...
chatStream.publish("mp4:" + myStream.name, "record");

Again there is no need for resource path prefixes or connection parameters as they are handled by the RTCFStream class returned by the RTCFComponent.

Current State of RTCF

There is a lot left to do in the framework. The NetStream wrapper is in need of a lot of work as the play2 method has not been wrapped and there is some work to do to correctly handle stream URIs. There aren't many sample components yet either. I hope that changes with time. I've started some proof of concept work on a whiteboard component but don't know how much time I'll have for it. Work on RTCF happens when something more is needed in the framework to support the Television-Studio-in-the-Cloud project.

Nevertheless, I hope there's enough there that people who work with FMS may at least find it interesting. If I have time I'll post more about RTCF here in the future.

Credits

Much of RTCF has been shaped by my initial experiences working with Macromedia's component framework for Flash Communication Server (FCS). I learned a lot from working with it. While Macromedia's old server-side code still ships with FMS it has some security weaknesses and the client-side components have been abandoned by Adobe. For my work it was better to start fresh.

I also need to thank Arianne de Guzman and Uwe Schulz for actually using RTCF. There's nothing like explaining your code to someone else to make you realize how poorly documented your work is and how many things you still have to do.


You might also be interested in:

News Topics

Recommended for You

Got a Question?