Accessibility

Table of Contents

Creating a video sharing web application using Flex, Flash Media Server, and Flash Media Encoding Server

Setting up video encoding

For this part you will use the Flash Media Encoding Server, which operates as the encoding engine on the server side. There are multiple ways to add jobs to the encoding queue including simple file-based watch folders, a watch folder for XML-based jobs, a command line interface, and a socket connection for advanced communication and interaction. For this application, you will use the socket connection.

Important note: To establish a connection between a Flex application and a socket server, even on the same domain, you'll need to run a policy server on port 843 to return a socket policy file on request. It sounds complicated, but there are existing scripts that support an easy setup. For more information on this required step, see Setting up a socket policy file server.

Flash Media Encoding Server

You can use Flash Media Encoding Server to set up your own preset. To set up a preset that fits your needs, choose Tools > Preset Editor and create a preset (see Figure 3) with 800 kbps H.264 encode and 720 × 480 resolution (480p).

List of encoding profiles

Figure 3. List of encoding profiles

To identify the preset when submitting the job through the socket connection, find the GUID in the preset editor. The GUID will be a string of hexadecimal values, for example: "{85DCCE70-0B4F-4328-86D1-5FC7756D9777}".

Set up the socket connection

The socket connection logic requires additional code on the Flex side to ensure appropriate visual feedback, which includes a progress bar to display the progress of the encode and updated status messages. You may want to open the sample source code to follow the next steps.

Flash Media Encoding Server requires a valid XML command, in this case sent through a socket connection, to add a job to the rendering queue. For better maintainability, I have externalized the commands in the submit.xml and checkstatus.xml files with placeholders for the file to encode. Please also make sure you replace the PresetGUID value in the XML below with the GUI of your encoding profile.

submit.xml

<cnpsXML TaskType="JobQueue">
<Sources>
   <Module_0 Filename="c:\videoshare\uploaded\:::filename:::"/>        
</Sources>
<Destinations><Module_0
  PresetGUID="{85DCCE70-0B4F-4328-86D1-5FC7756D9777}"
  ModuleGUID="{85DCCE70-0B4F-4328-86D1-5FC7756D9777}"
  AssignedTags=""><ModuleData CML_P_Path="C:\Program
  Files\Adobe\Flash Media Server
  3.5\applications\videoshare\streams\_definst_" CML_P_BaseFileName="%s"/><PostconversionTasks/><Filter_1/><Filter_0/></Module_0></Destinations>
            
</cnpsXML>

The submit command encodes the file and then moves to it the Flash Media Server application folder to allow the playback of the file. The filename is a unique placeholder. The Flex code will replace this value with the filename of the current job.

checkstatus.xml

<cnpsXML TaskType="JobStatusList">
   <Filter>
      <Criteria_0 Parameter ="Guid" Operator="EQUAL" Value=":::guid:::" />
   </Filter>
</cnpsXML>

The checkstatus XML triggers the JobStatusList command. This Flash Media Encoding Server command normally returns all jobs. In this case you want to limit it to the active job initiated by the active user; therefore it contains limitation criteria with the <filter> tag.

The following code establishes the socket connection and sends commands through it. It first loads the XML submission files via loadFMESAPIConfig() (for details on this method, see the sample source code), establishes a connection to Flash Media Server (I'll cover this in more detail below), and then, once the user has successfully uploaded a video file, triggers the encoding process.

To better understand the flow of the application, here are the steps that follow a successful file upload:

  1. Establish socket connection.
  2. Once connection successful, replace filename in submit.xml and submit command to Flash Media Encoding Server.
  3. Parse the XML response returned from the socket and extract the active job GUID.
  4. Use the GUID of the active job (not the preset) to query the status of the encoding process with the XML from checkstatus.xml.
  5. Parse the XML response from the socket and extract the current status (either not started yet, in process, or finished) and update the progress bar and status text accordingly.
  6. Once the encoding is complete, close the socket connection and retrieve the latest thumbnail list from Flash Media Server (more details below).

videoshare.mxml

// Initializes the interface and connects to the Flash Media Server
private function init() : void {
 
    nc = new NetConnection();
    fmsserver = "myserver.com";
    phpserver = "myserver.com";
    fmesserver = "myserver.com";;
    fmesport = 1547;
    nc.connect("rtmp://"+fmsserver+"/videoshare/");
    nc.addEventListener(NetStatusEvent.NET_STATUS,onConnect);
    playbackstatus = "stop";
 
    // Loads the FMES XML command files
    loadFMESAPIConfig();
    progressbar.mode = "manual";
    progressbar.label = "";
 
    // Event listener for the fullscreen mode
    Application.application.stage.addEventListener(FullScreenEvent.FULL_SCREEN, fullScreenHandler); 
}
 
// Once the upload is complete, initialize the video conversion
    private function completeHandler(event:Event):void {
    status.text = "Upload complete";
    fmessocket = new Socket();
    configureListeners(fmessocket);
    sendFMESCommand("submitjob");
    }
 
    // Establishes a new connection and connects to FMES
    private function sendFMESCommand(command:String) : void {
       activecommand = command;
       if (fmessocket.connected) fmessocket.close();
       if (fmesserver && fmesport) {
              fmessocket.connect(fmesserver, fmesport);
          }
    }
 
    // Configures all listeners for the socket connection primarily for debug reasons
       private function configureListeners(dispatcher:IEventDispatcher):void {
           dispatcher.addEventListener( ProgressEvent.SOCKET_DATA, dataHandler );
           dispatcher.addEventListener( IOErrorEvent.IO_ERROR, ioErrorHandler );
           dispatcher.addEventListener( Event.CONNECT, connectHandler );
           dispatcher.addEventListener( Event.CLOSE, closeHandler );
           dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler );
       }
 
    // Triggered once connection closes
       private function closeHandler(event:Event):void {
           debug("closeHandler: " + event);
        } 
    // Triggered once connection is established. This event should happen at the beginning of the encode        
    // Replaces the filename property of the XML command template with the uploaded file name value
       private function connectHandler(event:Event):void {
           if (activecommand == "submitjob") {
               var replaceFileNameExp:RegExp = /:::filename:::/;
               submitFMESCommand(submitapixml.replace(replaceFileNameExp,uploadedFile));
           }
       }
 
// Submits a FMES command to the server through the established socket connect
private function submitFMESCommand(commandxml:String) : void {
       debug(commandxml);
       var msg:String = commandxml;
       var bytes:ByteArray = new ByteArray();
       bytes.writeMultiByte( msg, "iso-8859-1" );
       var messageBytes:ByteArray = new ByteArray();
       var messageString:String = "CarbonAPIXML1 " + bytes.length.toString() + " " + msg;
       messageBytes.writeMultiByte( messageString, "iso-8859-1" );
       fmessocket.writeBytes( messageBytes );
       fmessocket.flush();
    }
 
// Handles the response from FMES
private function dataHandler(event:ProgressEvent):void {
    var bytesAvailable:int = fmessocket.bytesAvailable;
    var type:String = fmessocket.readMultiByte( 13, "utf-8" );
    fmessocket.readMultiByte( 1, "utf-8" );
    var ttlBytes:int = int( fmessocket.readMultiByte( 2, "utf-8" ) );
    fmessocket.readMultiByte( 1, "utf-8" );
    var str:String = fmessocket.readUTFBytes( fmessocket.bytesAvailable );
    // Parses and assigns the response to a XML variable
    fmesresponse = new XML( str );
    // Data can be received following the start job or the monitoring command
       switch (activecommand) {
       case "submitjob" :
    
    // Reads and stores the active job id
         activejobguid = (fmesresponse.attributes()[0].toString());
         var replaceGuidExp:RegExp = /:::guid:::/;
         checkstatusxml = checkstatusapixml.replace(replaceGuidExp,activejobguid);
         activecommand = "checkjobstatus";
    // Checks for the job status after 2 seconds
         setTimeout(checkEncodingStatus,2000);
         break;
       case "checkjobstatus" :
    // Reads the received status variable from the XML response
         var currentstatus:String = fmesresponse.children()[0].children()[0].attribute("Status");
    // Reads the received progress from the XML response
         var encodingprogress:String = fmesresponse.children()[0].children()[0].attribute("Progress.DWD");
    // Updates the progress bar component
         progressbar.setProgress(Number(encodingprogress),100);
    // Updates the label for the progress bar depending on the current status
         if (currentstatus!="STARTED" && currentstatus!="COMPLETED") {
         progressbar.label = "Waiting in encoding queue";
         }
   
          if (encodingprogress=="0") {
             progressbar.label = "Preparing encoding..";
          }
   
          else {
             progressbar.label = "Encoding .. " +encodingprogress+ "% completed";
          }
 
   // Once encoding is complete, updates the UI and closes the socket connection to FMES
   
         if (currentstatus=="COMPLETED") {
            fmessocket.close();
            progressbar.label = "Encoding complete";
            refreshVideos();
            browse_btn.enabled = true;
            upload_btn.enabled = false;
       }
 
// If encoding is not complete, check again in 2 secs what the current status is
    else {
        setTimeout(checkEncodingStatus,2000);
}
    break;      
}
       
}
 
// Triggers the FMES command
       private function checkEncodingStatus() : void {
       submitFMESCommand(checkstatusxml);      
}
 
// Error handler
      private function ioErrorHandler(event:IOErrorEvent):void {
      debug("ioErrorHandler: " + event);
      }
 
// Security handler
     private function securityErrorHandler(event:SecurityErrorEvent):void {
          debug("securityErrorHandler: " + event);          
          for (var foo:String in event) {      
             debug(foo + " " + event[foo]);
          }
     }

I won't explain the source line by line, since the concept is relatively simple and with comments the code should be fairly self-explanatory.

I have defined socket connection event handlers for I/O and security issues. If, for example, a security error occurs, the security handler will be triggered. This can happen when the security policy server on port 843 is not properly set up.

Since it employs the same socket connection for both commands with a single data handler, the application uses an activecommand state to differentiate between submitting the encoding call and querying the current status of the encoding process.