LegacyCaptivateLoader: dealing with pre-existing scripts in your Captivate SWF

Many of us use a Flash-based course interface (a.k.a. ‘player’) to load Captivate SWFs and other content. A well-known stumbling block for this kind of ‘loaded SWF’ approach has been Captivate’s lack of ActionScript support — Captivate won’t allow a user to add a simple line of custom ActionScript anywhere. This means that Captivate does not natively support direct SWF-to-SWF communication.

Here’s a common scenario where this might be a problem:

A developer wants to load a Captivate SWF into a ‘player’ SWF. She wants the Captivate SWF to automatically unload when it’s done playing. To do this, she’d simply like the Captivate SWF to call an ActionScript function named “unloadMe()” at the end of the movie.

Since Captivate doesn’t support custom ActionScript, this seemingly innocent bit of scripting can’t be done… at least not natively.

The first band-aid

In my session on the topic at DevLearn 2007, I discussed two primary workarounds for enabling a Captivate SWF to communicate with the parent Flash-based SWF:

  1. Embedded SWFs: Import a Flash “ActionScript” SWF into the Captivate project. This embedded ActionScript contains a reference to a function stored in the Flash SWF’s main timeline; when the Captivate playhead hits the embedded SWF, it will invoke the ActionScript in the embedded SWF, which in turn invokes the function in the main Flash SWF.
  2. ExternalInterface: Use Captivate’s limited JavaScript support to make JavaScript calls, which are then handled by ExternalInterface code in the main Flash SWF.

From what I can tell, these workarounds have become a (begrudgingly) accepted common practice. The ’embedded SWF’ method in particular has been in use by the Captivate community for at least three years.

The second band-aid

Both workarounds work great when the ‘player’ SWF is using ActionScript 2.0 (the same version of ActionScript used by Captivate when publishing its SWFs), but with the release of ActionScript 3, a much larger stumbling block was introduced: ActionScript 2 SWFs cannot communicate with ActionScript 3 SWFs at all. By design! The simple explanation is that ActionScript 2 uses a different processing engine than ActionScript 3, and never the twain shall meet.

I recently posted a workaround on the topic: a Flash ActionScript 3 class (and proxy SWF) I named LegacyCaptivateLoader. It enables an ActionScript 3-based player to load and control Captivate SWFs via ExternalInterface and the proxy SWF.

When I designed the LegacyCaptivateLoader, I was focused on giving the ActionScript 3 SWF the ability to control the ActionScript 2-based Captivate SWF; I hadn’t given much thought to how the situation affects Captivate SWFs using one of the workarounds I just described. Can the embedded SWFs still work? Will JavaScript calls from Captivate still work with ActionScript 3’s ExternalInterface system? The short answer is yes, but it may take some tweaking on your part.

LegacyCaptivateLoader and the Captivate-to-Flash communication workarounds

Geez, that title sounds terrible, doesn’t it? It certainly doesn’t sound like fun. πŸ™‚ Fear not, it isn’t as bad as it sounds. Let’s take a look at each of the workarounds and see how they fare with LegacyCaptivateLoader.

Workaround one: Embedding ActionScript SWFs in a Captivate SWF

I think it’s pretty obvious this one will need some attention. (And by the way, ActionScript 3 SWFs won’t work correctly when imported into Captivate, so don’t bother trying.)

Situation: Since Captivate uses ActionScript 2, any embedded SWF must also use ActionScript 2. That means that even if the embedded SWF refers to _root, now that the parent SWF is using ActionScript 3, the Captivate SWF and all of its contents can no longer communicate with the parent SWF.

Scenario: If we use the earlier example of a developer trying to call an ActionScript function named “unloadMe()” from Captivate, with the goal of invoking a function named unloadMe() in the main timeline of the parent SWF, we see that the AS2/AS3 barrier effectively kills the communication and renders our innocent little embedded SWF impotent.

Solution: Use the LegacyCaptivateLoader’s proxy SWF to listen for the embedded SWF’s calls and pass them to the AS3 SWF using ExternalInterface.

Yes, this definitely involves some custom coding on your part! It basically works like this:

The proxy SWF is an AS2 SWF that loads the Captivate SWF. That means the proxy SWF has become the defacto parent SWF; if the embedded SWF contains a call to _root.unloadMe(), Flash Player will look on the proxy SWF’s main timeline for a function named “unloadMe()”.

With me so far?

Here’s where the custom coding comes in: for every custom ActionScript call you expect to receive from the Captivate file, you’ll need to create a corresponding function in the proxy SWF and HTML file. The proxy SWF uses ExternalInterface to call the function in the HTML file, which in turn uses ExternalInterface to call the function in the AS3 file.

Complicated, yes. Fun, no. But it works.

Example A (using HTML)

Captivate SWF contains:

_root.unloadMe()

Proxy SWF contains:

function unloadMe(){
   ExternalInterface.call("unloadMe");
}

HTML contains:

function unloadMe(){
   var swf = document.getElementById("playerID");
   swf.unloadMe();
}

AS3 “Player” SWF contains:

var captivate:LegacyCaptivateLoader = new LegacyCaptivateLoader("playerID", "captivate.swf");

function unloadMe():void {
   captivate.unloadSWF();
}

ExternalInterface.addCallBack("unloadMe", unloadMe);

Note: you can also use the global ID method to skip the HTML function requirement, but it requires that you know the player SWF’s ID and hard-code it into the proxy SWF. This may not scale very well.

Example B (using global ID to avoid HTML)

Captivate SWF contains:

_root.unloadMe()

Proxy SWF contains:

function unloadMe(){
   ExternalInterface.call("playerID.unloadMe");
}

AS3 “Player” SWF contains:

var captivate:LegacyCaptivateLoader = new LegacyCaptivateLoader("playerID", "captivate.swf");

function unloadMe():void {
   captivate.unloadSWF();
}

ExternalInterface.addCallBack("unloadMe", unloadMe);

Workaround two: Using JavaScript in Captivate and ExternalInterface in the AS3 SWF

As far as I can tell, if you implemented JavaScript calls in your Captivate SWF, they should continue to work as before, without needing to modify the proxy SWF (yay!). Just be sure to update the functions contained in your HTML.

Example

Captivate SWF contains:

JavaScript: "unloadMe()"

HTML contains:

function unloadMe(){
   var swf = document.getElementById("playerID");
   swf.unloadMe();
}

AS3 “Player” SWF contains:

var captivate:LegacyCaptivateLoader = new LegacyCaptivateLoader("playerID", "captivate.swf");

function unloadMe():void {
   captivate.unloadSWF();
}

ExternalInterface.addCallBack("unloadMe", unloadMe);

What do you think?

While some (most?) of this is certainly a headache, my hope is that it will save you time compared to re-engineering and re-exporting all of your Captivate files. If you find this helpful, or if you know of a better, cleaner way to handle the situation (short of Captivate 4 coming along and being ActionScript 3 compatible), please let me know! πŸ™‚

New SCORM ebook coming soon!

I'm writing an ebook explaining how to build an HTML-based SCORM course. Subscribe to be notified when it's ready, as well as receive early bird pricing and some free goodies!

No spam, no sharing your email address, unsubscribe at any time. Powered by ConvertKit
Advertisements

3 Replies to “LegacyCaptivateLoader: dealing with pre-existing scripts in your Captivate SWF”

  1. I found myself in this same situation where I had to load captivate movies recording scores and clicks. The solution I had come up with is using local connection. It appears to be working rather well but it was kind of awkward for me to code it.

    Great solutions though, I think I will try out the javascript one and see how that does compared with local connection

Comments are closed.