Building a flash video player

on Saturday, June 27, 2009

Before I get into the technical details, I wanted to present some reasons why you may want to make your own customized/skinned flash video player instead of using YouTube, iTunes podcast or some other tool. In no particular order, you may need to build a custom player, if

  • custom branding of the user interface
  • protecting video content from unauthorized distribution and copying
  • copyright issues
  • customization with other flash feature in a project

The flash videoPlayer class is not without its quirks, especially in Flex3/Actionscript 3. There are some changes in the beta for Flex 4 (now renamed Flash Builder) to make creating a custom video player easier. I'm going to go over creating a single and multi-clip video player in actionscript using three different ways to get the data to the video player.

Method 1:

With the static method you embedded the video clip/s in the player and compile the project into a SWF to be placed on your website. This protects the video files from being accessible to others, but the downside is you must compile a new SWF every time you want to display video on your website.

Method 2:

With this dynamic method, the video player is passed some information on which clips to display and then queries a database to get information on the track/s titles, duration, and file path. This method requires a web service to be executed on a local or remote server which will return the query. Depending on your web environment, this option may not be available to you.

Method 3:

With this dynamic method, the video player is passed a file path to an XML file with information on the track/s title, duration, and file path. This information could be formatted in the exact same manner as a query would be returned by the web service. In fact, most web services return data in the XML format. Since the XML document is authored and dynamically acquired by the video player, you can reuse the video player by just pointing it to load a different XML file at run time.

I've used both method 2 and 3 for projects. If you catalog and store meta-data about your video assets in a database and have a web application server (Cold Fusion, PHP, etc), I think its the best option. If you do not, I would recommend method 3 and statically writing an XML file. I strongly discourage method 1.

Building the code

I build my projects in Flex Builder 3 and depending on the task choose to use either Actionscript 3 or MXML or a combination of both. In this section, I will discuss implementing both method 2 and 3. In each case, the first step is the same. Upon the application being created is for a function to be executed to load the dynamic data. In my projects I call this function with the CreationComplete attribute on the <mx:Application tag> and for consistency sake, I normally call this function initApp.

Example for method 3 (dynamic XML)

 public function initApp():void
 {

 var thisXML:String = Application.application.parameters.thisXML;

  var loader:URLLoader = new URLLoader(new URLRequest(thisXML));
  loader.addEventListener(Event.COMPLETE, loadNewXML);

 }

 public function loadNewXML(event:Event):void
        {
     
         loadedXML = new XML(event.target.data);
      
        }

In this implementation, the XML file is passed to the Flash SWF by FlashVars. For more information on using Flash Vars, read this adobe technical note.

Example for method 2 (Media Database)

In MXML, you can setup the web service with the RemoteObject tag by defining the which web service to call and functions to handle the result and fault event. Then in my InitApp function, I actually make the call to execute the Remote Object.



public function initApp():void
 {

        //list of video clip IDs passed into the SWF via Flash Vars on the html code block to load the SWF
  theseClip = Application.application.parameters.videoIDs;
   
       //executes the web service
 getClip.VideoClip(theseClip);
      
        }

In this implementation, the videoIDs are the IDs from the media database and they are passed to the Flash SWF by FlashVars. Depending on your database schema, you can return whatever columns you want, but at a minimum you would want track title, duration, and file path. For both method 2 and 3, I have formed the XML the same way.




 
  
   Clinical Pearl: Screening for Prostate Cancer
   http://localhost/Screening_Randy.m4v
   2:47
  
          
   < clip_title >Clinical Pearl: Screening for Breast Cancer
   http://localhost/Screening_Victoria.m4v
   3:25
  
 


Instead of creating a multi clip and single clip video player, I let the programming determine how many clips exist in the XML.

 private function processClips():void {
 
       thisResult= getClip.VideoClip.lastResult;
    
 if(thisResult.length > 1) {
   multiClip();
 } else {
     
  singleClip();
 }
  
    
}

Depending on how many clips are return, I resize the canvas and display the clip selector panel if there is more than one clip. Below are examples of each view and then I will explain the code.


Multi-Clip View


Single-Clip View

Rendering the Video Clip component

The processClips function execute either the singleClip or multiClip function depending on the number of clips contained in the XML. In the case of the single Clip function, it changes the visibility on the clipSelectionPanel and instantiates a custom video component. The same custom component is rendered with the multiclip view, but changes depending on what is selected in the clipSelectionPanel.


 private function singleClip ():void {
   
    //hides the clipSelectorPanel
    clipSelectorPanel.visible = false;

    file_path = thisResult[0].file_path.toString();
    
    //removes placeholder image that displays before video is loaded
    vidScreen.removeAllChildren();
       
       //custom component
        var thisVid:customVideoComponent = new customVideoComponent;
            thisVid.data =   file_path;
         vidScreen.addChild(thisVid); 

 }

 private function multiClip ():void {
  clipSelectorPanel.visible = true;
      
               //expands the canvas size variables
  canvas_width = 535;
  canvas_height = 400;
 }

In the case of a single clip, the function immediately loads the video and auto-plays the clip. For the multi-clip display, the clipSelectionPanel has a click handler. The clipSelection panel is an MXML list control with a custom itemRender.

  
    
  

When a user clicks on any item in the list, the following function is executed:


   private function clickHandler():void {
        
       // stops any video if it is currently playing
       this.thisVid.stop();
      
       //clears any video player on the canvas and/or the placeholder graphic
       vidScreen.removeAllChildren();

    //file path of the selected clip
       var thisvidFile:String =  videoSelector.selectedItem.file_path.toString();

    //custom video component     
          var thisVid:customVideoComponent = new customVideoComponent;
        thisVid.data =   thisvidFile;
        vidScreen.addChild(thisVid); 

       } 

In the code for the customVideoComponent I have an important event listener for when it is removed from the stage. This is because of a particular issue I discovered with the videoPlayer class when you remove them from the stage:

  • while the video display disappears, the audio will continue to play
  • if a clip is not fully downloaded it will continue to download even if the user is viewing another clip

To remedy this issue, I created the videoInterupt function.

  private function vidInterupt(event:Event):void{
   
   
    if(event.currentTarget.thisVideoDisplay.state != "disconnected") {
     event.currentTarget.thisVideoDisplay.stop();
    }
   
    if(event.currentTarget.thisVideoDisplay.state != "disconnected") {
     event.currentTarget.thisVideoDisplay.close();
    }

         SoundMixer.stopAll();
    

   } 

UI Considerations

From the sample images above you'll notice that the video player displays a default image until a clip is selected/loaded rather than leave a void for the video display on the canvas. The image also serves to direct users to use the clip selection panel to choose a clip in the multi-clip state. The video controls are also displayed in the image but are dimmed out.

In actionscript/mxml there is a lot of flexibility in designing the user interface. Originally I used MXML buttons for the play/pause/stop functions, but later switched to the graphical icons you see in the sample images. The interface also has a load controller display, which is especially useful for users who may have a slow connection to know the video is loading. Most video players I see on the net follow youTube like conventions for the UI. Below is the code for the custom video component.





 
    import mx.events.FlexEvent;
    import flash.media.Video;
    import flash.media.SoundMixer;
    import flash.events.ProgressEvent;
    import mx.events.VideoEvent;
    import mx.controls.Image;
   
   [Bindable]
   public var thisVid:String;
   
      
   private function initVideo():void{
    
    //get source from event target
    
    thisVid = this.data.toString();
    
    thisVideoDisplay.addEventListener(Event.REMOVED_FROM_STAGE,vidInterupt);
     
    
   } 
   
   private function vidInterupt(event:Event):void {
    
                       if(event.currentTarget.thisVideoDisplay.state != "disconnected") {
                       event.currentTarget.thisVideoDisplay.stop();
                       }
   
                      if(event.currentTarget.thisVideoDisplay.state != "disconnected") {
                       event.currentTarget.thisVideoDisplay.close();
                      }

                      SoundMixer.stopAll();
    
   }
   
 
   private function showInfo(event:VideoEvent):void {
   trace('stateChange: ' + event.state + ' ' + event); var foo:Timer = new Timer(2000, 100); foo.addEventListener(TimerEvent.TIMER, timerComplete); foo.start(); 
   trace('Clip: ' +  thisVideoDisplay); } 
 
   private function timerComplete(event:TimerEvent):void {
   trace('timerComplete: ' + thisVideoDisplay.state + ' ' + event); } 
   
   private function Vidplay(event:MouseEvent):void{

        
      thisVideoDisplay.play();
       }
       
      private function Vidpause(event:MouseEvent):void{
        
      thisVideoDisplay.pause();
       }
       
      private function Vidstop(event:MouseEvent):void{
        
        
      thisVideoDisplay.stop();
       }
       
   private function formatPositionToolTip(value:int):String{
  
    
    var result:String = (value % 60).toString();
          if (result.length == 1){
              result = Math.floor(value / 60).toString() + ":0" + result;
          } else {
              result = Math.floor(value / 60).toString() + ":" + result;
          }
          return result;
   } 
   
 



  
   
  
  
   
   

  
    
  

 
 
   
   
    
  


In conclusion

In this tutorial, I have reviewed why you would create a custom video player in actionscript/mxml and how you would do it. I hope you found it useful and benefit from what I've learned when using the videoPlayer class. Let me know if you have any questions or have a suggestion for different approaches.

Technorati Profile