Using JavaScript to handle Flash (ActionScript) events

Introduction 

While working on a recent project which involved building a video player, I came across an interesting design requirement.  I needed to have an integrated web page where events occurring in a Flash movie (Flash CS3 / ActionScript 3) could be handled by JavaScript functions.   To make it more fun, I didn’t know ahead of time what JavaScript functions might want to listen to the events, or even how many.  So, my conundrum  was:  How to create a multicast delegate in Flash that could be assigned in Javascript.  Fun, huh? 

Basics

Many of you may know already that Flash supports a number of methods for enabling communication from and to Javascript.  These have been well documented elsewhere, so I’m just going to focus on the method I chose, ExternalInterface. 

Now, ExternalInterface makes it pretty easy in most modern browsers to set up Flash-JavaScript communications.  It has a simple interface of two functions, one to expose an ActionScript function to JavaScript, and another to call a known JavaScript function.  The basic syntax is:

ExternalInterface.call(“JsFunctionName”[, args]); // calls a JavaScript function from inside your Flash or Actionscript file, passing arguments as additional parameters after the string function name of the JavaScript function.

ExternalInterface.addCallback(“JsFunctionName”, AsMethod); // exposes the ActionScript method AsMethod to javascript under the javascript function name JsFunctioName, which is passed as a string. 

As a quick example, you could write ExternalInterface.call(“alert”,”Hello, world”); to have Flash instruct JavaScript to fire off an alert box that says “Hello, world”.  Alternatively, if you want to have JavaScript pass in style information to a Flash movie, you’d expose a setStyle() function from ActionScript like so:

ExternalInterface.addCallback(“setStyle”,setStyle);

and then have the JavaScript access it like so:

movieObject.setStyle(/*style information you’re passing*/);

A Few Notes and Gotchas

I did encounter some trouble in Paradise getting this far, when I did it the first time.  First ‘gotcha’, I needed to abstract the whole thing out as a component that could be loaded via javascript; I wasn’t going to directly mark up the html.  That’s not a huge problem, but for background purposes it meant I wanted to write a JavaScript object that could instantiate the movie when its constructor is called.  To save time developing that piece of the solution, I made use of the excellent SWFObject  library which takes care of most of the heavy lifting there for me.  Because the <object> / <embed> tags are being dynamically generated, I have the added advantage of bypassing the “click to activate” message you get with static flash tags.

The next note is that I needed to make sure that my JavaScript object maintained a reference to that embedded movie object, since I’d need to call those exposed ActionScript methods against that movie object.  So, one of the lines of code called when my JS object is instantiated is:

this._component = (window[this._id]) ? window[this._id] : window.document[this._id];

An odd line of code, resulting from the need to be cross-browser.  Basically, browsers get references to Flash movie tags differently, so this line tests to see if the first method (window[this._id]) returns anything.  If it does, then it sets this._component as a reference to it, and if not, it tries to get window.document[this._id]. 

Another gotcha which I discovered after quite a while is that IE (naturally…) doesn’t destroy instances of the movie correctly when a page is reloaded or the like. In fact, it fouls up the whole works.  To get it to work, in my constructor, I call,

if(navigator.appName.indexOf(‘Microsoft’) != -1)
              
window[this._id] = new Object();

that gotcha is well documented at Steve Kamerman’s blog and that’s how I got my object working correctly.

Adding the Event Callbacks

So, that all worked pretty well.  I came up with a plan to handle my need for multicast delegates by having my Flash movie maintain a list of javascript event listeners for each event that I was going to expose; more or less an Observer Pattern -inspired technique, but with the Observer being an actual function rather than an object implementing a given interface. 

As an example, my video component raises an event when the video starts playing, and then another event periodically to notify listeners what the current elapsed time is.  So, I created a list property which holds the external handlers and each external handler has to indicate which event it’s listening to,and provide a string name of the javascript function it’s going to call.  It does this by calling a function I exposed through ExternalInterface called
registerJSListener(eventName:String,functionName:String). 

As registerJSListener gets called, the list of listeners is appended to.  I then have an Actionscript function that listens for, say, “play” events and iterates over the list of external handlers to fire them off in turn.  It looks something like this:

private function onPlay(ev:Event){
      var len = _externalHandlers.length;
      if(len <= 0) return;
      for (var i:uint=0;i<len;i++){
            var h:Object = _externalHandlers[i];
            if(h.event.toLowerCase() == ‘play’)
                       ExternalInterface.call(h.fcn,ev);
     }
}

Interestingly, the object ‘ev’ is passed into the JavaScript function and all its basic type data elements (anything that can serialize into JavaScript) are available for use.  You can also pass a custom object if you want custom data to pass down, again so long as its properties are serializeable into JS (generally, I’ve stayed with basic types such as string, boolean, and integers so far).

One Last Gotcha…

I was feeling pretty good about myself after getting all that figured out and wrapped by my JavaScript proxy class (the registerJSListener call gets made by the proxy in response to another object calling its observe method, which looked like this:

observe : function(eventName,observer){
      if(typeof(observer).toLowerCase() != “string”)
               throw “Target of VideoPlayer.observe must be the string representation of a function name”;

      this._videoComponent.registerJSListener(eventName,observer);
}

  I wanted to keep that part syntactially in line with Prototype, since that’s what I was planning on using for the rest of the integration).  So, in my page load handler I tried the following:

var video = new VideoPlayer($(‘content’)); //where ‘content’ is the element I want the html inserted into.

video.setVideoUrl(‘movies/one.flv’);

video.observe(‘play’,’handlePlay’);

and… I got an error in Javascript stating that _videoComponent didn’t have any properties.  _videoComponent is a private field in the VideoPlayer object that stores a reference to the flash player instance that my video is supposed to be playing in…

After a bit of debugging, I figured out that what was happening was that the movie wasn’t done downloading and getting ‘wired up’ so to speak with the flash player, and so it hadn’t exposed its ExternalInterface callbacks by the time my JavaScript was trying to execute them.

To solve the problem, I created an observer queue in the JS proxy that would hold onto those requests until the Flash component was done loading (at which point I had it call an onload function via ExternalInterface.call() which I set up to notify the proxy that it was safe to run those callbacks).  The new observe method looks like this:

observe : function(eventName,observer){
     if(typeof(observer).toLowerCase() != “string”)
          throw “Target of VideoPlayer.observe must be the string representation of a function name”

     if(this._loaded == false){ // the onLoad function hasn’t been called yet
          this._jsListenerQueue.push({evt:eventName,obs:observer}); // queue it up!
          return;
     }

     this._videoComponent.registerJSListener(eventName,observer);
}

In order to allow the flash component to call that specific instance of the video player proxy in Javascript, I created some static methods (functions) in JS that were scoped to the page (in case I wanted to let other flash components use the same framework to add to the page). 

Those were:

flash = {}  // Just an empty object

// These next two are a pattern I found in the ASP.Net Ajax Framework for creating a singleton-style effect in JS.
flash._movies = null;
flash.getMovies = function(){
     if(!flash._movies)
          flash._movies = [];
     return flash._movies;
}

/* When a Flash movie proxy starts loading the .swf for itself, it needs to register with the flash object; the id returned
     get_id needs to be included in the context variables (flashvars or other) so that the flash movie can get ahold of it
    through the root.loaderInfo.parameters collection. */
flash.registerMovieLoading = function(movieInstance){
    if(!movieInstance.get_id) throw ‘Movie proxy must implement get_id to uniquely identify itself’ on the page
     var m = flash.getMovies();
     m[movieInstance.get_id()] = movieInstance;
}

/* In the constructor of the flash object assigned as the document class of the .fla file, ExternalInterface.call
     (‘flash.notifyFlashLoaded’) is called. */
flash.notifyFlashLoaded = function(id){
     var m = flash.getMovies();
     var movie = m[id];
     if(movie && movie.onLoad)
          movie.onLoad();
}

Conclusion

That’s basically it.  Let me know if there are any confusing parts that I can explain better.  This is a pretty handy technique and once its in place it makes communicating with Flash fairly transparent to other javascript objects. 

About these ads

5 Responses to “Using JavaScript to handle Flash (ActionScript) events”

  1. Idrees Says:

    Hi,
    I am writing a PLugin for browser which captures the downloading path of the video running on it, like youtube video is running and plugin feature to download that video.
    I am thinking to get hold of flash player and get the path of file currently running.

    Can you help me in this regard? Your suggestions are welcomed.
    Waiting for the reply.
    Thanks,
    Idrees.

  2. Paul Vencill Says:

    Well, with a video there are two paths you need to be thinking of (possibly more, but at least two). The Flash player itself will be served from one URL, and that will have a .swf ending. The movie itself will be served from another (probably, but not necessarily, in the same domain) which will have a .flv ending. From javascript it’s pretty trivial to find the .swf one; you just find the correct Object or Embed tag and then look at the path that it’s pointing to.

    Finding out the path to the .flv is not possible from Javascript to my knowledge. What you’ll need there is a more generic HTTP proxy (e.g. Fiddler) which then can sniff out all HTTP/S traffic coming through the browser, and then look for movie URLs.

  3. er Says:

    thank you for this

  4. Luis Says:

    Thanks for the ground work.

  5. Cursos Fireworks Says:

    Cursos Adobe Fireworks…

    Using JavaScript to handle Flash (ActionScript) events « CodeRaptor…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: