Section 12.3. Getting a List of Streams

12.3. Getting a List of Streams

ColdFusion can be used to do more than just create log files. One particularly useful feature that FlashCom does not have is the ability to return the contents of a directorysuch as the name of the streams contained within an application. If ColdFusion is running on the same computer as FlashCom, it is quite simple to use the < cfdirectory > tag to retrieve a list of .flv files within a directory. Being able to search through the streams folder of an application is an important feature of administrative applications that must monitor disk utilization, clear out older streams, or provide alerts. It is a simpler alternative to maintaining stream names in a database.

You could design your application so that each client Flash movie makes separate Flash Remoting calls to request a list of stream names. However, this approach could generate many Flash Remoting calls, especially if the Flash clients continually poll ColdFusion for updated lists. If each of 100 clients each polls once per minute, 100 Flash Remoting requests are sent to the server per minute. A much more efficient approach is to make the Flash Remoting calls from the server. Then, even if 100 clients are polling once per minute, only one Flash Remoting call is made per minute. Using a remote shared object (RSO), the server can update the clients as needed. And, if done appropriately, the only data that the RSO will need to send to the clients is the data that has changed. Therefore, if the directory listing doesn't change during 10 minutes, no directory data is sent to the clients during that time. For each new stream added, one piece of data, rather than the entire directory listing, is sent to each of the clients. The following example builds an application that uses Flash Remoting calls from FlashCom to retrieve a listing of streams within the application. It then uses an RSO to update any connected clients. The example has four parts :

  • The streams

  • The CFC

  • The FlashCom application

  • The Flash client

12.3.1. Setting Up the Streams

The first step in the example is to set up the streams. You should create a new FlashCom directory for the application named fcs_streams . Then, within that directory, create a subdirectory named streams , and within the streams directory, create a subdirectory named _definst_ . The streams/_definst_ directory is the default location for streams within a FlashCom application. You can also add subdirectories within that directory. In the example, we'll add subdirectories to illustrate how ColdFusion can recurse through the subdirectories to retrieve the streams. So within the streams/_definst_ directory, create another subdirectory named archived .

Once you've set up the directory structure, the next step is to add the streams. If you don't already have some .flv files you can move to the directory, you can download an archive of sample streams from the book's web site (http://www.flash-communications.net). Copy two streams ( .flv files) to the streams/_definst_ directory and two streams to the streams/_definst_/archived directory.

12.3.2. Creating the CFC

Once you've set up the application and placed the .flv files in the appropriate directories, the next step is to create the CFC that can read the contents of those directories. You should create a new directory in the web root and name it fcs_streams . Then, within that directory, create a new file named DirectoryUtilities.cfc , and add the following code to it:

 <cfcomponent>   <!---   # Define   getFiles(  )   with remote access. Declare two parameters specifying   # the subdirectory and main path. For example, the subdirectory may be \ and the    # main path may be   C:\Program Files\Macromedia\Flash Communication Server   #   MX\applications\fcs_streams\streams\_definst_   .   --->   <cffunction name="getFiles" access="remote">     <cfargument name="sub_path" />     <cfargument name="main_path" />     <!--- Define two local variables; they must be local variables because the           method calls itself recursively and, otherwise, incorrect values result.     --->     <cfset var aFiles = "" />     <cfset var stData = "" />     <!--- Retrieve the directory listing for the specified directory. --->     <cfdirectory action="list" directory="#main_path##sub_path#" name="qFiles" />     <!--- Create a new array. --->     <cfset aFiles = ArrayNew(1) />     <!--- Loop through each of the items in the directory listing. --->     <cfloop query="qFiles">       <!--- If the type of element is a directory, call   getFiles( )   recursively.              Otherwise, just add the file to the array.        --->       <cfif qFiles.type EQ "Dir">         <!--- Determine the correct path to send to the   getFiles( )   method. --->         <cfif sub_path EQ "\">           <cfset new_path = "\" & qFiles.name />         <cfelse>           <cfset new_path = sub_path & "\" & qFiles.name />         </cfif>         <cfset directory_files = getFiles(new_path, main_path) />         <!--- Add each of the files from the return array to the   aFiles   array. --->         <cfloop from="1" to="#ArrayLen(directory_files)#" index="i">           <cfset ArrayAppend(aFiles, directory_files[i]) />         </cfloop>       <cfelse>         <!--- If the element is a file, create a new structure, populate it               with some information about the file, and add it to the array.         --->         <cfset stData = StructNew( ) />         <cfset stData.name = qFiles.name />         <cfset stData.path = sub_path />         <cfset stData.main_path = main_path />         <cfset ArrayAppend(aFiles, stData) />       </cfif>     </cfloop>     <!--- Return the array. --->     <cfreturn aFiles />   </cffunction> </cfcomponent> 

The CFC code isn't very big, but it does use recursion, which can appear somewhat confusing at first. So some further explanation will help clarify the code.

Recursive methods are methods that call themselves . The getFiles( ) method is recursive so that it can retrieve the files from subdirectories as well. Therefore, the method takes two parameters: the main directory path and the subdirectory path:

 <cffunction name="getFiles" access="remote">   <cfargument name="sub_path" />   <cfargument name="main_path" /> 

The main directory path remains the same with each recursive call to the method, but the subdirectory path changes to indicate from which subdirectory the method should retrieve a listing of contents.

Next, you want to make sure you define two variables as local variables. Using the var keyword in CFML, as in ActionScript, scopes the variable to the method or function:

 <cfset var aFiles = "" /> <cfset var stData = "" /> 

In the case of aFiles and stData , the variables must be local; otherwise, each time the method is called recursively, the previous values would be overwritten and the return value would be incorrect. Making the variables local ensures they are not overwritten by recursive calls.

The <cfdirectory> tag allows ColdFusion to work with directories on the server running ColdFusion. When the action attribute is set to "list", the tag retrieves a list of the contents of the specified directory as a query object and assigns it to the variable specified by the name attribute. Therefore, the following code creates a new query object named qFiles with the contents of the directory:

 <cfdirectory action="list" directory="#main_path##sub_path#" name="qFiles" /> 

In order to store and return information about each stream, create an array and assign it to the aFiles local variable:

 <cfset aFiles = ArrayNew(1) /> 

The next step is to loop through each of the records in the qFiles object:

 <cfloop query="qFiles"> 

Every record in a query object returned by <cfdirectory> contains several fields, including type and name . The type can be either "Dir" or "File". If the type is "Dir", the element is a directory. In that case, the code calls getFiles( ) recursively using the new subdirectory in order to retrieve that subdirectory's listing:

 <cfif qFiles.type EQ "Dir">   <cfif sub_path EQ "\">     <cfset new_path = "\" & qFiles.name />   <cfelse>     <cfset new_path = sub_path & "\" & qFiles.name />   </cfif>   <cfset directory_files = getFiles(new_path, main_path) /> 

Once you've retrieved the subdirectory's listing of files, you can add each element to the aFiles array:

 <cfloop from="1" to="#ArrayLen(directory_files)#" index="i">     <cfset ArrayAppend(aFiles, directory_files[i]) />   </cfloop> 

If the element is a file, the code creates a new structure and stores the name of the file, the subdirectory path, and the main path to the file. It then appends that structure to the aFiles array:

 <cfelse>   <cfset stData = StructNew(  ) />   <cfset stData.name = qFiles.name />   <cfset stData.path = sub_path />   <cfset stData.main_path = main_path />   <cfset ArrayAppend(aFiles, stData) /> </cfif> 

12.3.3. Writing the FlashCom Application

The next step is to create the FlashCom application, which is responsible for polling ColdFusion to get updated listings of streams and then updating connected clients using a remote shared object. To create the FlashCom application, create a new file named main.asc or fcs_streams.asc in the fcs_streams FlashCom directory. Then add the following code to the file. Look over the code briefly , and we'll discuss it momentarily. Note that each slot in the RSO uses a unique key composed of the subdirectory path and the name of the stream file:

 load("NetServices.asc"); application.onAppStart = function (  ) (   NetServices.setDefaultGatewayUrl("http://localhost/flashservices/gateway");   var ncFlashRemoting = NetServices.createGatewayConnection( );   this.frservice = ncFlashRemoting.getService("fcs_streams.DirectoryUtilities",                                               this);   // Create a NetConnection to connect to the Admin Service application.   this.ncAdmin = new NetConnection( );   // Once the Admin Service connection has been established, call   getConfig( )   // within that app to retrieve the physical path to the current application.   this.ncAdmin.onStatus = function (info) {     if (info.code == "NetConnection.Connect.Success") {       var key = "Adaptor:_defaultRoot_/VirtualHost:_defaultVhost_/AppsDir";       this.call("getConfig", application, key, "/");     }   };   // Connect to the admin application using the correct username and password.   // Replace the username and password with those you've set in your Admin Service.   this.ncAdmin.connect("rtmp://localhost:1111/admin",                         "adminuser", "adminpassword");   // Create a remote shared object to store the streams information.   this.files_so = SharedObject.get("files"); }; // The   onResult( )   method is called once the Admin Service returns a response  // from the   getConfig( )   call. application.onResult = function (value) {   // Call the   getFiles( )   CFC method to get the streams within the current    // application. Use an interval to poll every 10 seconds.   var application_path = application.name.split("/");   var path = value.data + "\" + application_path[0] + "\streams\" +              application_path[1];   this.file_interval = setInterval(this.frservice, "getFiles", 10000, "\", path);   this.frservice.getFiles("\", path); }; application.getFiles_Status = function (info) {   for (var item in info) {     trace(item + " " + info[item]);   } }; // The   getFiles_Result( )   method is called when the CFC method returns a response. application.getFiles_Result = function (files) {   // Lock the RSO so it doesn't send updates until every file has been checked.   this.files_so.lock( );   var key;   var previous_data;   var update;   // Loop through every one of the files returned by the CFC method.   for (var i = 0; i < files.length; i++) {     // Create a key to use with the RSO. Use the subdirectory path and the      // filename to ensure a key that is unique to the stream.     key = "file" + files[i].PATH.split("\").join("_") + "_" +                    files[i].NAME.split(".")[0];     // Retrieve the data stored in the RSO currently for the key.     previous_data = this.files_so.getProperty(key);     // Assume the file didn't change and the clients don't need to be updated.     update = false;     // If the previous data was null, it is a new stream, so set   update   to true.      // Otherwise, loop through the elements of the previous data; if any values      // differ from the data returned by the CFC method, set   update   to true.     if (previous_data == null) {       update = true;     }     else {       for (var item in previous_data) {         if (previous_data[item] != files[i][item]) {           update = true;         }       }     }     // Change the RSO element only if its value differs from the previous version.     if (update) {       this.files_so.setProperty(key, files[i]);     }   }   // You also need to check whether a stream was removed. So get    // the keys of the RSO in order to loop through it.   var keys = this.files_so.getPropertyNames( );   // If there are no keys in the RSO, unlock the shared object and exit the method.   if (keys == undefined) {     this.files_so.unlock( );     return;   }   var remove;   // Loop through each of the keys of the shared object.   for (var i = 0; i < keys.length; i++) {     remove = true;     // Loop through each of the files to make sure the element in the      // shared object still exists on the server.     for (var j = 0; j < files.length; j++) {       key = "file" + files[j].PATH.split("\").join("_") + "_" +                      files[j].NAME.split(".")[0];       if (key == keys[i]) {         remove = false;         break;       }     }     // If no matching element was found, remove the element from the shared object.     if (remove) {       this.files_so.setProperty(keys[i], null);      }   }   this.files_so.unlock( ); }; 

The preceding code is complex enough that it deserves some further explanation.

When the application starts, you want to programmatically determine the physical path to the application on the server. You can retrieve that information using the Admin Service application that comes with FlashCom. So you should connect to the Admin Service using a NetConnection object (just as the Admin Console client, admin.swf , that comes with FlashCom does). Make sure you use the correct username and password in order to connect to the application (these are set in the Admin Service application). Once a "NetConection.Connect.Success" message is returned, you can call the getConfig( ) method to retrieve the physical path to the applications directory:

 this.ncAdmin = new NetConnection(  ); this.ncAdmin.onStatus = function (info) {   if (info.code == "NetConnection.Connect.Success") {     var key = "Adaptor:_defaultRoot_/VirtualHost:_defaultVhost_/AppsDir";     this.call("getConfig", application, key, "/");   } }; this.ncAdmin.connect("rtmp://localhost:1111/admin",                      "adminuser", "adminpassword"); 

The response from the getConfig( ) method is handled by the application object. Once the response is returned, you next want to calculate the path to the streams in the application and then call the getFiles( ) CFC method. The following code assumes you are using FlashCom on a Windows machine and, therefore, need to convert forward slashes to backslashes. Additionally, it sets up a polling interval to call getFiles( ) every 10 seconds:

 application.onResult = function (value) {   var application_path = application.name.split("/");   var path = value.data + "\" + application_path[0] + "\streams\" +              application_path[1];   this.file_interval = setInterval(this.frservice, "getFiles", 10000, "\", path);   this.frservice.getFiles("\", path); }; 

Once the response is returned from the getFiles( ) call, the first step is to lock the shared object. Locking the shared object makes sure that the connected clients aren't updated as soon as a property within the RSO is changed. Instead, it will wait until unlock( ) is called:

 application.getFiles_Result = function (files) {   this.files_so.lock(  ); 

Checking the returned files against the existing RSO data takes two steps. The first is to loop through each of the elements of the returned array to check whether any elements have been added. Each element in the RSO uses a unique key composed of the subdirectory path and the name of the stream file:

 for (var i = 0; i < files.length; i++) {   key = "file" + files[i].PATH.split("\").join("_") + "_" +                  files[i].NAME.split(".")[0];   previous_data = this.files_so.getProperty(key);   update = false;   if (previous_data == null) {     update = true;   }   else {     for (var item in previous_data) {       if (previous_data[item] != files[i][item]) {         update = true;       }     }   }   if (update) {     this.files_so.setProperty(key, files[i]);   } } 

The second step is to loop through each of the elements of the RSO to see whether any elements have been removed from the server. Each element of the RSO needs to be compared to each element of the files array. If the RSO key is not found in the files array, then it means the stream was removed from the server. Therefore, it should be removed from the RSO as well:

 for (var i = 0; i < keys.length; i++) {   remove = true;   for (var j = 0; j < files.length; j++) {     key = "file" + files[j].PATH.split("\").join("_") + "_" +                    files[j].NAME.split(".")[0];     if (key == keys[i]) {       remove = false;       break;     }   }   if (remove) {     this.files_so.setProperty(keys[i], null);    } } 

Once the comparisons and updates have taken place, next call the unlock( ) method to send updates to the clients if necessary:

 this.files_so.unlock(  ); 

12.3.4. Making the Streams Client

The remaining step in the example is to create the client that retrieves the stream listing from the FlashCom application. In order to create the client, make a new Flash document, name it streamList.fla , and add two items to the Stage: a List component instance named files_list and a Video object named playback_vid . Then, add the following code to the first frame of the Actions layer on the main timeline. The client displays a list of streams in a listbox and plays back the stream selected by the user :

 // Create the connection to the FlashCom application. var nc = new NetConnection(  ); nc.connect("rtmp:/fcs_streams"); // Create the RSO. var rso = SharedObject.getRemote("files", nc.uri); // When the RSO synchronizes, remove the elements from the list, and  // repopulate it with the contents in the RSO. rso.onSync = function (list) {   files_list.removeAll( );   for (var item in this.data) {     files_list.addItem(this.data[item].NAME, this.data[item]);   } }; // Connect the RSO. rso.connect(nc); // Define a listener object to listen to   change   events dispatched by the list. var oListener = new Object( ); // A   change   event means the user selected a stream from the list,  // so play back the selected stream. oListener.change = function (oEvent) {   // Use   split( )   to split the path of the stream data using a backslash as the   // delimiter. Then, remove the first element from the array to get rid of the   // empty string element.   var path_array = oEvent.target.value.PATH.split("\");   path_array.shift( );   // Next, if the first element is still an emtpy string, remove it as well.   if (path_array[0] == "") {     path_array.shift( );   }   // Join the elements of the path array again using a forward slash since that is    // the format FlashCom uses to play streams.   var path = path_array.join("/");   // If the path is not an empty string, append a trailing forward slash.   if (path.length > 0) {     path += "/";   }   // Add the stream name to the path. Use   split( )   to remove the file extension.   path += oEvent.target.value.NAME.split(".")[0];      // Play the stream.   ns.play(path);   // Attach the stream to the Video object.   playback_vid.attachVideo(ns); }; // Register the listener object with the List instance. files_list.addEventListener("change", oListener); // Define the NetStream object to play back the streams. var ns = new NetStream(nc); 

Once you've added the preceding code, you can test the movie. You should get a listing of the streams in the streams/_definst_ directory as well as the streams/_definst_/archived directory. If you select one of the streams from the list, it will play back. Additionally, with the client still running, notice what happens when you move streams in and out of the streams/_definst_ and streams/_definst_/archived directories. Because of the polling interval on the server, it may take up to 10 seconds to see the newly updated stream list.

This example demonstrates one of many FlashCom applications that use ColdFusion's powerful features to make the application more capable or efficient.



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