1.8. Hello Video!
I'd like to dive in and take you through building a
very simple video conference application. The application is designed
to demonstrate many of the different things described in this
chapter, such as publishing and playing streams and updating and
responding to changes in a shared object. If you
don't understand all the code as I walk through it,
don't worry. The idea is to provide a quick tour to
building a communication application. The rest of the book explains
everything in much greater detail. Although a premade video chat
application already exists, this is good exposure to the concepts and
operations you'll need when you build your own
applications.
1.8.1. Setting Up helloVideo on the Server
Creating the helloVideo application on the
server requires you find the
applications directory and add a subdirectory
named helloVideo. After the default installation
on my system, the applications directory is
located at:
- C:\Program Files\Macromedia\Flash Communication Server MX\applications
Once you create the helloVideo subdirectory, you
have created a FlashCom application. Now you need to provide the
application with its unique server-side behavior. Create a plain text
file named main.asc and save it into the
helloVideo directory. You can use any plain text
editor such as the one included with Flash MX Professional 2004 or
Dreamweaver MX 2004. Example 1-1 shows the
source code you should add to the
main.asc file.
Example 1-1. The main.asc file for the helloVideo application
idPool = ["guest_1", "guest_2", "guest_3", "guest_4"];
application.onAppStart = function ( ) {
users_so = SharedObject.get("users");
};
application.onConnect = function (client, name) {
if (idPool.length <= 0) {
application.rejectConnection(client, {msg:"Too many users."});
}
client.id = idPool.pop( );
application.acceptConnection(client);
client.call("setID", null, client.id);
users_so.setProperty(client.id, name);
};
application.onDisconnect = function (client) {
idPool.push(client.id);
users_so.setProperty(client.id, null);
};
You can also download the source files for the
helloVideo example from the
book's web site
(http://www.flash-communications.net). The
main.asc file will be loaded, compiled, and run by
the server when the first client attempts to connect to a
helloVideo instance. The
application.onAppStart( ) method will be called
once after the file is executed. From then on, whenever a movie tries
to connect, the application.onConnect(
) method will be called, and when a movie
disconnects, application.onDisconnect(
) will be called.
The main.asc file listed in Example 1-1 is designed to do three things:
Only four clients are allowed to connect at any time. So the
application creates four unique user ID values, assigns one to each
client when it connects, and reclaims the ID when the client leaves. It notifies each client of its ID by calling a remote method of the
client after the connection succeeds. It updates a shared object when a client arrives or leaves so that
all the other clients know who is connected and the name of each
client's stream to play.
The application does not use Flash Remoting to connect to an
authentication database or directory server. It is a simple
demonstration program and is not designed for security. See Chapter 18 for information on designing and building
secure applications. In the helloVideo
application, any four users are allowed to connect and each is given
a unique user ID string. The global
idPool array contains the four available ID
strings. It is created as soon as the main.asc
file is loaded by the serverusually when the first client
attempts to connect. The application.onAppStart(
) method is called immediately after the
main.asc file is loaded and executed. The
onAppStart( ) method uses
SharedObject.get( ) to create a temporary shared
object that holds an optional name provided by each user. For
example, if four users with the names
"justin",
"peldi",
"robert", and
"brian" are connected, the shared
object would have the slot names and values illustrated in Table 1-1.
Table 1-1. Slot names and values for the users_so shared object|
Slot name
|
Slot value
|
|---|
|
"guest_1"
|
"justin"
| |
"guest_2"
|
"peldi"
| |
"guest_3"
|
"robert"
| |
"guest_4"
|
"brian"
|
The following statement gets a shared object named
users and assigns it to the variable
users_so:
users_so = SharedObject.get("users");
Thereafter, we can use users_so to access the
methods and properties of the users shared object.
Whenever a client attempts to connect, the
application.onConnect(
) method is called with a client object
passed in by FlashCom that represents the client trying to connect.
Any other information supplied about the client is also passed into
onConnect( ) as additional parameters. In Example 1-1, the name a user enters is the second
parameter, name.
When onConnect( ) is called, we have the option
of rejecting or accepting the connection or leaving it pending. In
this example, if there are no user ID strings left in the
idPool, the application rejects the connection and
passes a message back to the client to say why:
if (idPool.length <= 0) {
application.rejectConnection(client, {msg:"Too many users."});
}
If there is an available ID string, it is removed from the end of the
idPool array and assigned to an
id property of the client
object (id is not a built-in property of a
Client object; we chose it to suit our own
needs):
client.id = idPool.pop( );
If an ID is available, the application will accept the
client's request to connect and send the client its
user ID by invoking setID(
), which was introduced earlier under
"Remote Methods," on the client:
application.acceptConnection(client);
client.call("setID", null, client.id);
We'll look at the client-side setID(
) method again later. Finally, the application lets all
the other clients know that a new client has connected, so they can
subscribe to the video and audio stream the client will publish:
users_so.setProperty(client.id, name);
The setProperty(
) method saves the name
parameter in a slot named after the client's ID
string.
Later, when the client disconnects by clicking the Disconnect button
or by visiting a different page with her browser, the
application.onDisconnect( ) method will be
called on the server and passed the client object representing the
client that has disconnected. When a client disconnects, we need to
reclaim her ID string for use with other clients, and we need to
delete her slot in the users shared object to
indicate she is no long connected:
application.onDisconnect = function (client) {
idPool.push(client.id);
users_so.setProperty(client.id, null);
};
The application pushes the ID back into the idPool
array and sets its slot in the shared object to
null.
1.8.2. Building the helloVideo Client in Flash
The Flash movie we are going to walk through
building will automatically publish audio and video for the person
using it and will play any audio and video being streamed from the
other clients. When it connects to the server, it receives its own
unique user ID string in return. It will publish a stream named after
its user ID while monitoring changes in the users
shared object to discover the unique ID of each user who connects. It
uses the user IDs in the users shared object to
play each remote user's stream.
1.8.2.1 Building the user interface
Figure 1-6 shows the user
interface for the helloVideo client. Each
user's video is displayed above the name he chose
when he connected. The example shows the screen Robert used to
connect.

The interface is made using four movie clips and a few components.
There is one movie clip for each user. The movie clip contains an
embedded Video object and a Label and
TextInput component. The TextInput
component in each movie clip will display the name each user enters
in the My Name field. Note the TextInput component at the bottom of
the screen containing the text
"robert." I also use a TextInput
component to display the current connection status of the
application. The button also indicates that the user is connected by
displaying the Disconnect label. If the user is not connected, it
toggles to Connect.
To build the interface:
Start with an empty Flash movie and set its dimensions to 320 x 480
using the Properties panel. Create a new library symbol using the
Insert
New Symbol (Ctrl-F8) command. In the Create New Symbol dialog box, enter the
symbol name GuestVideo and set the Behavior
type to MovieClip. Enable the Export for ActionScript option, (click
the Advanced button to display this option if it
isn't already visible). Set the Identifier field to
GuestVideo as well. When the symbol is created in the Library, the Stage displays the
empty symbol and its registration point. We want to place an embedded
Video object within the symbol so that the
video's upper-left corner is at the
symbol's registration point. To add a Video object
to the Library, open the Library panel (Ctrl-L or Cmd-L), and
choose New Video from the Library panel's Options
menu, as shown in Figure 1-7.
 To place the embedded Video object within the
symbol so that the video's upper-left corner is at
the symbol's registration point: Drag the Video object from the Library to the
Stage and position it at the GuestVideo
symbol's registration point. Use the X and Y fields
in the Properties panel as illustrated in Figure 1-8 to position it at exactly (0, 0), and give
it the instance name video. Drag one Label and one TextInput component from the
Components
panel to the Stage. Arrange them as illustrated in Figure 1-8 and give the TextInput component the instance
name nameInput. Set the Text parameter of the Label to the text
Name: using the Properties panel.
 Now that you're done creating the
GuestVideo symbol with the embedded
Video object, we want four instances on stage
(to display the four possible simultaneous clients): To return to the main movie's Stage, click the
Scene 1 link in the Timeline
panel's Edit bar. Drag the GuestVideo symbol from the Library to
the Stage four times and arrange the four instances as shown in Figure 1-6. Select each one in turn and set its name to
guest_1, guest_2,
guest_3, or guest_4 using
Flash's Properties panel. Finally, at the bottom of the movie, add the two
Label
components, two TextInput components, and one Button component.
Position and resize them as illustrated in Figure 1-6. Name the large TextInput instance
statusInput, the small TextField instance
userNameInput, and the Button instance
connectButton.
With the main Stage of the movie laid out, it's time
to start coding.
1.8.2.2 Setting up the NetConnection and showing its status
Example 1-2 shows how to create the
NetConnection object
and add methods to it dynamically. This client-side script, like the
code in the following examples, should be placed in the first frame
on a separate Scripts layer in the
movie's timeline. Have a look at the sample
helloVideo.fla file from the
book's web site and you'll find the
code from Examples Example 1-2, Example 1-3, and Example 1-4 attached to
the first frame of the Scripts layer.
After the nc variable is assigned a new
NetConnection, the code adds two methods. The
setID( ) method is invoked by the server to
notify the client of its unique user ID. The onStatus(
) method is called whenever a change in
connection status occurs. Have a look at Example 1-2
and see if you can figure out what the two
methods do. I'll discuss
it in more detail later.
Example 1-2. Setting up the NetConnection
myID = "";
nc = new NetConnection( );
/* setID( ) is a remote method that will be called by the server
* after the client connects. Once we get our unique ID from the
* server we can use it to publish our stream.
*/
nc.setID = function (id) {
myID = id;
statusInput.text = "Online. Your client's ID is: " + myID;
ns = new NetStream(nc);
ns.attachAudio(Microphone.get( ));
var cam = Camera.get( );
ns.attachVideo(cam);
ns.publish(id);
_root[id].video.attachVideo(cam);
initUsers( );
};
// onStatus( ) is called whenever the status of the network
// connection changes. It is how we know whether we are connected.
nc.onStatus = function (info) {
connectButton.label = "Connect";
connectButton.enabled = true;
switch (info.code) {
case "NetConnection.Connect.Success":
connectButton.label = "Disconnect";
statusInput.text = "Online";
break;
case "NetConnection.Connect.Failed":
statusInput.text = "Cannot reach server. Possible network error.";
break;
case "NetConnection.Connect.Rejected":
statusInput.text = info.application.msg;
break;
case "NetConnection.Connect.Closed":
statusInput.text += " Connection closed.";
break;
}
};
Some time after a connection to the server is made, the
setID( ) method is called by the server and its
id parameter is passed as one of four strings:
"guest_1",
"guest_2",
"guest_3", or
"guest_4". The setID(
) method saves the name in the myID
variable and displays it for the user in the TextInput field
statusInput. Then it uses its ID string to publish
a stream containing audio and video by creating a
NetStream object on the nc
net connection. It attaches a microphone and camera object to the
stream and publishes it using the id as the stream
name. Finally, it displays the stream it is sending in one of the
GuestVideo movie clips on the Stage. Since each
clip is named after the user IDs the server provides, there will be
one clip on the Stage with the same name as the user who has just
been assigned. The _root property, which holds a
reference to the movie's main timeline, is used to
get a reference to the movie clip. _root[id]
returns a reference to one of the GuestVideo
clips. The statement _root[id].video returns a
reference to the embedded Video object in the
clip, and _root[id].video.attachVideo(cam)
displays what the camera sees in the Video
object. The setID( ) method also
calls the initUsers(
) method to set up the
users' shared object. We'll have a
look at initUsers( ) later.
The onStatus( ) method receives an information object
that contains information about the most recent connection-related
event. The code property of the object is a string
in dot-delimited format, such as
"NetConnection.Connect.Success".
Depending on the string in the code property, a
message indicating the network connection status is displayed in the
statusInput text field. As we'll
see shortly, in order to connect to the server, the user must click
the Connect button (connectButton). At that time,
the button is disabled so the user can't attempt a
second connection while waiting for the result of the first attempt.
The onStatus( ) method therefore reenables the
button and sets its label to Connect unless the connection has been
established.
1.8.2.3 Making the connection
In Example 1-3, connectButton is
set to deliver click events to the click(
) function whenever it is clicked. The
button displays one of three labelsConnect, Wait..., or
Disconnectdepending on the state of the connection. If the
button label is Connect, the click( ) function
tries to make a connection using nc.connect(
).
If the button label is Disconnect, clicking the button closes the
current connection by
calling nc.close( ).
Example 1-3. Making or breaking a connection when the Connect button is clicked
connectButton.addEventListener("click", this);
function click (ev) {
var button = ev.target;
var command = button.label;
switch (command) {
case "Connect":
nc.connect("rtmp:/helloVideo", userNameInput.text);
button.label = "Wait...";
button.enabled = false;
break;
case "Disconnect":
nc.close( );
button.label = "Connect";
break;
}
}
Attempting to connect displays the Wait... button until the
nc.onStatus( ) method from Example 1-2 is
called and the results of the attempt are known.
1.8.2.4 Showing remote users
So far we've seen how a
connection is established and how shortly afterward the client knows
its own ID, which it uses to publish a stream by the same name.
Let's have a look at how the client knows what
streams to subscribe to and how to display their video along with the
name of each user.
When the server calls the client's setID(
) method, setID( ) calls the
initUsers( ) method shown in Example 1-4 to set up a shared object and connect it to
the users shared object. Example 1-4 does three things: it gets a shared object,
dynamically defines an onSync( ) method for it,
and then connects it using the nc net connection.
When the shared object is first synchronized with the server, any
data in the server's version of the shared object is
copied to the client's version of the shared object,
and then onSync( ) will be called to notify the
client of the changes. After that, any changes made in the
server's version result in the
client's version being updated and onSync(
) being called again. The onSync( )
method in Example 1-4 does all the work of reacting
to changes in the shared object. When invoked, it receives an array
of information objects. Each object has a code
property, which indicates what kind of change the object represents.
There are numerous possible codes as discussed in Chapter 8, but we are interested in only two of
them: "change" and
"delete". We can ignore the rest
because all of the updates and deletions of the shared object are
done by the server-side code in main.asc. A
"change" code indicates that the
server has added or updated a slot in the shared object.
Besides the built-in code property, the contents
of the information object depend on the type of change reported by
the shared object. In this example, the information
object's name property contains
the name of the slot that has changed. For example, if a user has
logged in and been given the ID
"guest_2", then an information
object with a code value of
"change" will also have a
name property of
"guest_2". If a user disconnects,
the server will delete a slot of the shared object so the
code value will be
"delete" and the
name property will identify the deleted slot. The
onSync( ) code in Example 1-4
loops through the array of information objects passed into
onSync( ) and examines each
object's code property. If the
code property is
"change", the initUsers(
) method plays the user's stream and
shows her name in one of the GuestVideo movie
clips. If the code property is
"delete," the example stops playing
the stream associated with the user who has disconnected.
Example 1-4. Setting up the users SharedObject
function initUsers ( ) {
users_so = SharedObject.getRemote("users", nc.uri);
users_so.onSync = function (infoList) {
for (var i in infoList) {
var info = infoList[i];
switch (info.code) {
case "change":
var id = info.name;
var mc = _root[id];
mc.nameInput.text = users_so.data[id];
if (myID != id) {
var ns = new NetStream(nc);
mc.video.attachVideo(ns);
ns.play(id);
mc.ns = ns;
}
break;
case "delete":
var id = info.name;
var mc = _root[id];
mc.ns.close( );
mc.nameInput.text = "";
mc.video.clear( );
break;
}
}
};
users_so.connect(nc);
}
I'd like to look more closely at the code that
starts playing a remote user's stream and shows her
name:
case "change":
var id = info.name;
var mc = _root[id];
mc.nameInput.text = users_so.data[id];
if (myID != id) {
var ns = new NetStream(nc);
mc.video.attachVideo(ns);
ns.play(id);
mc.ns = ns;
}
break;
The name property is the remote
user's ID. For example, if it is
"guest_2",
_root[id] equates to the
GuestVideo movie clip of
the same name. Once we know what clip represents the user, we can
show the name the user selected when she logged in by setting the
text of the nameInput TextInput component inside
the movie clip:
mc.nameInput.text = users_so.data[id];
Unlike the server-side code that used users_so.setProperty(
) and users_so.getProperty( ) to set
and get values in a shared object slot, in the client a special
data object is available to
read and write slot values. For example,
to get or set the guest_2 slot of the shared
object, use the expression:
users_so.data["guest_2"].
Because the server-side code changes a slot each time a user
connects, the client will receive notification that a slot has
changed when other remote users connect as well as when it connects.
However, we want to play only the streams of remote users because
there is no point in wasting bandwidth to play the local
user's own stream. Fortunately, the setID(
) method was called before the shared object was set up
and connected, so we already know the local user's
ID. If id is different from the value in the
myID variable, id represents a
remote user and we can safely play it. To play it, the code creates a
new NetStream object and attaches it as a
dynamic property of the movie clip:
var ns = new NetStream(nc);
mc.video.attachVideo(ns);
ns.play(id);
mc.ns = ns;
If a remote user disconnects, the NetStream
object playing her stream can be safely closed, her video cleared,
and the nameInput field set to an empty string:
case "delete":
var id = info.name;
var mc = _root[id];
mc.ns.close( );
mc.nameInput.text = "";
mc.video.clear( );
break;
1.8.3. Hello Video! Summary
If you've
read through the code and commentary on the
helloVideo application, you've
seen most of the communication classes working together to create a
very simple video conference application. And, while there is a lot
more we can do with Flash and FlashCom, you've
already seen many of the essential techniques for building a
communication application.
|