More Books
Flash Communication Server
Flash Communication Server
Table of Contents
Copyright
About the Authors
Brian Lesser
Giacomo
Joey Lott
Robert Reinhardt
Justin Watkins
Foreword
Preface
What Does FlashCom Offer?
What's in This Book?
How to Use This Book
Audience
ActionScript 1.0 Versus ActionScript 2.0
Server-Side ActionScript
The flash-communications.net Site
Director, Breeze, and Other Options
Flash Video Options
Licensing and Hosting Options
Conventions Used in This Book
Voice
Using Code Examples
Safari Enabled
Comments and Questions
Acknowledgments
Part I:  FlashCom Foundation
Chapter 1.  Introducing the Flash Communication Server
Section 1.1.  Clients and Servers
Section 1.2.  Creating an Application
Section 1.3.  Real-Time Messaging Protocol
Section 1.4.  The Communication Classes
Section 1.5.  Communicating with Application Servers, Databases, and Directory Servers
Section 1.6.  Firewalls and Security
Section 1.7.  Getting Started
Section 1.8.  Hello Video!
Section 1.9.  Conclusion
Chapter 2.  Communication Components
Section 2.1.  Overview of Communication Components
Section 2.2.  Summary of Communication Components
Section 2.3.  Creating an Application that Monitorsa Connection
Section 2.4.  Building a Simple Chat Room
Section 2.5.  Adding Audio and Video to the Chat Room
Section 2.6.  Forgoing the SimpleConnect Component
Section 2.7.  Conclusion
Chapter 3.  Managing Connections
Section 3.1.  Making a Connection
Section 3.2.  Managing a Connection
Section 3.3.  Reusing a NetConnection Object
Section 3.4.  Multiple Simultaneous NetConnection Objects
Section 3.5.  Testing and Debugging Network Connections
Section 3.6.  Subclassing the NetConnection Class
Section 3.7.  Communication Components Without SimpleConnect
Section 3.8.  Conclusion
Chapter 4.  Applications, Instances, and Server-Side ActionScript
Section 4.1.  Scripting Application Instances
Section 4.2.  Differences Between Flash ActionScript and Server-Side ActionScript
Section 4.3.  The Life of an Application Instance
Section 4.4.  Running a Simple Hello World Test Script
Section 4.5.  A More Realistic Example
Section 4.6.  Instance-to-Instance Communications
Section 4.7.  Script Filenames and Locations in Detail
Section 4.8.  Testing and Debugging Server-SideScript Files
Section 4.9.  Designing Communication Applications
Section 4.10.  Conclusion
Part II:  Audio, Video, and Data Streams
Chapter 5.  Managing Streams
Section 5.1.  A Simple Publisher/Subscriber Example
Section 5.2.  Stream Names
Section 5.3.  Publishing Streams in Detail
Section 5.4.  Playing Streams in Detail
Section 5.5.  The Stream Class
Section 5.6.  Publishing and Playing ActionScript Data
Section 5.7.  Creating Synchronized Presentations
Section 5.8.  The NetStream and Stream Information Objects
Section 5.9.  Stream Enhancements and Limitations
Section 5.10.  Conclusion
Chapter 6.  Microphone and Camera
Section 6.1.  Working with Microphone/Audio Input
Section 6.2.  Working with Camera Input
Section 6.3.  Building a Message-Taking Application
Section 6.4.  Building a Surveillance Application
Section 6.5.  Conclusion
Chapter 7.  Media Preparation and Delivery
Section 7.1.  Audio and Video Compression
Section 7.2.  Converting Prerecorded Materialto FLV Format
Section 7.3.  Using Flash Pro's Media Components
Section 7.4.  Enabling Multiple Bit Rate FLVsWithin an Application
Section 7.5.  Streaming MP3 Audio
Section 7.6.  Conclusion
Part III:  Remote Connectivity and Communication
Chapter 8.  Shared Objects
Section 8.1.  Objects and Shared Objects
Section 8.2.  Getting a Shared Object in Flash
Section 8.3.  Updates and Frame Rates
Section 8.4.  Scripting Shared Objects on the Server
Section 8.5.  Temporary and Persistent Shared Objects
Section 8.6.  Proxied Shared Objects
Section 8.7.  Shared Objects and Custom Classes
Section 8.8.  Avoiding Collisions
Section 8.9.  Optimizing Shared Object Performance
Section 8.10.  Broadcasting Remote Method Callswith send( )
Section 8.11.  A Simple Video and Text Chat Application
Section 8.12.  Conclusion
Chapter 9.  Remote Methods
Section 9.1.  Why Use Calls?
Section 9.2.  The send( ) and call( ) Methods
Section 9.3.  Client-to-Server Calls
Section 9.4.  Server-to-Client Calls
Section 9.5.  Server-to-Server Calls
Section 9.6.  A Simple Lobby/Rooms Application
Section 9.7.  Debugging Calls
Section 9.8.  Advanced Topics
Section 9.9.  Conclusion
Chapter 10.  Server Management API
Section 10.1.  Connecting to the Admin Service
Section 10.2.  Using the Server Management API
Section 10.3.  Server Management API Uses
Section 10.4.  Conclusion
Chapter 11.  Flash Remoting
Section 11.1.  The Remoting Gateway
Section 11.2.  Remoting Basics
Section 11.3.  Role of Remoting in FlashCom Applications
Section 11.4.  Securing Access
Section 11.5.  Conclusion
Chapter 12.  ColdFusion MX and FlashCom
Section 12.1.  Understanding ColdFusion MXand Flash Remoting
Section 12.2.  Using Flash Remoting to Log Events
Section 12.3.  Getting a List of Streams
Section 12.4.  Using ColdFusion and FTP to Mirror Streams
Section 12.5.  Conclusion
Part IV:  Design and Deployment
Chapter 13.  Building Communication Components
Section 13.1.  Source Files
Section 13.2.  People Lists
Section 13.3.  A Simple People List
Section 13.4.  Listenable Shared Objects
Section 13.5.  Status and People List
Section 13.6.  Text Chat
Section 13.7.  Shared Text
Section 13.8.  Video Conference and Video Window
Section 13.9.  PeopleGrid
Section 13.10.  Summary
Section 13.11.  Conclusion
Chapter 14.  Understanding the Macromedia Component Framework
Section 14.1.  The Component Framework
Section 14.2.  Under the Hood of the Chat Component
Section 14.3.  Creating a Simple Component from Scratch: SharedTextInput
Section 14.4.  Creating a Container Component: SharedAddressForm
Section 14.5.  Creating an Authenticating Component
Section 14.6.  Integrating Components with Your Existing Applications
Section 14.7.  Understanding the Framework
Section 14.8.  Conclusion
Chapter 15.  Application Design Patterns and Best Practices
Section 15.1.  Shared Object Management
Section 15.2.  Moving Code to the Server
Section 15.3.  Building Façades on the Server
Section 15.4.  Server-Side Client Queues
Section 15.5.  A Framework for Recording and Playing Back Componentized Applications
Section 15.6.  Components and Component Frameworks
Section 15.7.  Conclusion
Chapter 16.  Building Scalable Applications
Section 16.1.  Coordinating Instances
Section 16.2.  Scalability and Load Balancing
Section 16.3.  Conclusion
Chapter 17.  Network Performance, Latency,and Concurrency
Section 17.1.  Latency
Section 17.2.  Bandwidth
Section 17.3.  Concurrency
Section 17.4.  Conclusion
Chapter 18.  Securing Applications
Section 18.1.  The Three A's: Authentication, Authorization, and Accounting
Section 18.2.  Authentication
Section 18.3.  Authorization
Section 18.4.  Accounting
Section 18.5.  Suggestions and References
Section 18.6.  Conclusion
Index
SYMBOL
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
R
S
T
U
V
W

3.6. Subclassing the NetConnection Class

You can extend the features of the NetConnection class by creating a subclass based on it. Creating subclasses lets you encapsulate code that operates on the NetConnection object within the subclass's methods such as connect( ), onStatus( ), or close( ). In turn, this can lead to more modular and easier to maintain applications. The key to doing this is to use the super operator. Example 3-8 shows a very simple example of how to extend the NetConnection class and then how to use it within a simple test script.

Example 3-8 uses AS 1.0 syntax so it works in both Flash MX and Flash MX 2004. This also allows you to compile using ActionScript 1.0 under File Publish Settings ActionScript. Example 3-10 provides an alternative implementation that uses AS 2.0 syntax and the v2 UI component's EventDispatcher class.

Example 3-8. A simple extension of the NetConnection class
// Constructor function must call super(  ).
function NetConnectionSubClass ( ) {
  super( );
}

// Subclass NetConnection by assigning an instance of it
// to the prototype object of the new subclass.
NetConnectionSubClass.prototype = new NetConnection( );

// A simple demonstration onStatus( ) event handler that just
// writes out each info.code message.
NetConnectionSubClass.prototype.onStatus = function (info) {
  trace("info.code: " + info.code);
};

/* This connect( ) method may be passed any number of arguments.
 * The apply( ) method makes sure all arguments are passed into
 * the super.connect( ) method when it is called.
 */
NetConnectionSubClass.prototype.connect = function ( ) {
  return super.connect.apply(super, arguments);
};

// Create an instance of the NetConnectionSubClass subclass.
lobby_nc = new NetConnectionSubClass( );

// Attempt the connection.
if (lobby_nc.connect("rtmp:/testLobby/", "Guest", "Guest")) {
  trace("Attempting connection...");
}
else {
  trace("Can't attempt connection. Is the URI correct?");
}

The super operator is used in two different ways in Example 3-8. First, it must be called within the constructor function as follows:

super(  );

Invoking super( ) in this way calls the NetConnection constructor (the superclass's constructor). Second, whenever a NetConnection method must be called by a subclass's method, the super operator can be used to call the superclass's method. In this example, when the NetConnectionSubClass.connect( ) method is called, it in turn uses super to call the NetConnection.connect( ) method as follows:

return super.connect.apply(super, arguments);

Using the apply( ) method provides a good, general purpose way to call the superclass's connect( ) method. Passing super as a parameter ensures that connect( ) is called as a method of the NetConnection superclass and passing arguments insures that all the parameters passed to the subclass's connect( ) method are passed on to the superclass's connect( ) method.

A simpler, but less general purpose, way to call the NetConnection.connect( ) method is to specifically pass individual parameters this way:

NetConnectionSubClass.prototype.connect = function (targetURI, userName, password) {
  return super.connect(targetURI, userName, password);
};

In cases in which parameter values must be manipulated before being passed to the superclass's connect( ) method or those in which not all parameters must be passed on, it is often simpler to avoid using the apply( ) method.

On its own, Example 3-8 shows the basic mechanics of creating a subclass of the NetConnection class, but let's see what advantages this offers. Looking back at Example 3-4 and Example 3-6, you will see that before calling the connect( ) or close( ) methods, the handleCloseEvents flag had to be set each time. This is extra work that shouldn't have to be done in the doConnect( ), doChat( ), and doLobby( ) functions.

In fact, these details can be hidden inside a NetConnection subclass's methods as follows:

// close(  ) turns off the handleCloseEvents flag before closing the connection.
LobbyChatConnection.prototype.close = function (  ) {
  this.handleCloseEvents = false;
  super.close( );
};

If we modify the connect( ) method to set the handleCloseEvents flag as well, the doLobby( ) function can be greatly simplified:

// Called when the Lobby button in a chat room is clicked.
function doLobby(btn) {
  lobbyChat_nc.close(  );
  lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
                        userName, password);
}

Compare the preceding code to the doLobby( ) function in Example 3-6. You may notice that something else is missing. In the "Reusing a NetConnection Object" section of this chapter, two onStatus( ) event handlers were written: one for managing lobby connections and the other for managing chat room connections. The doLobby( ) and doChat( ) functions were responsible for making sure the right onStatus( ) handler was in place before calling connect( ). In some cases, this is necessary. However, when the onStatus( ) handlers are almost identical, there is a more elegant solution. The main difference between the handlers is the frame to which they move the playhead when a connection is made. A more general purpose onStatus( ) handler can call a function when a connection is established. In Example 3-9, one of two simple functions is called by the onStatus( ) handler depending on what application it connects to:

// Called when a connection has been established to the testLobby app.
function handleLobbyConnection(  ) {
  writeln("Success, you are connected to the lobby!");
  gotoAndPlay("Lobby");
}

// Called when a connection has been established to a chat room.
function handleChatConnection( ) {
  writeln("Success, you are connected to a chat room!");
  gotoAndPlay("ChatRoom");
}

How does the onStatus( ) handler know which of these functions to call? In this example, a reference to one of them is passed into the connect( ) method and saved in the connectionHandler property. Example 3-9 is a complete listing of a test script in which the onStatus( ) method uses the connectionHandler property to call either the handleLobbyConnection( ) or handleChatConnection( ) function.

Example 3-9. NetConnection subclass test script
// writeln( ) writes messages into a text field named trace_txt
// and is a replacement for using trace( ) and the Output panel.
function writeln (msg) {
  trace_txt.text += msg + "\n";
  trace_txt.scroll = trace_txt.maxscroll;
}

// LobbyChatConnection( ) is a constructor function for a NetConnection subclass.
// It will not work unless super( ) is called within the function.
function LobbyChatConnection ( ) {
  super( );
}

LobbyChatConnection.prototype = new NetConnection( );     // Subclass NetConnection.
LobbyChatConnection.prototype.handleCloseEvents = true;  // Initial value.

// connect( ) is always passed four parameters, updates the handleCloseEvents flag,
// and calls the superclass's connect( ) method.
LobbyChatConnection.prototype.connect = function (targetURI, connectionHandler,
                                                  userName, password) {
  this.connectionHandler = connectionHandler;
  this.handleCloseEvents = true;
  var result = super.connect(targetURI, userName, password);
  if (result) {
    writeln("Please wait. Attempting connection...");
  }
  else {
    writeln("Can't attempt connection to: " + this.uri);
    writeln("Is the URI correct?");
  }
  return result;
};

// close( ) turns off the handleCloseEvents flag before closing the connection.
LobbyChatConnection.prototype.close = function ( ) {
  this.handleCloseEvents = false;
  super.close( );
};

/* onStatus( ) calls the current connectionHandler when a successful connection
 * is made. It reports closed connections and errors except when closed
 * connections are expected. When a connection is closed, the playhead is
 * sent to the Login frame.
 */
LobbyChatConnection.prototype.onStatus = function (info) {
  // Always deal with successful connections.
  if (info.code == "NetConnection.Connect.Success") {
    this.handleCloseEvents = true;
    this.connectionHandler( );
  }
  // Handle messages when the connection is closed.
  if (!this.isConnected && this.handleCloseEvents) {
    // Always handle rejection messages when they occur.
    if (info.code == "NetConnection.Connect.Rejected") {
      writeln(info.application.Message);
      writeln('Did you use the username "Guest" and password "Guest" ?');
    }
    else {
      writeln("Error: Connection Closed.");
    }
    this.handleCloseEvents = false;
    gotoAndPlay("Login");
  }
  // Handle remote method call errors here if you need to.
};

// Called when a connection has been established to the testLobby app.
function handleLobbyConnection ( ) {
  writeln("Success, you are connected to the lobby!");
  gotoAndPlay("Lobby");
}

// Called when a connection has been established to a chat room.
function handleChatConnection ( ) {
  writeln("Success, you are connected to a chat room!");
  gotoAndPlay("ChatRoom");
}

// Second, create a LobbyChatConnection object.
lobbyChat_nc = new LobbyChatConnection( );

// When the Connect button in the Login frames is clicked, this
// function attempts to connect to the testLobby application.
function doConnect ( ) {
  // Keep track of the username and password
  _global.userName = userName_txt.text;
  _global.password = password_txt.text;

  // Try to connect to the lobby.
  lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
                       userName, password);
}

// Called when the Lobby button in a chat room is clicked.
function doLobby (btn) {
  lobbyChat_nc.close( );
  lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
                       userName, password);
}

// Called when the Chat button in the lobby is clicked.
function doChat (btn) {
  lobbyChat_nc.close( );
  lobbyChat_nc.connect("rtmp:/testChat/room1", handleChatConnection,
                       userName, password);
}

// Called when the Disconnect button is clicked.
function doDisconnect ( ) {
  lobbyChat_nc.close( );
  gotoAndPlay("Login");
}

gotoAndPlay("Login");

The onStatus( ) handler in Example 3-9 moves the playhead to the Login frame itself. If necessary, the more general purpose mechanism of calling a function could be used in the same way that the connectionHandler( ) function was called.

Now let's look at how to create a NetConnection subclass using ActionScript 2.0. The FCSConnector class defined in Example 3-10 is just one way to do it. Like the previous examples, it uses an internal handleCloseEvents property to remember whether it should report a "NetConnection.Connect.Closed" code. It is also designed to take advantage of the event-passing system introduced in the UI components distributed with Flash MX 2004. Whenever onStatus( ) is called, one of several possible events is dispatched to any objects that have added themselves as event listeners. Events are dispatched by the dispatchEvent( ) method. You may notice in the listing that the dispatchEvent( ) method is never defined. In fact, it is added to each FCSConnector object when the EventDispatcher class initializes the object in the FCSConnector( ) function.

Example 3-10. An ActionScript 2.0 NetConnection subclass
class com.oreilly.pfcs.FCSConnector extends NetConnection {
  // EventDispatcher needs these.
  var addEventListener:Function;
  var removeEventListener:Function;
  var dispatchEvent:Function;
  var dispatchQueue:Function;

  // Internal data.
  var handleCloseEvents:Boolean = true;

  function FCSConnector ( ) {
    super( );
    mx.events.EventDispatcher.initialize(this);
  }

  function connectionClosed (type, info) {
    if (handleCloseEvents) dispatchEvent({target:this, type:type, info:info});
    handleCloseEvents = false;
  }

  function onStatus (info) {
    switch (info.code) {
      case "NetConnection.Connect.Success":
        dispatchEvent({target:this, type:"onConnect", info:info});
        break;
      case "NetConnection.Connect.Rejected":
        connectionClosed("onReject", info);
        break;
      case "NetConnection.Connect.Closed":
        connectionClosed("onClose", info);
        break;
      case "NetConnection.Connect.Failed":
        connectionClosed("onFail", info);
        break;
      case "NetConnection.Connect.AppShutdown":
        connectionClosed("onClose", info);
        break;
      case "NetConnection.Connect.InvalidApp":
        connectionClosed("onReject", info);
        break;
      case "NetConnection.Call.Failed":
        dispatchEvent({target:this, type:"onCall", info:info});
        break;
    }
  }

  function connect ( ) {
    handleCloseEvents = true;
    return super.connect.apply(super, arguments);
  }

  function close ( ) {
    handleCloseEvents = false;
    super.close( );
  }
}

The FCSConnector class does not simply create an onStatus event and pass on the information object as part of the event. Instead its onStatus( ) method looks at the info.code string it receives and dispatches one of the following events: onConnect, onReject, onFail, onClose, or onCall. Creating separate events has advantages and disadvantages. It allows someone using the FCSConnector class to receive only the events he is interested in and to write separate functions to handle each type of event. However, in some applications, it may be less work to handle a single onStatus event. Example 3-11 lists code for a simple demonstration movie that uses the FCSConnector class. It imports the FCSConnector class, stores a single instance of it in the nc variable, and then adds the main timeline as a listener for all four connection-related events. Each event is delivered to a function or method of the same name.

Example 3-11. Importing and using the FCSConnector class in the main timeline of a movie
import com.oreilly.pfcs.FCSConnector;

// Create a new FCSConnector.
nc = new FCSConnector( );

// Add the _root timeline as a listener for connection-related events.
// Note: Since this code is on the main timeline this refers to _root.
nc.addEventListener("onReject", this);
nc.addEventListener("onFail", this);
nc.addEventListener("onClose", this);
nc.addEventListener("onConnect", this);

// Handle "NetConnection.Connect.Failed" messages here.
function onFail (ev) {
  var info = ev.info;
  writeln("Can't reach the server.")
  writlen("Please check your network connection and try again.");
  writeln("info.code: " + info.code);
  connectButton.label = "Connect";
}

// Handle "NetConnection.Connect.Success" messages here.
function onConnect (ev) {
  var info = ev.info;
  writeln("You are now connected.");
  writeln("info.code: " + info.code);
  connectButton.label = "Disconnect";
}

// Handle "NetConnection.Connect.Rejected" messages here.
function onReject (ev) {
  var info = ev.info;
  writeln("Your connection attempt was rejected.");
  writeln("info.code: " + info.code);
  writeln("info.description: " + info.description);
  if (info.application) {
    for (var p in info.application) {
      writeln("info.application." + p + ": " + info.application[p]);
    }
  }
  connectButton.label = "Connect";
}

// Handle "NetConnection.Connect.Closed" messages here.
function onClose (ev) {
  var info = ev.info;
  writeln("Your connection was closed.");
  writeln("info.code: " + info.code);
  connectButton.label = "Connect";
}

connectButton.addEventListener("click", this);

function click (ev) {
  var button = ev.target;
  if (button.label == "Connect") {
    if (nc.connect("rtmp:/testLobby",
                   userNameTextInput.text,
                   passwordTextInput.text)) {
      button.label = "Wait...";
      writeln("Attempting to connect...");
    }
    else {
      writeln("Can't attempt connection. Check the URI.");
    }
  }
  else {
    writeln("Connection closed.");
    button.label = "Connect";
    nc.close( );
  }
}

function writeln (msg) {
  msgTextArea.text += msg + "\n";
  msgTextArea.vPosition = msgTextArea.maxVPosition;
  msgTextArea.redraw( );// Fixes scrolling bug in TextArea as of Flash 7.2.
}

All the preceding examples are available in a Zip archive file from http://flash-communications.net. The archive includes the file com/oreilly/pfcs/FCSConnector.as. That file must be extracted from the archive with its path intact (most unZip programs have a Maintain Folders option for this purpose) and placed in a directory in the classpath of Flash MX 2004. See Chapter 9 of Essential ActionScript 2.0 (O'Reilly) for more information on packages and the classpath.