Section 8.11. A Simple Video and Text Chat Application


8.11. A Simple Video and Text Chat Application

Chapter 5 described a very simple two-way chat that required each user to know that the other user was online and to manually subscribe to the other user 's stream by name . Shared objects provide the facilities required to make movies aware of all the users connected to a chat or conferencing application instance and to notify the movies about all the streams that are available.

We've already seen in Example 8-5 a server-side script that forces users to log in with a unique username. As each user logs in, his username is used as the name of a shared object property that contains a unique path assigned to him by the script. For example, if three users named " rreinhardt ", " blesser ", and " peldi " are logged in, the public/userList shared object contains the following properties:

 blesser: public/chat/blesser peldi: public/chat/peldi rreinhardt: public/chat/rreinhardt 

Movies that connect to the application instance can read the shared object and present to each user a list of usernames. Each movie can also use the unique path associated with its user's name to publish an audio and video stream and can subscribe to the stream of all the other users by retrieving the unique path for each user from the shared object.

The complete version of Example 8-11 on the book's web site contains all the files necessary to implement a chat/conference room with the following features:

  • Each user is forced to log in with a unique username.

  • A list of users is displayed in each movie and updated as users leave or log in to the chat room.

  • Every movie automatically publishes one live stream to a relative path based on the user's username: public/chat/ username . A pop-up movie clip shows the video stream being published and allows the user to stop or start sending audio or video.

  • Every movie uses the public/userList shared object to get the path to subscribe to every other movie's stream and displays a pop-up movie clip that displays the video and allows the user to mute or unmute the incoming audio and video.

  • A text chat is provided using the send( ) method of the public/textchat/messages shared object.

Example 8-5 already lists the server-side script that forces each user to log in with a unique username and updates the public/userList shared object, and earlier code snippets showed excerpts from the Chat class that implements the text chat feature. The remainder of this chapter describes how the user list is implemented and how shared objects and streams work together to provide an audio/video chat facility. The entire application, though not complex, is too large to describe in detail here.

The application has three states: Init, Login, and Chat. Each state is represented by a separate labeled sequence of frames on the timeline, as shown in Figure 8-3.

Figure 8-3. Timeline state labels for the chat application

The Init frame contains a global trim( ) function, a NetConnection subclass, and an application object that controls changing state between the Login and Chat frames. When the movie starts, these objects are created and initialized , and the playhead is sent to the Login frame. When the user submits a username, the application object attempts to connect to an application instance. If successful, the playhead is sent to the Chat frame where the real work of the movie is done. The Chat frame contains an onstage instance of the Chat movie clip symbol with the following elements:


welcome_txt

A text field that displays the current user's name


peopleList_lb

A listbox that displays the usernames of everyone in the chat room


chat_txt

A text field that displays text chat


send_txt

An input text field for writing text chat messages


send_pb

A push button for sending text chat messages

More importantly, there are two movie clip symbols in the Library that are attached to the Chat movie clip as needed:


StreamController

A movie clip symbol that displays the video being published by the Chat object and allows muting of video and audio


StreamViewer

A movie clip symbol that plays a stream and allows its audio or video to be muted

Both movie clip symbols are designed to be attached to another movie clip and expect to be passed a streamName variable containing the relative URI of a stream to either publish or subscribe to. Example 8-11 is the complete listing of the Chat class including comments that describe each method. See the book's web site for the complete .fla including the code for the StreamController and StreamViewer symbols.

Example 8-11. Chat class for the video chat application
 #initclip // Chat constructor function. Most of the setup work for this class // is done in the   init( )   method. function Chat( ) {   this.enabled = false;     // Current enabled state--see   setEnabled( )   method   this.streamLevel = 100;   // Level to place stream viewer and controller clips   this.streamClipCount = 0; // Number of clips--for pop-up clip positioning } Chat.prototype = new MovieClip( ); Object.registerClass("Chat", Chat); //   onLoad( )   sets component properties that must wait for an   onLoad   event. Chat.prototype.onLoad = function ( ) {   this.send_pb.setEnabled(this.enabled);   this.send_txt.selectable = this.enabled; }; //   setEnabled( )   locks or unlocks the text chat's input text field. Chat.prototype.setEnabled = function (flag) {   if (this.enabled == flag) {     return;   }   else {     this.enabled = !this.enabled;   }   this.send_pb.setEnabled(this.enabled);   this.send_txt.selectable = this.enabled;   if (!this.enabled) {     this.send_txt.text = "";   } }; /*   init( )   is called when the playhead reaches a frame containing the   Chat   * movie clip to set up the chat. It must be passed the user's current  * username and a reference to a   NetConnection   object that has already  * established a connection to the application instance.  */ Chat.prototype.init = function (nc, userName) {   this.nc = nc;   this.userName = userName;   this.setEnabled(false);   this.welcome_txt.html = true;   this.welcome_txt.htmlText = "Welcome <B>" + userName + "</B> to the chat area.";   // Set up the   userList_so   shared object so that it notifies the   Chat   clip when   // it needs to redraw the people list and show/delete stream clips.   this.userList_so = SharedObject.getRemote("public/userList", nc.uri);   this.userList_so.owner = this;   this.userList_so.onSync = function (list) {     this.owner.redrawPeopleList(this, list);     this.owner.displayStreams(this, list);   };   // Set up the   messages_so   shared object to receive text messages.   this.messages_so = SharedObject.getRemote("public/textchat/messages", nc.uri);   this.messages_so.owner = this;   this.messages_so.ready = false;   this.messages_so.onSync = function (list) {     if (!this.ready) {       this.ready = true;       this.owner.onMessagesReady(this);     }   };   this.messages_so.showMessage = function (msg) {     this.owner.showMessage(msg);   };   // Connect the shared objects.   this.userList_so.connect(nc);   this.messages_so.connect(nc);   // Publish a stream for this user with video and audio.   this.ns = new NetStream(nc);   this.cam = Camera.get( );   this.ns.attachVideo(this.cam);   this.mic = Microphone.get( );   this.ns.attachAudio(this.mic);   // Use the username to set the URI for the stream to publish.   this.ns.publish("public/chat/" + userName); }; //   redrawPeopleList( )   is called by   userList_so.onSync( )   . Chat.prototype.redrawPeopleList = function (so, list) {   this.peopleList_lb.removeAll( );   for (var p in so.data) {     this.peopleList_lb.addItem(p, so.data[p]);   } }; /*   displayStreamController( )   attaches a   StreamController   clip to the   Chat   clip  * and passes it the name of the stream and screen position to appear in.  * See the   displayStreams( )   method.  */ Chat.prototype.displayStreamController = function (name, streamName, x, y) {   this.attachMovie("StreamController", name, this.streamLevel++,               {_x:x, _y:y, name:name, streamName:streamName, ns:this.ns,                cam:this.cam, mic:this.mic}); }; //   displayStreamView( )   attaches a   StreamViewer   clip to the chat. Chat.prototype.displayStreamViewer = function (name, streamName, x, y) {   this.attachMovie("StreamViewer", name, this.streamLevel++,               {_x:x, _y:y, name:name, streamName:streamName}); }; /*   displayStreams( )   is called by   userList_so.onSync( )   whenever a user  * is added or removed from the user list. It is passed both a  * reference to the   userList_so   shared object and the information  * object list passed to   onSync( )   .  */ Chat.prototype.displayStreams = function (so, list) {   for (var p in list) {     // Get the information object.     var obj = list[p];     // If the   code   value is "delete" and there is a stream clip attached     // to the chat clip, delete the stream clip.     if (obj.code == "delete" && this[obj.name]) {       this[obj.name].removeMovieClip( );       this.streamClipCount--;     }     // Otherwise, if a "change" code is received, add a stream clip for new user.     else if (obj.code == "change") {       var name = obj.name;       if (!this[name]) {         var x = (this.streamClipCount % 4) * 180;         var y = (((this.streamClipCount % 8) < 4) ? 0 : 1) * 185;         this.streamClipCount++;         // Add a   StreamController   clip if the name is this user's username.         if (name == this.userName) {           this.displayStreamController(name, so.data[name], x, y);         }         else {           this.displayStreamViewer(name, so.data[name], x, y);         }       }     } // End else-if.   } }; //   onMessagesReady( )   is called when the   messages   shared object is first // synchronized and is used to visibly enable the text chat fields. Chat.prototype.onMessagesReady = function (so) {   this.setEnabled(true); }; //   showMessage( )   is called from   messages_so.showMessage( )   when text chat arrives. Chat.prototype.showMessage = function (msg) {   this.chat_txt.text += msg + "\n";   this.chat_txt.scroll = this.chat_txt.maxscroll; }; //   sendMessage( )   uses the   messages_so   shared object to   send( )   a message. Chat.prototype.sendMessage = function ( ) {   var msg = trim(this.send_txt.text);   if (msg == "") {     return;   }   this.send_txt.text = "";   this.messages_so.send("showMessage", this.userName + ": " + msg); }; #endinitclip send_pb.setClickHandler("sendMessage", this); send_txt.enterChar = String.fromCharCode(Key.ENTER); send_txt.onChanged = function ( ) {   var lastChar = this.text.charAt(Selection.getEndIndex( ) - 1);   if (lastChar == this.enterChar) {     sendMessage( );   } }; 

If you read through the code, you'll see that the Chat class's init( ) method sets up both the messages_so and the userList_so shared objects. The earlier "Broadcasting Remote Method Calls with send( )" section describes how the messages_so shared object is used to implement a text chat. The userList_so shared object's onSync( ) method is very simple:

 this.userList_so.onSync = function (list) {   this.owner.redrawPeopleList(this, list);   this.owner.displayStreams(this, list); }; 

It calls the Chat object's redrawPeopleList( ) and displayStreams( ) methods and passes a reference to the shared object and the information list to them. The displayStreams( ) method examines each information object in the list array looking for code values of "change" or "delete". When a user connects to the chat, her username is added to the user list by the server-side script and deleted when her client disconnects. After each of these events, the userList_so.onSync( ) method is called.

If the code value of an information object is "delete", then a user has left the chat application. If a StreamViewer movie clip for that user already exists, it must be deleted:

 var obj = list[p]; if (obj.code == "delete" && this[obj.name]) {   this[obj.name].removeMovieClip(  );   this.streamClipCount--; } 

Each StreamViewer movie clip is named after the unique username that each person logged in with and is attached to the Chat movie clip. Since, in this case, obj.name contains the username, this[obj.name] returns a reference to a StreamViewer movie clip if it exists.

When the information object's code value is "change", a user has logged in. However the code checks whether the user is someone connecting from another Flash movie or is the person logged in using this Flash movie:

 var name = obj.name; if (name == this.userName) {   this.displayStreamController(name, so.data[name], x, y); } else {   this.displayStreamViewer(name, so.data[name], x, y); } 

If the user connected via another Flash movie, a StreamViewer movie clip is attached to the Chat movie clip by the displayStreamViewer( ) method. Otherwise, we're dealing with the user who logged in with this Flash movie, so a StreamController movie clip is attached to the Chat clip. In either case, the relative URI to the stream will be retrieved from the user list shared object and used by the StreamViewer to subscribe to the stream or by the StreamController to control the stream:

 so.data[name] 

8.11.1. Designing with Shared Objects

In this chapter, you've seen two related ways to design applications using shared objects in some detail. In the shared ball example, the SharedBall class uses a shared object to share its x and y coordinates with other instances of itself in other movies. In that case, shared objects simply extend the idea of containing data within an object to containing data within the same object running in several clients . Internal data in an object determines the current state of an object. The shared ball position is an example of object state, and shared objects provide a mechanism for manipulating the state of every ball object in every movie.

The user list shared object does more than extend data within an object into instances of the object in several Flash movies. When the user list is updated in one place (on the server), the information in it is used by more than one object. When data in the user list changes, the people list in every client is updated and stream viewers are created or deleted.



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