Section 13.8. Video Conference and Video Window


13.8. Video Conference and Video Window

In a video conference application, each user 's client publishes a stream that may carry video, audio, and other data. Each client must somehow be made aware of each stream being published by other clients so that they can all be played . The VideoConference component presented here uses a simple stream naming convention. Each stream is created within the component namespace and is named using each user's unique username. For example:

 pfcs/VideoConference/main/users/blesser/presence 

where blesser is a username and users is a subdirectory under the component's name , in this case, main . The name of the stream is presence . It also creates a shared object that contains information about the current state of the stream. The shared object has the same name as the stream.

The VideoConference component, shown in Figure 13-11, displays a separate pop-up window that shows video and plays audio for each incoming stream and contains a drop-down menu that shows information about the stream based on the information in the shared object. The VideoConference component also creates a publishing window that allows the user to control his outgoing video and audio and updates the shared object associated with the stream.

Figure 13-11. Video conference pop-up windows ; the blesser window shows the publishing controls in a drop-down list

To create a pop-up window that plays all remote users' streams, the component must know the username of each connected user. The component could create a shared object on the server containing usernames in the same way the PeopleList and PeopleGrid components do. However, since those components already manage a shared object with information about each user, the VideoConference component simply uses the PeopleList or PeopleGrid shared object. When an instance of a VideoConference component exists in a Flash movie, it can be initialized this way:

 videoConference.userName = application.user.userName; videoConference.userList = peopleList.peopleList; videoConference.nc = application.nc; 

Once the userName , userList , and nc properties are set, the component's enterConference( ) method from the VideoConference.as file, shown in Example 13-30, is called. Among other things, it saves a reference to the people shared object named __userList and sets itself up as a listener on it. If the shared object has already connected, enterConference( ) make sure the VideoConference.onSync( ) method is called once with a list of information objectsone for each user. See Section 13.4 earlier in this chapter for more information on the isConnected property and onSync( ) message broadcasting.

Example 13-30. The VideoConference component's enterConference( ) method
 function enterConference (nc, userName, userList) {   if (nc) __nc = nc;   if (userName) __userName = userName;   if (userList) __userList = userList;   __userList.addEventListener("onSync", this);   if (__userList.isConnected) {     var list = [];     for (var p in __userList.data) {        list.push({code:"change", name: p});     }     onSync({target:__userList, list:list});   } } 

The VideoConference.onSync( ) method is called by the people shared object. When a client connects to the application instance, the application creates a playback window and provides the username to indicate which user's stream it should play. When a client disconnects, the window is deleted. Example 13-31 shows the VideoConference.onSync( ) method from the VideoConference.as file.

Example 13-31. The onSync( ) method is responsible for creating and deleting video playing and video publishing windows
 function onSync (ev) {   var list = ev.list;   for (var i in list) {     var name = list[i].name;     var code = list[i].code;     // If the   code   is "delete", remove the window, if it exists.     if (code == "delete" && windows[name]) {       windows[name].deletePopUp( );       delete windows[name];       vidWinCount--;     }     // If the   code   is "change", add the window if it does not exist already.     else if (code == "change" && !windows[name]) {       var x = (vidWinCount % xMaxClips) * vidWidth + 20;       var y = Math.floor((vidWinCount & xyMaxClips) / xMaxClips) * vidHeight + 20;       vidWinCount++;       // If the name is the same as the one the user logged in with,       // display a VideoWindow and ask it to publish a stream.       if (name == __userName) {         displayVideoPublisher(name, x, y);       }       // Otherwise display a VideoWindow and have it play a stream.       else{         displayVideoPlayer(name, x, y);       }     }   } } function displayVideoPublisher(name, x, y) {   // Create a new VideoWindow pop up.   var pubWindow = mx.managers.PopUpManager.createPopUp(this,                                                 VideoWindow, false, {_x:x, _y:y});   // Have it publish a stream.   pubWindow.publish(__nc, path + "users/" + name + "/presence", name);   // Remember we opened the window by userName.   windows[name] = pubWindow; } function displayVideoPlayer(name, x, y) {   var playWindow = mx.managers.PopUpManager.createPopUp(this,                                                 VideoWindow, false, {_x:x, _y:y});   playWindow.play(__nc, path + "users/" + name + "/presence", name);   windows[name] = playWindow; } 

To avoid the possibility of creating duplicate windows for each user, the VideoConference component maintains an object named windows that keeps a reference to each video window indexed by username. Before deleting a window, the windows object is checked to make sure one exists under the userName that has just been removed from the shared object. Before adding a window, the windows object is checked to make sure a video window does not already exist with the same userName .

Aside from creating, deleting, and determining the initial position of video windows, the VideoConference component does not do much work. Most of the work of setting up camera and microphone objects, publishing and playing streams, and muting video and audio is done by the VideoWindow component.

The VideoWindow component is the most complex component presented in this chapter because it is composed of several movie clips and uses a variety of objects. The VideoWindow class is a subclass of the Window class provided with Macromedia's v2 components. It is responsible for adding a menu and minimize/maximize button to a pop-up window and handling menu, minimize/maximize, play, publish, and dragging events. When a VideoWindow is created, it adds two SimpleButton components to its titlebar and loads the VideoWindow_Contents movie clip as the window's content. The init( ) method of the VideoWindow class is listed in Example 13-32.

Example 13-32. The init( ) method of the VideoWindow component loads the VideoWindow_Contents clip and listens for the complete event to discover when it is loaded
 function init(Void):Void {   super.init( );   // Center the title.   var styleObj = _global.styles.newStyle=new mx.styles.CSSStyleDeclaration( );   styleObj.fontFamily = "_sans";   styleObj.fontSize = 12;   styleObj.fontWeight = "bold";   styleObj.textAlign = "center";   titleStyleDeclaration = styleObj;   // Load the   VideoWindow_Contents   symbol designed for this window subclass.   addEventListener("complete", this);   contentPath = "VideoWindow_Contents"; } function complete (ev) {   contentLoaded = true;   if (pendingStateInit) {     pendingStateInit = false;     vControlsState.enterState( );   } } 

The VideoWindow_Content clip contains a stack of three other objects: at the top, a menuPanel clip that contains Buttons and other components used to control stream playback or publishing; a clip that contains an embedded Video object to play video; and on the bottom, an event mask clip that captures mouse events and stops them from affecting components underneath the window. Figure 13-12 shows a VideoWindow with buttons and video playing, as well as the same window with the menuPanel clip visible. The center VideoWindow shows what the menuPanel clip looks like when it contains the components needed to control publishing a stream. The Video Mute button has been clicked to stop publishing video. The VideoWindow on the right of Figure 13-12 shows the playback components in the menuPanel clip. The video clip with the line through it indicates no new video frames are being received.

Figure 13-12. Three views of a ViewWindowwith the menu closed (left), with the publishing menu (center), and with the playing menu (right)

When its play( ) method is called, a VideoWindow delegates all the work to an object it creates to play a stream. When publish( ) is called, a different object that knows how to publish a stream is called, and the work is delegated to it. Separate objects are used because a window that is publishing a stream needs to create a camera and microphone object, create a specialized menu to control the video and audio being published, and manage menu and shared object events. On the other hand, a playback window must create and manage a different menu and handle different events.

A common way to create an object that can exhibit different behaviors, such as playing or publishing a stream, while keeping code as simple as possible, is to use separate state objects . When an object has to behave one way, a state object is used to implement behaviors. When the object has to behave differently, a different state object is used in its place. Each state object implements a different set of behaviors. An excellent and more detailed description of the state pattern is available in the book Design Patterns (Addison Wesley).

The VideoWindow component does something similar. It keeps an object in a variable named vControlsState and passes most events and requests to it. When it needs to publish a stream, it creates a PublishVideoControlsState object and stores it in vControlsState . When it needs to play a stream, it creates a PlayVideoControlsState object and stores it in vControlsState .

When a VideoWindow is first created, it creates a new VideoControlsState object this way:

 vControlsState = new VideoControlsState(this); 

The VideoControlsState class doesn't do much. However, it has two important subclasses that handle playing and publishing. Example 13-33 shows the VideoWindow.publish( ) method, which is called to have a VideoWindow publish a stream.

Example 13-33. The publish( ) method of the VideoWindow class replaces the current state with an instance of PublishVideoControlsState
 function publish (nc, streamName, displayName) {   if (nc) __nc = nc;   if (streamName) __streamName = streamName;   if (displayName) title = displayName;   vControlsState.exitState( );   vControlsState = new PublishVideoControlsState(this);   if (contentLoaded) {     vControlsState.enterState( );   }   else {     pendingStateInit = true;   } } 

Whatever else the window was doing, it stops doing it by calling:

 vControlsState.exitState(  ); 

After the vControlsState object exits, its current state can be replaced with another object:

 vControlsState = new PublishVideoControlsState(this); 

The new PublishControlsState object is passed a reference ( this ) to the VideoWindow, so it can find and manipulate any part of the window or its contents. Assuming that the contents of the pop-up window have completed loading, the new state can be started:

 vControlsState.enterState(  ); 

Otherwise, the call to enterState( ) must be delayed until all the VideoWindow assets are loaded. (See the complete( ) method in Example 13-32.)

A PublishVideoControlsState object does all the real work of publishing a stream, creating a shared object with information about the stream, and providing a control menu in a pop-up window. Its enterState( ) method will set up the drop-down menu panel with the controls needed to allow the user to control what is published within the stream and then actually start publishing the stream.

If a VideoWindow must play a stream, the play( ) method, shown in Example 13-34, changes the vControlsState object as well.

Example 13-34. The play( ) method of the VideoWindow class replaces the current state with an instance of PlayVideoControlsState
 function play(nc, streamName, displayName) {   if (nc) __nc = nc;   if (streamName) __streamName = streamName;   if (displayName) title = displayName;   vControlsState.exitState( );   vControlsState = new PlayVideoControlsState(this);   if (contentLoaded) {     vControlsState.enterState( );   }   else {     pendingStateInit = true;   } } 

As in the early publishing example, the following two statements cause the window to exit a previous state and prepare to enter the play video state:

 vControlsState.exitState(  ); vControlsState = new PlayVideoControlsState(this); 

When the contents of the pop-up window are completely loaded, the new state is initialized:

 vControlsState.enterState(  ); 

The PlayVideoControlsState and PublishVideoControlsState objects initialize themselves when their enterState( ) methods are called and clean up their resources when exitState( ) is called. When either object's enterState( ) method is called, the state attaches the menu controls it needs to initialize the menuPanel movie clip and sets itself up as a listener of their events. It gets the NetConnection object from the VideoWindow and uses it to create a SharedObject and NetStream object before publishing or playing a stream. The shared object is used to provide information about the stream to all the VideoWindows that will play the stream in other clients. It contains information on the height, width, and frames per second of the video and whether the stream is sending audio or video.

13.8.1. Publishing

A partial listing of the enterState( ) method of the PublishVideoControlsState class is provided in Example 13-35. The somewhat lengthy code that creates the components within the menuPanel clip has been removed to keep the listing at a reasonable length.

Example 13-35. An abbreviated listing of the PublishVideoControlsState.enterState( )method
 function enterState (  ) {   super.enterState( );   // Set up user interface elements.   menuPanel.visible = false;   // Place components inside the menuPanel and listen for events.   var depth = 1000;   // NOTE: CODE REMOVED HERE THAT PLACES COMPONENTS IN THE MENU PANEL.   // Set up capture sources, the stream, and shared object.   nc = context.__nc;   streamName = context.__streamName;   video = context.content.videoContainer.video;   var instance = this;   // Set up the camera.   cam = Camera.get( );   // If there is no camera, create a fake one that is muted.   if (!cam) cam = Camera({muted: true});   cam.onStatus = function (info) {     instance.setCameraStatus( );   };   cam.setMode(160, 120, 6);   menuPanel.fpsStepper.value = 6;   menuPanel.rComboBox.selectedIndex = 2;   setCameraStatus( );   // Set up the microphone.   mic = Microphone.get( );   if (!mic) mic = Microphone({muted: true});   mic.onStatus = function (info) {     instance.setMicrophoneStatus( );   };   menuPanel.gStepper.value = mic.gain;   setMicrophoneStatus( );   // Set up the stream and attach sources.   if (ns) ns.close( );   ns = new NetStream(nc);   ns.publish(streamName);   ns.attachVideo(cam);   ns.attachAudio(mic);   video.attachVideo(cam);   // Set up the shared object and add data to it after the first sync.   so = SharedObjectFactory.getRemote(streamName, nc.uri);   so.addEventListener("onFirstSync", this); // set initial control values.   so.connect(nc); } 

Once the stream is published and the shared object connected, events arrive at the PublishVideoControlsState object. For example, if the user clicks the Microphone or Camera button, a click event will be handled by the click( ) method and either setMicrophoneStatus( ) or setCameraStatus( ) will be called to attach or remove audio or video from the stream. A property of the cam or mic object is used to remember the state it was left in. The shared object will be updated so that all the clients playing the stream will be informed of the change in the stream's contents. Example 13-36 shows the click( ) and setCameraStatus( ) methods of the PublishVideoControlsState class from the PublishVideoControlsState.as file.

Example 13-36. The PublishVideoControlsState click( ) and setCameraStatus( ) methods
 function click (ev) {   var b = ev.target;   switch(b) {     case menuPanel.mButton:       setMicrophoneStatus( );       break;     case menuPanel.vButton:       setCameraStatus( );       break;   } } function setCameraStatus ( ) {   if (cam.muted) {     menuPanel.vButton.enabled = false;     menuPanel.vButton.selected = false;     so.data.video = "Off";     cam.wasMuted = true;   }   else {     if (cam.wasMuted) {       cam.wasMuted = false;       menuPanel.vButton.selected = true;;     }     menuPanel.vButton.enabled = true;     so.data.video = menuPanel.vButton.selected ? "On" : "Off";     ns.attachVideo(menuPanel.vButton.selected ? cam : null);   } } 

Now that we've looked at the publishing feature, let's continue with playing the video.

13.8.2. Playing

A partial listing of the enterState( ) method of the PlayVideoControlsState class from the PlayVideoControlsState.as file is provided in Example 13-37. The somewhat lengthy code that creates the components within the menuPanel clip has been removed to keep the listing a reasonable length. After adding the controls to menuPanel , enterState( ) creates a new NetStream object and begins to play the stream. It also sets itself up as a listener on the shared object named after the stream so that it can respond to changes.

Example 13-37. An abbreviated listing of the PlayVideoControlsState.enterState( )method
 //   enterState(  )   sets up this state object. function enterState ( ) {   super.enterState( );   video = context.content.videoContainer.video;   //  Set up user interface elements.   menuPanel.visible = false;   // Place components inside the   menuPanel   and listen for events.   var depth = 1000;   // NOTE: CODE REMOVED HERE THAT PLACES COMPONENTS IN THE MENU PANEL.   // Set up the stream and shared object.   nc = context.__nc;   streamName = context.__streamName;   if (ns) ns.close( );   ns = new NetStream(nc);   ns.play(streamName);   video.attachVideo(ns);   menuPanel.attachAudio(ns);   soundControl = new Sound(context.content.menuPanel);   menuPanel.lStepper.value = soundControl.getVolume( );   so = SharedObjectFactory.getRemote(streamName, nc.uri);   so.addEventListener("onFirstSync", this); // Set initial control values.   so.addEventListener("onSync", this);      // Reset state of sound and video.   so.connect(nc); } 

The onSync( ) method, also from PlayVideoControlsState.as , is shown in Example 13-38. If the audio or video slot of the shared object has changed, the new value is used to update the appropriate icon in menuPanel . For example, if the video property contains the string "On", the web cam icon will appear in its normal state. If the value is "Off", the web cam will appear with a line through it as seen in Figure 13-12.

Example 13-38. The onSync( ) method of the PlayVideoControlsState class
 function onSync (ev) {   var list = ev.list;   for (var p in list) {     var info = list[p];     if (info.code == "clear") continue;     switch (info.name) {       case "audio":         menuPanel.dmStatus.gotoAndPlay(so.data.audio); // "On" or "Off"         break;       case "video":         menuPanel.wcStatus.gotoAndPlay(so.data.video); // "On" or "Off"         break;       case "fps":         menuPanel.fpsStepper.maximum = so.data.fps;         if (menuPanel.fpsStepper.maximum < menuPanel.fpsStepper.value) {           menuPanel.fpsStepper.value = menuPanel.fpsStepper.maximum;         }         break;       case "res":         var w = so.data.res[0];         var h = so.data.res[1];         changeResolution(w, h);         break;       default:         trace("Error: Unhandled shared object property. " + streamName);         for (var prop in info) {           trace("   >> " + prop + ": " + info[prop]);         }     }   } } 

Similarly, when the publisher changes the frame rate of its video, the new value is displayed in menuPanel . When the res slot is changed, the changeResolution( ) method is called and both the embedded Video object and the entire VideoWindow component are resized to accommodate the new size of the video.

In summary, a people shared object is responsible for letting the VideoConference component know when a user has joined or left the application instance. The user's username is used as a unique identifier to publish her stream from her own client and to play it on all the other clients. A shared object with the same name as the stream is createdone for each userto provide additional information about the current state of the stream and the data it carries. The actual work of publishing or playing a stream and updating or responding to changes in a shared object is done by a state object in each VideoWindow.

Let's look at one more example, an advanced cousin of the PeopleList component called PeopleGrid.



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