Section 1.8. Hello Video


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.

Figure 1-6. The helloVideo Flash movie

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:

  1. Start with an empty Flash movie and set its dimensions to 320 x 480 using the Properties panel.

  2. Create a new library symbol using the Insert New Symbol (Ctrl-F8) command.

  3. 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.

  4. 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.

  5. 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.

    Figure 1-7. Using the Library's Options menu to add an embedded video object

    To place the embedded Video object within the symbol so that the video's upper-left corner is at the symbol's registration point:

  6. 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 .

  7. 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 .

  8. Set the Text parameter of the Label to the text Name: using the Properties panel.

    Figure 1-8. Using the Library menu to add the Video, Label, and TextInput to the GuestVideo symbol

    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):

  9. To return to the main movie's Stage, click the Scene 1 link in the Timeline panel's Edit bar.

  10. 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.

  11. 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.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net