Loading Captivate files into an AS3 Flash SWF

Update April 7, 2008: I’ve written a new AS3 class named LegacyCaptivateLoader that uses ExternalInterface to bridge the AS3 SWF and the Captivate SWF. Check it out.

I guess I’m late to the party, but I only recently realized that although a Flash Player 9 SWF can load an older Flash Player 6/7/8 SWF, it can’t communicate with it.

(In my defense, since we haven’t really started using ActionScript 3 at work yet, I’ve been a bit slow in switching to AS3. The leap from AS2 to AS3 is pretty daunting, so I’m sure I’m not the only one dragging my feet!)

Turns out the mechanism that processes the ActionScript (the ActionScript Virtual Machine, or “AVM” for short) has been rebuilt. Flash Player now ships with two unique ActionScript processors: AVM1 for legacy ActionScript 1 & 2 SWFs, and AVM2 for ActionScript 3 SWFs. Without getting overly technical, this enables AVM2 (Flash Player 9 SWFs) to be exponentially faster than AVM1 (Flash Player 8 and lower SWFs).

As you can imagine, many Flash developers — like you and me — still need to load old SWFs into a new Flash Player 9 (ActionScript 3) user interface. For instance, many Flash-based e-learning courses load ‘content’ SWFs that were created a couple of years ago with Flash MX (7) or Captivate. No one wants to recreate or republish a few years’ worth of development files.

To accommodate people who still need to use their older SWFs, Adobe configured Flash Player to allow AMV2 SWFs to load the older AVM1 SWFs in a virtual sandbox. But, as I mentioned, there’s a catch: these AVM1 SWFs cannot communicate with the parent AVM2 SWF.

This is a very big problem for many Adobe Captivate users. Adobe didn’t wait for the Captivate development team to convert Captivate to AS3, which means Captivate users are still publishing AS2 SWFs on a daily basis. A large number of Captivate users ‘play’ their Captivate files in custom Flash-based course interfaces. If they want to use a newer AS3 interface to control their Captivate SWFs (play, pause, etc.), they’re out of luck.

I guess you can’t blame Adobe for not updating Captivate’s codebase; Captivate has probably had the same codebase since early versions of RoboDemo, and converting to AS3 would probably require a complete overhaul of the product. No small task.

The Experiment

Anyway, I’ll get to the point: I researched the different methods available for AVM1 to AVM2 communication, and discovered there are a few workarounds that can enable the AS3 SWF to communicate with the AS2 SWF. I spent the entire day whipping up a Captivate-specific proof-of-concept, which can be viewed here.

For this experiment, I used LocalConnection. I’ve also been researching an ExternalInterface method, but the LocalConnection method was much easier to implement and doesn’t require JavaScript.

Because LocalConnection requires the old SWF to have specific LocalConnection code inside it, we can’t use LocalConnection on Captivate SWFs without a little help. I was able to use a proxy SWF to load the Captivate movie.

I’m not ready to explain the code and hand out the source files, but I hope this proof of concept can help others out. The short version is:

  1. A ‘player’ SWF (AS3) loads an AS2-based ‘proxy’ SWF. This proxy SWF is configured with custom LocalConnection settings allowing it to send and receive commands from the AS3 player.
  2. The proxy SWF loads the Captivate SWF. Since the proxy SWF and Captivate SWF are both AS2, they can communicate with each other using the famed ‘Captivate variables’.

Thus the AS3 SWF sends instructions to the proxy SWF, which relays the instructions to the Captivate SWF. Conversely, the Captivate SWF sends data (frame count, current slide, etc.) to the proxy SWF, which then sends the data via LocalConnection to the AS3 SWF.

BTW, using LocalConnection to bridge AVM1 and AVM2 isn’t an original idea… many people have blogged about these concepts over the last year or two, and had some good tips (see my references at the end of this post). There are even a few functional commercial and freeware products out there.

I decided to develop my own method out of curiosity, and because most of the existing products are overly complicated, designed to handle way more than my dinky little Captivate files. Plus I wanted to create a system that would have the ‘Captivate variables’ built-in, so it will be plug-and-play with any Flash-based Captivate loader.

Caveats

There are some very big caveats when using LocalConnection to bridge AVM1 and AVM2 SWFs; these caveats are big enough to make me question just how far I want to go with this project.

Caveat #1: LocalConnection is asynchronous. This means it can’t return values, and it may not kick in as soon as you’d hoped. I learned firsthand that LocalConnection worked much faster in my local environment than it did after I uploaded it to the server.

Caveat #2: LocalConnection works independent of the browser, and can only have ONE active connection per unique LocalConnection session. For instance, if I create a course that uses a LocalConnection named “FlashToCaptivate_LC”, I can only have one instance of that course running on my computer. If I open a second instance of the course, regardless of which browser it’s in, or whether or not the course is local or online, the second course will return a LocalConnection error because the connection named “FlashToCaptivate_LC” is already in use. Think of it as a phone number without call waiting. If someone is on the phone and you try calling, all you’ll get is a busy signal. That’s LocalConnection.

What do you think?

I’d love to hear any feedback you might have about this topic, including whether or not any of you have tried LocalConnection yourselves.

Resources

Here are some good resources/discussions about the topic if you’d like to learn more (no particular order):

Advertisements

DevLearn 2007

Today was the day… I gave a presentation at the eLearning Guild’s DevLearn 2007 conference in San Jose. Topic? Captivate-to-Flash ActionScript Communication. Not a sexy title, I know, but there was a pretty respectable turnout… which indicates to me that a lot of people want to use Captivate but are feeling quite frustrated with its limitations.

I had a few technical glitches with the laptop and projector (Murphy’s Law, I suppose), but otherwise all seemed to go well. The audience was very receptive and had some great questions. I even managed to give a little plug for SWFObject.

While working on this DevLearn presentation, I realized I have an awfully lot of Captivate-related stuff (scripting how-tos, demonstrations, blog posts, etc.). So much, in fact, that I decided to dedicate a new section of my website to Adobe Captivate. You can find it at http://pipwerks.com/lab/captivate.

I’m still trying to fill out the content and smooth out the wrinkles, so please bear with me. Hope you like it!

A look at Captivate 3.0, part one

Here are my first impressions of Captivate 3’s improvements and new features.

This is part one of a multi-part journal entry.

Importing PowerPoint presentations

Disclaimer: I have never recommended nor will I ever recommend using PowerPoint presentations as online courseware. It’s a bad idea, whether it’s been converted to Captivate or not.

Having said that, Captivate 3 represents a substantial improvement over Captivate 2 when importing PowerPoint (PPT) presentations. In Captivate 2, PPT animations were lost during the conversion, and Captivate 2 didn’t automatically add pauses (‘wait for mouse click’ behavior) in each slide. This meant that every slide became a still screenshot, and the converted presentation would automatically go from slide to slide in a matter of seconds. Users had to add their own navigational aids or use the Captivate playback controls.

Captivate 3, on the other hand, converts PPT animations to SWF format, enabling users to retain most of their PPT animation. I haven’t pushed the boundaries when testing this feature, but for general zoom, resize, and fade-style animations, the SWF version was identical to the PPT version.

One important thing to note about imported PPT presentations: Captivate 3 does NOT import clickable items. For one of my tests, I imported a large PowerPoint presentation that contained clickable items on each page. In this case, the clickable items were all homemade navigation aids, such as next, previous, and menu. Captivate stripped the links from all clickable items and rendered them as non-clickable drawings. Clicking anywhere on the screen — even on what used to be a ‘back’ button — simply caused the Captivate movie to go forward one slide.

Slide Transitions

In Captivate 1 and 2, users only had three slide transitions to choose from: fade in, fade out, and fade between. Captivate 3 has swiped emulated PowerPoint’s array of transition types, including blinds, fly, iris, photo, pixel dissolve, rotate, squeeze, wipe and zoom.

In the name of all designers who create items of taste, I implore you to ignore these new transitions unless they make sense for your particular needs. For most cases, I say less is better. There are some exceptions; for instance, if you’re recreating scenes from Star Wars and want that famous wipe transition between scenes, go for it! Or the iris transition for a James Bond-themed Captivate movie. Otherwise, most of these transitions are intended to appeal to the PowerPoint crowd, and are best left alone.

I’m not a Mac evangelist by any means (I use Windows XP), but if you want to see some very tasteful transitions, check out a demo of Keynote sometime. It’s enough to make me want to buy a MacBook Pro.

Why Adobe chose to emulate the worst-of-class PowerPoint transitions and ignore the best-of-class Keynote transitions — which are perfectly suited for a SWF — is beyond me.

Stronger Captivate branding

It seems Adobe is trying a little harder to spread the Captivate brand name around. The default playback controller now includes an “Adobe Captivate” stamp. This is only on the default skin, so you can easily choose another skin if you don’t like giving Adobe free advertising.

Adobe has also added a new Adobe-branded preloader graphic. The previous default was a small circle spinning around. The new default is a progress bar with the text “Adobe Captivate” placed in bold right above the bar. Again, free advertising for Adobe if you don’t want to change the defaults. If you don’t like the Adobe branding, you can choose a different preloader graphic, just like Captivate 2.

While I’m not crazy about this branding being set as the default, I also acknowledge that Adobe’s competitors have been doing it for years, and many of them won’t give you an easy way to remove their logos (hello, TechSmith). So long as Adobe keeps the branding optional, I’m ok with it.

Up next

In part two, I’ll take a look at the new Question Pool feature, as well as the LMS export options.

Captivate 3, JavaScript and Actionscript

I just got Captivate 3, and eagerly installed it to see if any improvements have been made regarding JavaScript and Actionscript handling. Short answer: nope.

Javascript

According to Captivate’s ‘Help’:

You can add JavaScript to click boxes, text entry boxes, and buttons in Adobe Captivate projects. The JavaScript can run when a user clicks inside or outside the box or button. Using JavaScript gives you the opportunity to extend projects in numerous ways while adding interactivity.

Same as before.

Actionscript

Absolutely zero changes from what I can tell.

Unfortunately, this means my previous comments regarding using JavaScript and Actionscript in Captivate movies still stand.

I plan to post a brief review of Captivate 3 shortly.

Actionscript, JavaScript, and SCORM

Update: A few months after writing this journal entry, I developed SCORM class files for ActionScript 2 and ActionScript 3 (both require ExternalInterface). Check them out here.

For the last week, I’ve been doggedly attempting to create a hybrid of Flash-to-JavaScript communication techniques for creating cross-browser SCORM-conformant courses that work with almost any version of Flash Player. Today I threw in the towel. Here’s my story.

Some background on SCORM in regards to JavaScript and ActionScript

  • SCORM is a standardized method of communicating between a web-based course and a Learning Management System (LMS).
  • SCORM communication is most commonly handled by JavaScript.
  • A Flash-based course therefore needs to communicate with the JavaScript in an HTML file in order to send SCORM calls to the LMS.

It looks like this:

course communication: AS to JS to LMS

Unfortunately, even though they’re both part of the ECMAScript family, ActionScript and JavaScript cannot natively communicate; a ‘communication system’ has to be installed in each Flash-JavaScript project if you want them to talk. Think of it as needing a cell phone to call your mom because she lives in another part of the country!

[ you > cellphone < mom ]

[ actionscript > communication system < javascript ]

Flash-JavaScript Communication: Old-School Versus New-School

Flash-to-JavaScript communication has long been a heavily-discussed topic. Let me try and get you up to speed on it without getting too technical. Old-School refers to Flash Players v6-7, while New-School refers to Flash Players v8+.

Old-School #1: FSCommand

The first old-school technique is to use FSCommand. FSCommand allows synchronous communication, and is pretty straightforward to implement. It’s arguably the most widely-used old-school method for Flash-JavaScript communication in Flash-based SCORM-conformant online courses. (Wow, that’s a lot of hyphens!) But FSCommand has a huge downside: it isn’t cross-platform. It’s specific to Windows machines, and will fail on Macs and Linux boxes.

Old-School #2: GetURL and SetVariable

The second old-school technique is to use ActionScript’s getURL() coupled with SetVariable in JavaScript. For a while, this technique seemed to be popular with a segment of Flash-based course developers who worked with SCORM. The technique basically works like this:

getURL in ActionScript invokes a function contained in the JavaScript. The JavaScript function contains a reference to the SWF, and invokes a method (function) called ‘SetVariable’ in the SWF.

Example:


//ActionScript
getURL("javascript:'myFunction(\'myVariable\')'");

//JavaScript
var myVar2 = "Send me to Flash!";

function myFunction(v){
   var swf = window.document.mySwfId;
   swf.SetVariable("myVariable", myVar2);
}

//ActionScript:
//watch() or setInterval() used here to detect
//when variable "myVar" has been updated.

This technique wasn’t very popular because it was hard to implement, could only pass strings (no object, arrays, etc.), and was also asynchronous.

Being asynchronous was probably the biggest sticking point for most people, because it meant you couldn’t return the data from JavaScript into ActionScript right away — no var myVariable = getURL("javascript:'myFunction(\'myVariable\')'");; you had to send your request from ActionScript to JS, then wait for it to come back. This required some ugly tricks such as object.watch and setInterval to check if the variable had been updated before you could use it in Flash.

Old-School #3: The Flash-JavaScript Integration Kit

The third old-school technique is to use a proxy SWF and LocalConnection. I believe this was first discussed by the guys at MustardLab, and was turned into a full-blown system — the Flash-JavaScript Integration Kit (FJIK) — by two Macromedia employees. I’ll try and explain this as simply as I can.

First, a few things to note about loading external variables into a SWF:

  1. When a Flash SWF first loads on a page, it’s really easy to pass variables into the SWF from your HTML page using FlashVars.
  2. If an HTML page has two SWFs embedded on it, passing variables between the two SWFs is (relatively) easy using LocalConnection.
  3. Passing variables to a SWF AFTER the SWF has already been embedded to the page is the tricky part.

(Warning: this may be a gross oversimplification to some die-hard coders.)

The main idea behind the FJIK method is to dynamically embed a second SWF to your HTML page whenever you want your original SWF to load a variable from JavaScript. This second SWF is usually referred to as a proxy or gateway SWF. The data is loaded into the second SWF using FlashVars as it gets embedded in the HTML. The data is then transferred from the second SWF to the first SWF using LocalConnection. When the transfer is complete, the second SWF can be deleted.

Sounds reasonable, right? This biggest benefit of the FJIK method is that it isn’t limited to passing strings; it supports passing different variable types, such as objects and arrays. It proved to be a popular technique, but it had a few significant drawbacks: transfer speed (you have to wait for a new SWF and FlashVars to load each time you make a call), the required use of 4 or 5 external files (.as, .js, and .swf) is cumbersome, and — like the getURL method — the data is returned asynchronously. No instant ‘return’ statements.

New-School: ExternalInterface

The new-school technique is to use ExternalInterface, which was specifically designed by Macromedia Adobe to make Flash-JavaScript communication much easier. By most accounts, they did an excellent job — data can now be called from ActionScript and returned from JavaScript synchronously, which means your ActionScript and JavaScript can work as one. The code has some kinks, but generally speaking has worked pretty well.

Back to the point: Why the attempt at a hybrid?

You might ask: Why was I trying to make a hybrid of old and new techniques in the first place? The new-school ExternalInterface method works fine and is super-easy!

Answer: ExternalInterface works fine for people with Flash Player 8 and above. That eliminates the majority of the student population I’m targeting. Yes, I know Flash Players v8-9 are supposed to be ubiquitous, but the IT dept. at my workplace installed Flash Player 7 in early 2005 and hasn’t updated most of the machines since then. Employees have no admin rights on their computers, and therefore way to update Flash without calling IT to come do it. When you have over 5,000 employees, this becomes a big issue. It will be at least a few months before the majority of users get Flash Player upgrades.

I also have a colleague in a similar situation, except his target audience is mostly Mac users! This means FSCommand won’t work for him, and can’t hold him over until his end users get Flash Player upgrades.

Simply put, I was hoping to devise a solution for both our problems: a Flash-JavaScript communication method that doesn’t care what version of Flash you have or what platform you’re using! What a dream, huh?

How I hoped it would work

I wanted to create an abstraction layer that separates the SCORM calls from any particular Flash-JavaScript communication method. I was hoping I could do something as simple as this:


//ActionScript

var canDoEI:Boolean = ExternalInterface.available;
getURL("javascript:setCanDoEI(" +canDoEI +")");

function getDataFromJS(v){
   if(canDoEI){
      //external interface call
   } else {
      //old-school method
   }
}

//JavaScript

var canDoEI = false;

function setCanDoEI(v){
   canDoEI = v;
}

function sendDataToFlash(v){
   if(canDoEI){
      //external interface call
   } else {
      //SetVariable method
   }
}

This would have allowed me to use the same function calls in Flash Player v6-7 as in Flash Player v8-9, regardless of what technique was being implemented under-the-hood. However, because ExternalInterface and the FJIK each rely on imported classes, have such different syntax, and aren’t both synchronous (only ExternalInterface is synchronous), it would have been a huge headache to try and cram these very different techniques into one course. Bah humbug.

Why not use getURL/SetVariable instead of FJIK?

Granted, the getURL/SetVariable method is much easier to implement than the FJIK method, but it’s also limited to passing strings, and is asynchronous. The asynchronous nature of the getURL/SetVariable method is the main sticking point for me… it’s really tricky to set up the watchers/intervals needed to detect when the variable has successfully been returned from JavaScript. It doesn’t mix well with the synchronous and much speedier ExternalInterface.

So whatcha gonna do?

Wait until we upgrade to Flash Player 9. Sucks, but it’s a pragmatic choice.

I’m in a unique situation in that our IT dept. only supports Internet Explorer on Windows PCs. So far I’ve been able to use the easy-to-implement (and PC-only) FSCommand. I’ll be the first to admit this is something I’ve never been happy about, but hey, it has been a practical and fully-functioning solution for over 2 years. When I developed my current Flash course interface, I knew ExternalInterface was over the horizon, so I didn’t bother with getURL or the FJIK (which hadn’t been released yet). Little did I know that over 2 years later we’d still be supporting Flash Player 7!

In my own defense, I must say that I haven’t ignored standards and best-practices: everything else I’ve built is been cross-browser and cross-platform, including course content. But my guilt is catching up to me, along with a new crop of Mac users at work! It’s in all of our best interests for me to stop using FSCommand in our SCORM courses. If it weren’t for that stinkin’ Flash Player 7 on our older computers… grrr.

So my plan for now is to develop an ExternalInterface version of our Flash-based course interface, and have it ready for that fateful day when the IT guys tell me Flash Player 9 is up and running on (most of) our machines. Sigh… I was hoping for a happier ending!

Related links

Here are some interesting articles I encountered while doing my research:

Captivate-JavaScript limitations

Captivate SWFs can communicate with the host HTML file via JavaScript, but the scripting options suffer from severe limitations imposed by the Captivate authoring environment.

For starters, this communication is (generally) a one-way street: the JavaScript goes out of Captivate to the HTML file, but the JavaScript in the HTML can’t really ‘talk’ to the Captivate SWF.

Here’s a simple example of a Captivate SWF calling a function located in the HTML host. Once the JavaScript in the HTML file receives the function call, it executes it. In this case, the function identify() will manipulate DOM elements to display a message. [ Download the source files. ]

The ability to send JavaScript calls from Captivate SWFs should open up a number of possibilities, even if we’re limited to one-way calls. A few ideas off the top of my head:

  • Use JavaScript to track a user’s choices or actions while interacting with a scenario-based (branching) Captivate SWF.
  • Use JavaScript to control the SWF’s navigation.
  • Use JavaScript to relay SWF data (such as current slide number, clicked items, etc.) to the HTML container for an AJAX-like user experience.

However, Captivate’s JavaScript capabilities suffer from three severe limitations:

  1. The aforementioned one-way-street issue
  2. Only static hard-coded calls can be made
  3. JavaScript can only be executed in limited circumstances, and at the expense of other Captivate functionality

The first item is pretty obvious, so I won’t go into it here.

The second item is absolutely painful. Captivate’s JavaScript capabilities are limited to static hard-coded calls; Captivate doesn’t allow dynamic JavaScript variables, such as a dynamic call based on the slide number. This eliminates the ability to use easy-to-maintain dynamic code, which means you’d have to spend hours hand-coding every little JavaScript call in the SWF. This can get very complex very quickly, and is a royal pain in the buttocks.

Matter-of-fact, Captivate makes it hard to do ANY kind of dynamic scripting, whether it’s JavaScript or Actionscript; Actionscript is the native language for Captivate SWFs, but is strangely — and strictly — off-limits to Captivate users.

(I don’t know what the folks at Macromedia/Adobe were thinking when they implemented such poor scripting functionality into Captivate… they have a proud history of making their applications scriptable — Authorware, Director, and Flash were all astounding successes — but they really dropped the ball with Captivate.)

The third item is when/where JavaScript can be executed from Captivate. Captivate’s developers took an odd approach and decided that JavaScript can be executed:

  • When something is clicked
  • At the end of a slide
  • At the end of the movie

To complicate things further, only ONE action can be taken per event. This means you can either choose to execute JavaScript, OR you can jump to another slide OR you can send e-mail, etc. If you want to execute a single line of JavaScript, you will be forced to give up any navigation option other than allowing the movie to progress to the next slide (playing the SWF linearly).

This eliminates the ability to use JavaScript when working with branching or other non-linear navigation in Captivate, unless you implement crazy workarounds, such as building empty slides that execute JavaScript a split second before transitioning to the intended slide.

Summary

When I started this journal entry, I had no intention of writing a rant. However, while trying to come up with useful examples of Captivate-JavaScript functionality, I was reminded just how ugly Captivate’s JavaScript support is. C’mon Adobe… you’ve created Actionscript 3.0, Flex, Flash, Acrobat, Acrobat Connect, Apollo, the Spry framework, and more… why can’t you give us a little more control over JavaScript and/or Actionscript in Captivate? It’s peanuts in comparison to your other projects!

Thoughts on using JavaScript in Adobe Captivate

Having just finished my Making Actionscript calls from Adobe Captivate tutorial, I’ve been looking at Captivate 2.0 a lot the last few days. Specifically, I’ve been looking for ways to use JavaScript in Captivate. I’m a bit disappointed to report that JavaScript can only be used in very limited instances.

From what I can gather, JavaScript calls can be made in the following instances:

  1. At the end of a slide
  2. At the end of the movie
  3. When a clickbox or button is clicked
  4. When a text input field is used

“That sounds like plenty of ways to use JavaScript,” you say? Well, the major shortcoming is this: if you choose to execute JavaScript in any of those cases, you’re giving up the ability to use the other ‘Navigation’ options, such as ‘go to next slide,’ ‘go to previous slide,’ ‘jump to slide,’ ‘open URL or file,’ and ‘open other project.’

Navigation options in Captivate

As you can see in the image above, Captivate only allows you to choose ONE ‘navigation’ option. You can’t execute JavaScript and jump to a slide.

In some cases this may be fine, but what if your Captivate movie is a scenario with branching? A button click or other interaction must occur to tell the movie to jump to the appropriate scenario slide. If you choose to execute JavaScript instead of using a ‘jump to slide’ action, your scenario is toast… you will not be able to navigate to any slide other than the next slide in the timeline.

Johnny doesn’t play well with others

The bottom line is that Captivate is being developed as a stand-alone solution, and is not really meant to integrate with any other course development tools.

A Captivate SWF can be embedded in an HTML-based course interface, but doing so will render the built-in interaction tracking system practically useless. The tracking system is designed to deliver data in a limited, pre-packaged capacity; there is no obvious way to access to the raw tracking data. For instance, if you want to get SCORM calls such as lesson_location from the Captivate SWF, there’s no clear, easy solution for doing so. And there’s no way to set up the Captivate file as a component of a SCORM course… it’s meant to be a be-all-end-all solution (for example it will do ‘LMS initialize’ calls, which is a no-no in the middle of a course).

A Captivate SWF can be loaded into a Flash-based course interface, but because there is no easy access to the Captivate SWF’s Actionscript code, it’s a huge challenge — though not impossible — to extract Actionscript variables from a Captivate SWF. This includes tasks such as controlling the Captivate SWF’s playback using your own Flash playback controls; The sheer number of Captivate ‘help’ sites dedicated to this normally simple exercise is proof enough.

Have you ever tried decompiling a Captivate-generated SWF and inspecting its Actionscript? It’s very enlightening and utterly confusing! (FYI SoThink SWF Decompiler has a limited trial version you can use if you’re curious).

And NO, asking us to export the Captivate movies as FLAs and then customizing in Flash is NOT an appropriate solution. Big chunks of functionality and settings get lost in the conversion, and you can’t send the FLA back to Captivate. It’s a path of no return. The whole point of Captivate was to make things quick and easy and avoid having to do heavy lifting in Flash.

In many ways, Captivate is a great product. It’s the best software I’ve ever used for creating software simulations, and the SWFs it creates are much smaller than video-based tools such as Camtasia.

However, until the Captivate design team starts acknowledging the needs of course developers who use Captivate as a small part of their development toolbox, we will be stuck pulling our hair out and spending hours on end searching for workarounds. And that kinda sucks, don’t you think?

Making Actionscript calls from Adobe Captivate

Diagram of languages spoken by each document

Captivate 2.0 doesn’t include the ability directly manipulate Actionscript. This has been problematic for people like myself who have Flash-based ‘players’ that load and unload both Captivate SWFs and Flash SWFs… we often need the Captivate SWF to perform some kind of action when it reaches its end.

In my case, I usually need the loaded Captivate SWF to tell the Flash container — an e-learning course interface — that a simulation has been successfully completed.

The figure on the right illustrates the problem: Captivate only allows developers to make Javascript calls, even though Captivate SWFs are running Actionscript under-the-hood!

Flash SWFs, on the other hand, traditionally use Actionscript. Granted, there’s a long history of Flash-Javascript tricks, but most of them have been very ‘hacky’ and not very robust.

The HTML container doesn’t recognize Actionscript at all; it only speaks Javascript.

The old solution: embedded SWFs

Diagram of languages spoken by each document (includes embedded SWF)

For a long time, the only reliable workaround had been to import a Flash SWF containing Actionscript into Captivate, and carefully place it on a Captivate slide. It works like this:

  1. Create a blank Flash SWF, then type a small amount of Actionscript into a frame script (such as calling function “doSomething()” on frame 1).
  2. In order for the imported SWF’s Actionscript to properly communicate with the Flash container, it must be prefixed with “_root.”; this corrects a scope issue and forces the Captivate movie to send the Actionscript call to its parent. The full line of Actionscript in the blank Flash SWF looks like this:
    _root.doSomething();
  3. The container SWF must contain the corresponding function. Something like this:
    
    function doSomething() {
       //do something
    }
    
  4. When the Captivate SWF’s play head reaches the frame containing the embedded SWF, any Actionscript contained in that embedded SWF is executed.

Click here to see a functioning sample. You can download a ZIP containing the source files here.

This is a simple solution, but it suffers from scope creep and is difficult to maintain. Who really wants to import a SWF every time they need to make an Actionscript call? I sure don’t. And if you ever want to change your Actionscript code, you need to re-export the SWF from Flash, and update the imported SWF in Captivate. Yuck.

Isn’t there a better way?

What about Javascript?

I got to thinking about it, and wondered (again) “Isn’t there a better way to do this?” Then I remembered two things: That Captivate supports Javascript calls, and that SWFObject — arguably the best way to embed Flash SWFs onto a web page — supports passing Javascript variables to a Flash SWF. What if I could make the Captivate SWF communicate with the Flash container SWF via Javascript and SWFObject?

Sad to say, no dice! SWFObject only passes variables when the Flash container SWF loads. I need to pass variables at various times, not just at ‘onload.’

Even though SWFObject was quickly shot down as a communication method, it got me thinking about Javascript as an alternative. Versions of Flash prior to Flash 8 could handle small snippets of Javascript via GetURL, but the GetURL hack is clumsy at best. I knew I didn’t want to go that route. But hang on a minute… what about Flash 8’s ExternalInterface class? It was designed to make Javascript-to-Actionscript communication a breeze! Having never really used it — it’s only supported in Flash Player 8+ and we’re still stuck supporting Flash Player 7 at the office — I decided to give it a whirl. I quickly created this example using Adobe’s tutorial [link no longer available]. First thoughts: Very intriguing and easy to get up and running, but can this be applied to Captivate SWFs loaded into Flash?

The short answer is… YES!

The new solution: Flash’s ExternalInterface class (Actionscript 2.0)

First I laid out the objectives for my experiment:

  • Make the Captivate SWF call the Actionscript function unload() (located in the Flash SWF) via Javascript
  • No variables would be passed… I wanted to keep things as simple as possible

I began editing the code from the aforementioned Adobe example, and in a few short minutes my proof-of-concept was up and running! I was amazed at how quick and easy it was… ExternalInterface was much easier to use than I anticipated.

Here’s what I did:

Step one: Add two lines of Actionscript code to the Flash container


import flash.external.ExternalInterface;
ExternalInterface.addCallback("unload", this, unloadSWF);

Step two: Add two small Javascript functions to the HTML page


//Detect Flash container movie
function getFlashMovie(movieName) {
   if (navigator.appName.indexOf("Microsoft") != -1) {
      return window[movieName];
   }
   else {
      return document[movieName];
   }
}

//Function to be called by Captivate
function captivateUnload() {
   //Calls "unload" method established in ExternalInterface Actionscript code
   getFlashMovie("simpleSwfLoader").unload();
}

Update: In my subsequent testing, using document.getElementById() works fine in place of the getFlashMovie() function. While some developers warn against using getElementById for grabbing Flash SWFs, my tests — using SWFObject as the embed method — were successful in the following environments: WinXP (FF2, FF3, IE6, IE7, Safari 3) and Mac OS X (Safari 3, FF3, Opera 9.5). You can still use getFlashMovie if you prefer.

Step three: Add the Javascript call captivateUnload() to the Captivate button.

That’s it! Really simple, huh? I must admit I was expecting it to be much more complicated.

How it works

As I mentioned earlier, I’m no expert on the ExternalInterface class, but I’ll try and explain what’s going on here. Let’s work backwards and start with the Javascript call in Captivate. Here are the source files if you’d like to use them.

The Captivate file

Captivate Button OptionsIn my example, I’m using a button to call a Javascript function named captivateUnload(). This Javascript function is located in the HTML file.

The HTML file

Moving to the HTML file, we see two functions. The first function, getFlashMovie(movieName), returns the Flash SWF as a Javascript object. This allows us to add functions to the SWF object. This is critical because Flash’s ExternalInterface class will listen for functions attached to the SWF object.

In my example, the Javascript function captivateUnload() attaches the Javascript function unload() to a SWF named simpleSwfLoader:


function captivateUnload() {
   getFlashMovie(simpleSwfLoader).unload();
}

By itself, this script won’t do much. In fact, it will throw an error in your browser if it doesn’t detect the accompanying ExternalInterface Actionscript code in your Flash SWF.

The Flash file

In the Flash file, the first line of Actionscript simply tells Flash to import the ExternalInterface class.

import flash.external.ExternalInterface;

The second line is where the action’s at (no pun intended… well, maybe a little!).


ExternalInterface.addCallback("unload", this, unloadSWF);

ExternalInterface’s addCallback method basically tells Actionscript to start listening for Javascript functions that have been attached to the SWF object. In our scenario, the unload() function has been attached to the SWF object.

The three parameters ("unload", this, unloadSWF) specify exactly what to listen for (a function named “unload”), where to listen (“this” SWF), and what to do when the SWF hears the correct bit o’ Javascript (execute a function named “unloadSWF”).

So for this example, the Flash SWF will execute function unloadSWF() when it hears Javascript call a function named unload() that is attached to a SWF named simpleSwfLoader.

Got all that? I know it sounds confusing (it still makes my head spin a little), but it’s actually pretty straightforward. Here’s a diagram for you visual learners:

Flow of scripting calls

And that’s the whole enchilada.

Let me remind you that this is a very simple example, and I’m sure this technique can be harnessed to do much more! Also, this example uses ExternalInterface for Actionscript 2.0. Flash CS3’s new Actionscript 3.0 uses slightly different code, but the concept is the same.

If any of you find this tutorial useful, or think of new ways to utilize this technique, please let me know by adding a comment.

Thanks, and good luck!

Embedded SWF technique source files  |  ExternalInterface technique source files

I have seen the future…

…and it still kinda creeps me out. I’m referring specifically to the Adobe-Macromedia merger. While both companies have a history of making excellent products, Adobe seems to have lost its way the last few years, and I’m worried it will drag the Macromedia product line down with it. I’m especially dismayed about the future of competing products: Freehand versus Illustrator, Fireworks versus ImageReady, Dreamweaver versus GoLive, Flash versus LiveMotion, FlashPaper versus Acrobat. Adobe’s programs (with a few exceptions) have become massive bloatware… try installing CreativeSuite 2 sometime — it takes up over 2GB of hard drive space!

Will Adobe force its interface standards onto Macromedia products? Personally, I prefer Macromedia’s “docked” interface style to Adobe’s tab system… it’s easier to manage and more efficient for my workflow. Will Adobe force the PDF format into all of the programs? (The answer is yes… they’re even planning on integrating PDFs with Breeze.)

So what’s my point? I’m concerned — like MANY others — about the future of both Adobe and Macromedia product lines because of how it will affect the work I do. Which brings me to a Bruce Chizen (Adobe head honcho) interview I just read. While he doesn’t address some of my concerns, he does give a pretty thorough overview of Adobe’s plans for the near future. This will potentially affect anyone who uses Office-style software, online services, and mobile devices, so I thought I’d post a link for you to read. Enjoy! 🙂

http://knowledge.wharton.upenn.edu/index.cfm?fa=viewArticle&id=1399

– philip