Accessibility

Building a Live Video Switcher with Flash Communication Server MX

Kevin Towes

Adobe

In this article, you will learn how to build a basic video switcher using Flash MX Professional 2004 and Flash Communication Server MX 1.5. This video switcher enables multiple people to broadcast video and audio to Flash Communication Server, and lets you control what the end user watches. You will learn how to use server-side ActionScript and Flash Communication Server messaging easily to control the source of the stream that your audience watches.

As a bonus, you will even incorporate switching between live and prerecorded video sources—all with nothing more than Flash Player, a camera, and some real personality.

In this article, you will create three Flash movies and one ActionScript communications file:

To help you get started, I created the user interfaces for this article. You can download the basic interface and the completed solution with some bonus material below. The basic interface contains all visual elements with the key ActionScript removed. To follow along with this tutorial, use the files in the fcsBroadcaster_start folder. The completed solution is located within the fcsBroadcaster folder. Both folders should be placed within the flashcom/applications folder on your server.

Requirements

For this article, you will need the most recent versions and updaters of these Macromedia products:

Macromedia Flash MX Professional 2004


Macromedia Flash Communication Server MX (version 1.5 updater 2)

Communication Authoring Components for Flash MX 2004

Source Files

Prerequisite Knowledge

This article makes significant use of ActionScript and server-side ActionScript. You should be comfortable with the ActionScript language. The user interface for this article is already complete, so you don't need to be a designer!

Understanding the Switch Concept

Before you start building the video switching application, let's first take a look at how the switch will happen and how you can deliver the best possible experience to your users.

There are two ways to make a video switch with Flash Communication Server:

There are three key problems with the messaging option:

This article focuses on using server-side ActionScript to produce the best possible experience for receiving live video with absolute synchronization, no rebuffering, and optimal Flash Communication Server performance.

All Flash Player clients will subscribe to a single "program" stream. Server-side ActionScript will perform the switch on the server. Sustaining the playback stream on the Flash Player means it will not need to rebuffer and everyone will see the switch at the proper time in the video. This solution also produces less CPU load on the server.

There are three applications that make up this solution:

I explain each part of the application separately so you can understand how each interface operates and how server-side ActionScript interacts with each client. Let's start with the Broadcaster application.


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;

Building the Live Video Player Application

File: simplePlayer.fla

The Live Video Player application plays a live video stream from Flash Communication Server. The interface (see Figure 5) includes only what is required to display the video and some additional information:

The Live Video Player application

Figure 5. The Live Video Player application

All that you need to play a live video stream from Flash Communication Server is an embedded video object and some ActionScript. The Media Display and Media Controller media components are intended only for prerecorded video (both streaming and progressive download); they will not work with live video solutions.

Player Step 1: Set Up the Connection

Set up the connection between Flash Player and Flash Communication Server. As you did with the Broadcaster application, place this ActionScript on Frame 1 of the Actions layer.

Also similar to the Broadcaster application, the NetConnection.connect method receives a parameter identifying this connection request as PLAYER:

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

Handle the NetConnection.onStatus event using the ncStatus function. Once the connection has been accepted, call the doInitStreams() function to set up the incoming stream and the doInitSO function to start listening to the "public" shared object:

function ncStatus(nsObj) {
   switch(nsObj.code) {
   // If the connection is successful, start the stream
      case "NetConnection.Connect.Success":
         // start the stream
         doInitStreams();

         // initialize the SharedObject (Public)
         doInitSO();
         break;
      }
   }

Player Step 2: Subscribe to and Play the Live Feed

The doInitStreams() function will create an instance of the NetStream class attached to the NetConnection. It is called only after a connection request has been successful. This function performs four operations:

  1. Instances the NetStream class, sets the buffer to two seconds, and attaches the stream to the embedded video object on the Stage.
  2. Attaches the video to the video object and the sound object. Use the sound object here so you can control the volume.
  3. Starts an interval that monitors the buffer and the time properties of the stream object.
  4. Plays (subscribes to) the programStream stream. This starts playing a live stream, even if there is no data being sent. The parameters (–1,–1) inform the object to play a live stream, even if there is no stream available.

Here is the doInitStreams() function:

function doInitStreams() {
   // 1) instance the NetStream Class and set the buffer   
   program_ns = new NetStream(nc);
   program_ns.setBufferTime(2);
   
   // 2) attach the streams to the Objects
   live_video.attachVideo(program_ns);
   live_sound = new Sound();
   live_sound.attachSound(program_ns);
   // set the default volume level
   vol_slide.value=70;
   
   // 3) set up an interval to monitor the buffer / and time
   playTimer_int = setInterval(monPlayback, 250);   
      
   // 4) Subscribe to the stream "programStream"
   program_ns.play("programStream",-1,-1,true);
   }

When a single video player connects to Flash Communication Server, you will see this activity in the Streams panel (Figure 6) of the App Inspector.

The App Inspector showing a single player connected, playing the programStream stream. No broadcasters are sending video at this point.

Figure 6. The App Inspector showing a single player connected, playing the programStream stream. No broadcasters are sending video at this point.

Note: The player is "playing live" the programStream stream. As you will see later, Flash Communication Server is publishing an empty stream, called programStream, using the server-side ActionScript Stream object. This is the key to the system.

Player Step 3: Monitor the Stream Buffer and Time

The monPlayback() function monitors the buffer and time properties of the NetStream object. It is called by an interval created when the stream starts. It calculates the buffer size and writes its value as a percentage to the buffer_txt text field. The stream time is sent to the playTime_txt text field (see Figure 7):

function monPlayback() {
   // Calculate the buffer percentage
   currentBuffer = (program_ns.bufferLength/program_ns.bufferTime)*100;

   if (currentBuffer>100) currentBuffer = 100;

   // Write the values to the Interface
   buffer_txt.text = "BUFFER: " + Math.round(currentBuffer)+"%";
   playTime_txt.text = program_ns.time;
}
Examples of dynamic text fields in the video player

Figure 7. Examples of dynamic text fields in the video player

Player Step 4: Monitor the Shared Object

A single shared object is used to monitor information about the current live stream. The player only monitors broadcasterName but you can use this to build additional monitoring and communication for the players:

function doInitSO() {
   public_so = SharedObject.getRemote("public",nc.uri,false);
   public_so.onSync = syncPublic;
   public_so.connect(nc);
   }

function syncPublic(syncObj) {
   broadcasterName_txt.text = this.data.currentStream.broadcasterName;
   }

Player Step 5: Set the Volume

The volume slider controls the gain property of the sound object. The live_sound object manages the stream audio. This onVolChange() function is assigned to the change handler for the vol_slide slider component:

function onVolChange() {
   live_sound.setVolume(vol_slide.value);
   volLevel_txt.text = live_sound.getVolume();
   }

vol_slide.changeHandler = onVolChange;

That's it for the Live Video Player application. Now let's build the Video Switcher application.

Building the Video Switcher

File: simpleSwitcher.fla

The Video Switcher application is the key to this whole solution (see Figure 8). Its interface operates similarly to a television control room (except with a better-looking interface and less radiation).

The Video Switcher application interface

Figure 8. The Video Switcher application interface

The principal interface for the Video Switcher application consists of a control panel and two video panels:

When you click the Cut button, the "switch" happens: The live window becomes the preview and the preview becomes the live. This technique makes it easier to switch between the two sources. The director can then select another live or prerecorded source to preview and then cut to the live stream.

In the case of prerecorded video, the preview window can be used to preview a prerecorded (FLV) video before sending it to the live stream.

Let's examine the key ActionScript used in this application. As with the other two solutions, the ActionScript is located in Frame 1 of the Actions layer.

Switcher Step 1: Set Up the Connection

As you did with the two other applications, identify the connection as SWITCHER in the connect method after you set up the connection:

nc = new NetConnection();
nc.onStatus= ncStatus;
nc.connect(connString, "SWITCHER");

The success handler for the switcher performs two operations:

  1. Initializes two streams, one to monitor the live programStream and one to monitor a preview stream.
  2. Initializes the shared object where the broadcaster list is stored by Flash Communication Server.

Here is the ncStatus() function:

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

      // 1) initialize 2 Streams
      doInitStreams();

      // 2) Initialize the SharedObject
      doInitSO();
      break;
      }
   }

Switcher Step 2: Initialize the Streams

The doInitStreams() function sets up two incoming streams:

The function performs five operations:

  1. Instance the Netstream objects on the NetConnection.
  2. Sets the incoming buffer time to two seconds each.
  3. Attaches the streams to their corresponding video objects in the user interface.
  4. Subscribes to (plays) the program stream and a null (empty) stream for the preview stream. The null play could be omitted but we'll leave it in to make it easier to follow.
  5. Monitors the streams with the monStreams() function.

Here is the doInitStreams() function:

function doInitStreams() {
   // 1) Instance the NetStream Objects
   program_ns = new NetStream(nc);
   preview_ns = new NetStream(nc);
   
   // 2) Set the incoming buffer (2 seconds)
   program_ns.setBufferTime(2);
   preview_ns.setBufferTime(2);   
      
   // 3) attach the Video to the embedded video objects
   prog_video.attachVideo(program_ns);
   prev_video.attachVideo(preview_ns);
   
   // 4) play the program stream and a null stream
   program_ns.play("programStream",-1,-1,true);
   preview_ns.play("null",-1,-1,true);
   
   // 5) startup the streams monitor (interval 250ms)
   setInterval(monStreams,250);
   
   }

Switcher Step 3: Connect the Shared Objects

All SharedObjects are managed by server-side ActionScript. There are two shared objects used for the application:

Here is the doInitSO() function:

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

   public_so = SharedObject.getRemote("public",nc.uri,false);
   public_so.onSync = syncPublic;
   public_so.connect(nc);
   }

The synchronization handlers for each shared object are built in Step 4.

Switcher Step 4: Handle onSync Events

The SharedObject handlers monitor changes to the shared objects. In this example, we're only using it as a trigger when the shared object changes. Normally, you would loop through the information object and handle each change.

Flash Communication Server is the only part of the application that writes to either of the shared objects, so we can keep the onSync handlers simple.

The first handler (syncBroadcaster) is responsible for populating the DataGrid component that contains a list of all broadcast clients currently streaming to the server. It performs three operations:

  1. References the SharedObject slots (in the data property) into a temporary soData variable.
  2. Loops through the slots and generates a new array of objects containing specific data to be displayed in the DataGrid component.
  3. Populates the DataGrid component by setting the dataProvider property equal to the currentPublishers array.

Here is the syncBroadcaster() function:

function syncBroadcaster(syncObj) {
   // initialize temporary variables (syncObj is not used)
   var soData = broadcaster_so.data;
   var gridObj;
   var currentPublishers = new Array();

   // Loop through the soData and copy only the columns to display in the DG
   for (i in soData) {
      gridObj = new Object();
      gridObj.userID = soData[i].userID;
      gridObj.streamName = soData[i].streamName;
      gridObj.broadcasterName = soData[i].broadcasterName;   

      // push the new object to the array used for the Datagrid
      currentPublishers.push(gridObj);
}   
   // set the datagrid's dataprovider to the array of objects
   broadcasterList_dg.dataProvider = currentPublishers;
   }

The second handler (syncPublic) monitors the public shared object and updates the name of the broadcaster in the user interface:

function syncPublic(syncObj) {
   // display the broadcaster name for the live (program) stream
   prog_BroadcasterName_txt.text = this.data.currentStream.broadcasterName;
   }

Switcher Step 5: Load a Preview Stream

This function subscribes the NetStream object preview_ns to the selected stream in the DataGrid component. First it retrieves the selectedItem property of the data grid and stores it in the _global.stream_prev variable. Next it sets the stream property to the selected stream name. Finally, using the NetStream.play() method, it subscribes to the stream. Note the absence of the start and duration parameters in the play() method. This allows you ultimately to use this application to stream both live and prerecorded FLV files using the same system!

Here is the doLoadPreview() function:

function doLoadPreview() {
   _global.stream.preview = broadcasterList_dg.selectedItem;
   prev_BroadcasterName_txt.text = stream.preview.broadcasterName;
   preview_ns.play(stream.preview.streamName);
   }

Finally, assign this function to the data grid's change event handler:

broadcasterList_dg.changeHandler = doLoadPreview;

Switcher Step 6: Switch the Preview and Live Streams

This last function switches the preview and play streams. It has three operations:

  1. Saves the stream objects. It is important to save the old live stream because that will be used to play the new stream.
  2. Plays the stream in the preview window that was live.
  3. Calls the server-side ActionScript Client method switchStream, sending it only the user ID of the stream to switch. The server will use the user ID to identify which stream to switch to.

Here is the doSwitchVideo() function:

function doSwitchVideo() {
   // Save the program and preview stream data into the    _global scope
   _global.stream.program = _global.stream.preview;
   _global.stream.preview = public_so.data.currentStream;

   // play the current live stream in the preview window
   preview_ns.play(stream.preview.streamName);
   prev_BroadcasterName_txt.text = stream.preview.broadcasterName;

   // send the User ID to the server for switching
   nc.call("switchStream",null,stream.program.userID);
   }

Switcher Step 7: Remote Device Control and Mute

These next three functions let the switcher remotely control the Camera and Microphone properties of the broadcaster clients. The first two functions are assigned to the slider components for both the program and preview streams. Each function determines which component called it and sets the targetUserID parameter.

Using the broadcaster shared object, they send a message with the user ID and the new value to set. You developed the onCameraSet() and onMicSet() handlers earlier in this article:

function setCamera() {
   var targetUserID = stream.preview.userID;
   if (this._name == "prog_camQuality_slide") targetUserID =    stream.program.userID;
   broadcaster_so.send("onCameraSet",targetUserID, this.value)
   }

prev_camQuality_slide.changeHandler = setCamera;
prog_camQuality_slide.changeHandler = setCamera;

function setMicrophone() {
   var targetUserID = stream.preview.userID;
   if (this._name == "prog_micLevel_slide") targetUserID =    stream.program.userID;
   broadcaster_so.send("onMicSet",targetUserID, this.value);
   }

prog_micLevel_slide.changeHandler = setMicrophone;
prev_micLevel_slide.changeHandler = setMicrophone;

The onMute() function is used by both mute buttons on the interface to enable/disable the audio stream. Although this method works fine, you might consider using a sound object and the setGain() method as you saw earlier when you built the Live Video Player application:

function onMute() {
   var targetStream = "program_ns";
   if (this._name == "mutePrev_ch") targetStream = "preview_ns";
   this._parent[targetStream].receiveAudio(this.value);
   }

mutePrev_ch.clickHandler = onMute;
muteProg_ch.clickHandler = onMute;

That's it for all three Flash interfaces. Now, let's build the final piece, the server-side ActionScript.

Building the Server-Side ActionScript

File: fcsBroadcast.asc

The server-side ActionScript stitches everything together. It is the key to stream management and managing the connections. We use three key events on the server to manage users connecting and disconnecting:

We also extend the Client prototype with three additional methods:

Two additional functions are used by these new client methods and the application event handlers. I cover them in the following steps.

Server-Side ActionScript Step 1: Initializing with onAppStart()

This first event handler is called the first time the application is instanced (usually at the first connection request). It performs three key functions:

  1. Sets initial data values.
  2. Connects to the broadcaster and public shared objects. The broadcaster shared object stores the client objects for all Broadcaster clients and the public shared object stores the client object for the current live broadcaster.
  3. References the program streamand publishes a null stream to it.

Here is the onAppStart function:

application.onAppStart = function() {

   // set initial Values
   application.currentStreamID = undefined;
   userID = 0;

   // setup the BROADCAST SharedObject
   broadcaster_so = SharedObject.get("broadcaster",false);

   // setup the PUBLIC SharedObject   
   public_so = SharedObject.get("public",false);
   public_so.setProperty("currentStream", ({broadcaster: "No Broadcaster"}));

   // start the program stream all viewers will play
   prog_stream = Stream.get("programStream");
   prog_stream.play("null");
   }

When you play a stream from Flash Communication Server, you are in fact publishing a stream to the server (see Figure 9). The stream name set as the first parameter is how you ultimately connect to other streams being published to the server.

The App Inspector showing FCS publishing the programStream stream with no players nor broadcasters connected

Figure 9. The App Inspector showing Flash Communication Server publishing the programStream stream with no players or broadcasters connected.

Server-Side ActionScript Step 2: Accepting Connection Requests with onConnect()

This second event handler is called each time an instance of Flash Player requests a connection to the server. This is where any authentication or user counter functions would go. For this article, the onConnect function will just do the basics:

  1. Increment the userID counter. Note that this will not record the number of users but will provide a unique user ID for each client connecting to the server.
  2. Initialize only Broadcast clients.
  3. Accept all connection requests from Broadcast, Player, and Switcher clients.

The Client object is modified only for broadcasters. In the next step, you'll see the definition for the initBroadcasters() function.

Here is the onConnect() function:

application.onConnect = function(clientObj, userType) {
   // 1) increment the userID counter 
   userID++;

   // 2) Setup the Broadcaster (if userType is correct)
   if(userType == "BROADCASTER") 
      clientObj = initBroadcaster(clientObj);
   
   // 3) Accept All Connections
   application.acceptConnection(clientObj);
   }

Server-Side ActionScript Step 3: Modify the Broadcaster's Client Object

This function sets three additional properties inside the Client object only for BROADCASTER connections. It performs three tasks:

  1. Sets the streamName property based on the user ID, sets the status of the stream to "READY," and sets the initial broadcasterName value.
  2. Adds the Client object to the broadcasters shared object. The slot will be the user ID of the connection, which makes it easier to handle later in the application.
  3. Returns the updated Client object to the caller.

Here is the initBroadcasters() function:

initBroadcaster = function(clientObj) {
   // Set new properties in the Client Object
   clientObj.streamName = "stream_" + userID;
   clientObj.broadcasterName = "Broadcaster #" + userID;
   clientObj.userID = userID;
   clientObj.status = "READY"
   
   // Add the client to the broadcasters SharedObject  
   broadcaster_so.setProperty(userID, clientObj);

   // Return the Client Object
   return clientObj;
   }

Server-Side ActionScript Step 4: The Switch Stream Function

The switchStream function sets the programStream source to the required stream (see Figure 10). This function is called by the switcher application and requires the userID argument to be passed. It uses the user ID to retrieve the streamName value and the Client object from the broadcasters shared object.

The application.currentStreamID variable stores the user ID of the broadcaster currently set to the program feed. There are two occasions when application.currentStreamID is undefined: if there is no previous live stream or if the current broadcaster suddenly disconnects. The if statement within this method skips the resetting routines in case there is no current live broadcaster:

Client.prototype.switchStream = function(userID)  {

   var oldVal = new Object();
   var currentStream;

   // get the PREVIOUS Stream Object
   if (application.currentStreamID != undefined){
      oldVal = broadcaster_so.getProperty(application.currentStreamID);
      oldVal.status = "READY";
      broadcaster_so.setProperty(oldVal.userID, oldVal);
}

   // get the NEW Stream Object
   currentStream = broadcaster_so.getProperty(userID);
   currentStream.status = "LIVE";

   // update the slots
   broadcaster_so.setProperty(userID, currentStream);   
   public_so.setProperty("currentStream", currentStream);

   // set the currentStreamID to the new Stream ID
   application.currentStreamID   = userID;   

   // connect the new stream with the programStream
   prog_stream.play(currentStream.streamName);
   }
The App Inspector showing a situation with two broadcasters publishing, one player, one switcher with one live stream, and one preview stream

Figure 10. The App Inspector showing two broadcasters publishing, one player, one switcher with one live stream, and one preview stream.

Server-Side ActionScript Step 5: Return the Client Object to Flash Player

This function returns the Client object to the Broadcaster application. The Client object contains the stream name and user ID, which are required by the broadcaster to start publishing a video to the server:

Client.prototype.getClientObj = function() {
   return this;
   }

Server-Side ActionScript Step 6: Update the Broadcaster Slot

As I mentioned earlier, we made the decision to let only server-side ActionScript perform any changes to shared objects. This Client method is called by a broadcaster client when a change is made to the information. It requires the deltaObj parameter containing Client properties that have changed. This method has four operations:

  1. Updates the server-side ActionScript Client object (this) by looping through the deltaObj parameter.
  2. Updates the broadcaster shared object slot that represents this client.
  3. Updates the public shared object if the broadcaster client is currently set as the current program feed (this affects all players viewing the live stream).
  4. Returns the new Client object (this) to Flash Player.

Here is the Client method:

Client.prototype.updateBroadcaster = function(deltaObj) {
   // 1) set the changed properties in the clientObject 
   for (i in deltaObj) {
      this[i] = deltaObj[i];
   }

   // 2)_update the Broadcaster SharedObject
   broadcaster_so.setProperty(this.userID, this);

   // 3) update the Public SharedObject
   if (application.currentStreamID == this.userID) {
      public_so.setProperty("currentStream", this);
   }

// 4) return the updated Client Object back to the Flash player
return this;
}

Server-Side ActionScript Step 6: Cleaning Up the Broadcasters with onDisconnect()

Typically when any user disconnects, you should do garbage collection (cleaning up variables). This is where strategically using the Client object comes in real handy. Each time a Flash client disconnects from Flash Communication Server, the onDisconnect event handler is called. You can use the Client object (automatically passed as a parameter) to get the user ID (the Slot name) in the shared object and delete it. It's important to flush the shared object after you have deleted a slot to destroy it completely.

This handler also resets application.currentStreamID, should the disconnected user be the one currently broadcasting on the live program stream:

application.onDisconnect = function(clientObj) {
   broadcaster_so.setProperty(clientObj.userID, undefined);
   broadcaster_so.flush();

   if(application.currentStreamID == clientObj.userID) {
      application.currentStreamID = undefined;
   public_so.setProperty("currentStream", ({broadcasterName:    "Please Stand By..."}));
   }
}

That's it for the server-side ActionScript and fcsBroadcast.asc file. At this point you have everything you need to build your first Internet television station using Flash. Inside the sample code, I've added some functionality not mentioned in this article that will help you complete this solution.

Using Flash Video Streaming Service

As an alternative to building your own live video encoding solution with Flash Communication Server would be to use Flash Video Streaming Service, a hosted service for streaming on-demand and live Flash Video from a reliable content delivery network partner.

VitalStream provides a live streaming service and a live encoder application as a part of its implementation of Flash Video Streaming Service (see Figure 11). You can use it to stream video to as many people as you want.

Flash Video Streaming Service powered by VitalStream

Figure 11. Flash Video Streaming Service powered by VitalStream

VitalStream provides you with the encoder and live player. Their HTML embed code makes it simple to get your live streaming project up and going quickly. For more information, read Tim Napoleon's article, Delivering High-Quality Video with Flash Video Streaming Service.

Read more about Flash Video Streaming Service ›

Where to Go from Here

As more and more people push Flash Video to the next level, solutions like the one I've presented in this article function as building blocks for creating powerful live video solutions. Audiences just need Flash Player 6, broadcasters just need a little onscreen personality, and you've got a winning project. For additional features and functionality, refer to the extra Actions layer in the completed sample files you downloaded at the beginning of this article.

If you are interested in more live video switching solutions, check out my session Streaming Live Video with Flash at Macromedia MAX 2004 conference in New Orleans.

Good luck with your live video project, Mr. Director!

About the author

Kevin Towes is the product manager for Flash Media Server at Adobe Systems and is responsible for defining, delivering, and supporting Adobe streaming video products and services. Before joining Adobe, Kevin spent 13 years working to enable customers with Flash based interactive video streaming solutions using Flash Media Server. His Flash Media Server Live Video work with the Canadian Broadcasting Corporation (CBC) led to an Emmy nomination in 2004.