Accessibility

Flash Article

 

Building a Live Video Switcher with Flash Communication Server MX


Table of Contents

Comments

Building the Broadcaster/Encoder Application

File: simpleBroadcaster.fla

The Broadcaster application is used to publish input from a webcam or other video source to Flash Communication Server. This section of the article shows you how to build the functionality required to publish a stream. It also demonstrates how to use a slider component to set the Camera and Microphone device properties both locally and remotely. First, let's review the user interface for the Broadcaster application (see Figure 1).

The finished Live Broadcaster application

Figure 1. The finished Live Broadcaster application

To build this application following the steps in this article, start with the prebuilt files in the fcsBroadcast_start folder. This folder is located in the ZIP package you downloaded. This folder contains three prebuilt interfaces and a server-side ActionScript file.

In this section of the article I show you how to calculate the bandwidth you need automatically, and give your users a very simple interface to control their video feed.

Broadcaster Step 1: Set Up the Connection

First, create an instance of the NetConnection class and then connect the Flash client to your Flash Communication Server using the connect method. Place this ActionScript (and all ActionScript in this article) on Frame 1 of the Actions layer:

nc = new NetConnection();
nc.onStatus= ncStatus;
nc.connect("rtmp://localhost/fcsBroadcast_start", "BROADCASTER");

The connect method sends the string "BROADCASTER" as the second parameter. This triggers special processing in server-side ActionScript and sets the connection as a Broadcast client. Each of the three applications identify themselves as either BROADCASTER, PLAYER or SWITCHER.

The NetConnection.onStatus handler (ncStatus) performs three operations:

  1. References the capture devices (initDevices())
  2. Connects to the broadcaster shared object with the initSO() function
  3. Calls a custom Client method on Flash Communication Server to return the Client object.

I talk more about the broadcaster shared object in Broadcaster Step 4. The shared object monitors changes to the broadcast stream—for example, if the switcher makes the stream live or remotely sets the Camera or Microphone properties.

The NetConnection call to getClientObj returns the Client object (information about the connection and the stream) from Flash Communication Server to Flash Player. The returned Client object contains an important property called streamName that is created for broadcasters when they connect. Figure 2 shows the full Client object structure:

function ncStatus(nsObj) {
   switch(nsObj.code) {
      case "NetConnection.Connect.Success":

      // initialize the local Capture Devices
      initDevices();

      // initialize the SharedObject
      doInitSO();

      // Call Flash Communication Server to return the Client Object
      this.call("getClientObj", new onFCSData());
      break;
      }
   }

The onResult handler receives the Client object in the object called onFCSData. The handler for the server call to onFCSData performs the following two operations:

  1. Saves the Client object returned to Flash Player into the _global scope as clientObj.
  2. Initializes the outbound stream by calling the initStreams() function. It is important to return the Client object before the streams are initialized because the name of the stream that Flash Communication Server creates is stored in the Client object's streamName property.

Here is the onFCSData function:

function onFCSData() {
   // Handle the call back from the server
   this.onResult = function(clientObj) {
      //1) Copy the returned Client Object into the _global scope
      _global.clientObj = clientObj;
      //2) Initialize the broadcaster streams
      initStreams(clientObj.streamName);
   }
}
The Client object stored in the _global scope of Flash Player. The four
    new properties—streamName, broadcasterName, status, and userID—are created in SSAS
    when the Broadcaster connects.

Figure 2. The Client object stored in the _global scope of Flash Player. The four new properties—streamName, broadcasterName, status, and userID—are created in server-side ActionScript when the Broadcaster connects.

Note: I have not included any connection error handling for this article but you should consider monitoring other information objects from the NetConnection onStatus event specifically for connection failures.

Broadcaster Step 2: Reference and Set Up the Devices

The initDevices() function performs two operations:

  1. References the capture devices (Camera and Microphone).
  2. Sets the initial device properties.

Here is the initDevices() function:

function initDevices() {
   // Reference the devices
   source_cam = Camera.get();

   source_mic = Microphone.get();
   source_mic.setRate(11);
   
   // Set the initial properties of the devices
   //(we'll build these two functions in step 5)
   doSetCamera();
   doSetMicLevel();

   // attach the camera to the video UI Object
   local_video.attachVideo(source_cam);

The NetConnection.onStatus handler calls the initDevices() function once the connection has been established. The capture devices are set up using reusable functions that you will set up in Step 5.

Broadcaster Step 3: Instance and Publish the Stream

The initStreams() function performs three operations:

  1. Creates an instance of the NetStream object on the NetConnection.
  2. Attaches the Camera and Microphone.
  3. Starts publishing a live stream to Flash Communication Server, whose name is sent by the caller as an argument of the function (this stream will be used as a source for the program stream).

This function is called in the onFCSData handler after the server has successfully returned the server-side ActionScript Client object back to Flash Player.

Here is the initStream() function:

function initStreams(streamName) {
   // instance the NetStream object
   out_ns = new NetStream(nc);

   // attach the capture devices
   out_ns.attachAudio(source_mic);
   out_ns.attachVideo(source_cam);

   // start publishing the stream
   out_ns.publish(streamName, "live");
   }

The App Inspector (Streams tab) shows the broadcaster's stream publishing to the server (see Figure 3). A nice extension to this solution would be to make the broadcaster stream publish only to the server when requested by the switcher. This technique would manage the stream more efficiently on the server but would require some additional functionality to be built.

A broadcaster stream shown in the App Inspector

Figure 3. A broadcaster stream (stream_61) shown in the App Inspector

Broadcaster Step 4: SharedObject Communication and Remote Controls

The doInitSO() function connects Flash to the remote shared object called "broadcaster." It also assigns the event handlers used later for synchronization and remote control of the Microphone and Camera:

function doInitSO() {
   broadcaster_so = SharedObject.getRemote("broadcaster",nc.uri,false);
   broadcaster_so.onSync = syncBroadcaster;

   // Custom Event handlers (listeners) to remotely manage the capture devices
   broadcaster_so.onCameraSet = onCameraSet;
   broadcaster_so.onMicSet = onMicSet;

   // Connect the SharedObject to the Server
   broadcaster_so.connect(nc);
   }

The syncBroadcaster() function (below) is assigned as the onSync event handler (above). The function is called each time a change is made to (or by) any broadcaster sending video to the server. This handler monitors only the changes to the slot associated with its current user ID. It has three operations:

  1. Copies the Client object associated by the user ID. Each broadcaster publishing to the server is assigned a slot in the broadcaster shared object. The name of each slot is the user ID. This technique makes it much faster to access the information with much less ActionScript.
  2. Sets the background color to red if the broadcaster is selected to be live. This is a simple move of the playhead in the cameraBG_mc movie clip.
  3. Sets the status message on the interface either to "live" or "ready." These values will be assigned later, when you build the server-side ActionScript for this solution.

Here is the syncBroadcaster() function:

function syncBroadcaster(syncObj) {


   //1) Copy the Client Object
   var mySlot = this.data[_global.clientObj.userID];


   //2) Change the background colour of the camera
   cameraBG_mc.gotoAndStop(mySlot.status);


   //3) Sets a UI Status message informing the user that the camera is live
   statusMsg_txt.text = mySlot.status;
   }

These final two functions, onCameraSet and onMicSet, were assigned earlier in this step as event handlers. They respond to changes made remotely by the switcher to the Camera or Microphone properties. They each receive two arguments: targetUserID and newValue. The targetUserID argument contains the user ID of the broadcaster being changed. The newValue argument contains the numerical position of the slider component. By setting the value, the component automatically changes and the component's change handler is called. You will develop the change handler and the device settings in Step 5.

Both functions are assigned to handlers in the initSO() function. They listen for messages sent over the shared object::

function onCameraSet(targetUserID, newValue) {
   if (targetUserID == clientObj.userID) 
   camQuality_slide.value = newValue;
   }


function onMicSet(targetUserID, newValue) {
   if (targetUserID == clientObj.userID) 
   micLevel_slide.value = newValue;
   }

Broadcaster Step 5: Configure the Camera Quality

The Camera Quality properties are set by the user interface slider component (included in the sample files). The slider allows users easily to change their quality settings. When called, this function has three operations:

  1. Uses the slider component's value property (1 to 3) to determine how to set the Camera width (w), height (h), frame rate (fps), and quality values.
  2. Calculates the key frame interval (kfi) and the bandwidth (bw) required.
  3. Sets all Camera properties using built-in methods.

Here is the doSetCamera() function:

function doSetCamera() {
   var camSet = new Object();
   
   switch(camQuality_slide.value) {
      case 1:
         camSet.w = 80;
         camSet.h = 60;
         camSet.fps = 8;
         camSet.quality = 75;
         break;
      case 2:
         camSet.w = 192;
         camSet.h = 144;
         camSet.fps = 7;
         camSet.quality = 80;
         break;         
      case 3:
         camSet.w = 320;
         camSet.h = 240;
         camSet.fps = 15;
         camSet.quality = 90;
         break;         
      
      }
   // calculated KeyFrame and Bandwidth
   camSet.kfi = camSet.fps * 4;
   camSet.bw = (camSet.w * camSet.h * camSet.fps) / 8;

   // Set the Camera   
   source_cam.setMode(camSet.w,camSet.h,camSet.fps,false);
   source_cam.setQuality(camSet.bw,camSet.quality);
   source_cam.setKeyFrameInterval(camSet.kfi);
   }

Finally, set the event handler for the "change" event of the Camera slider component (camQuality_slide) to the doSetCamera function. The change handler will be called if the user adjusts the slider or if the switcher changes the value remotely (as I mentioned in Step 4):

camQuality_slide.changeHandler = doSetCamera;

To illustrate further what is being set, Figure 4 shows the three settings as they relate to each case.

Bandwidth targets used in this solution: 38 Kbps dial-up (left); 194 Kbps DSL (middle); 1.2 Mbps LAN (right)

Figure 4. Bandwidth targets used in this solution: 38 Kbps dial-up (left); 194 Kbps DSL (middle); 1.2 Mbps LAN (right)

Broadcaster Step 6: Set the Microphone Volume

The Microphone volume is set by the change handler of the volume slider. It feeds the slider component's value property (0 to 100) directly into the Microphone.setGain() method:

function doSetMicLevel() {
   source_mic.setGain(micLevel_slide.value);
   micLevel_txt.text = source_mic.gain;
}

Now assign the doSetMicLevel function to the change event handler for the Microphone volume slider component:

micLevel_slide.changeHandler = doSetMicLevel;

Broadcaster Step 7: Set the Preview Mode (Optional)

The preview mode uses the loopback property of the Camera object to show users how other people see their streams. When set to true the display shows the stream filtered through the encoder. A false setting lets the broadcaster see the raw camera feed:

function previewMode() {
   var isLoopBack = previewMode_cb.selectedItem.data;
   source_cam.setLoopback(isLoopBack);
   }

Assign this function to the change event handler for the ComboBox component:

previewMode_cb.changeHandler = previewMode;

Broadcaster Step 8: Update the Broadcaster Name

Each broadcaster can set its name by filling in a text area. The updateSystem() function sets the broadcaster's name by sending the text property of the textArea component to the server by calling the updateBroadcaster Client method. I discuss the updateBroadcaster function later in the section on server-side ActionScript.

The deltaObject object (which stores changes) is loaded with properties that have changed. When received by the server, it resets the clientObject and the SharedObject slots with the changed information. The implementation below is a good starting point for addressing multiple property changes. However, for this article, I've only included a single property, broadcasterName:

function updateSystem() {
   var deltaObj = new Object();
   deltaObj.broadcasterName = broadcasterName_txt.text;
   nc.call("updateBroadcaster", new onFCSUpdate, deltaObj);
}

This NetConnection call to updateBroadcaster returns the updated Client object back to Flash Player. It is handled by the onFCSUpdate() function, which updates the Client object in the _global scope:

function onFCSUpdate() {
   this.onResult = function(clientObj) {
     _global.clientObj = clientObj;u
   }
}

setName_btn.clickHandler = broadcasterName_txt.onKillFocus = updateSystem;