Untitled Document

Most Flash developers are familiar with using fscommand() or the following technique to invoke a JavaScript function from a Flash movie:

myButton.onRelease = function(){
  getURL("javascript:alert('Hello from JavaScript');");
}

But what about the opposite? How do you communicate with Flash from an HTML page using JavaScript? You could use the technique described in this Flash Technote, but it doesn't work on all browsers and platforms (notably Firefox).

At the time of this writing — Flash Player 7 — there is no consistently reliable method for talking to Flash with JavaScript. But there is a workaround. In this article, you'll learn how to use FlashVars and the LocalConnection class to send messages to a Flash movie embedded in an HTML document. This technique should work on virtually every browser and platform, provided the user hasn't disabled JavaScript.

Controlling Flash Video Playback with JavaScript

Below is a screenshot of the application we're going to create:


Figure 1  The completed application with debugging enabled (view demo)

When we're finished, we'll be able to select videos using an HTML drop-down menu, and control the playback of the current video using HTML buttons (view demo). To pull this off, we have to assemble the following assets, which are included in this article's support files:

To obtain the completed versions of these files, click Download Support Files at the bottom of this page.

How It Works

Before we start creating the application, let's examine how it works. When the user clicks the Help button in Figure 1, the following events occur:

  1. The JavaScript sendToFlash() function is invoked in the HTML document (demo.htm):

    sendToFlash('showDialog', 'Please choose a video.,Help Dialog');

  2. The function name, and its parameters, are passed to the hidden Flash agent (agent.swf) using FlashVars:

    fo.addParam("flashvars", "functionName=" + functionName + "&params=" + params);

  3. The agent movie is loaded (or reloaded) into its DIV element by FlashObject:

    fo.write("agent");

  4. When the agent movie loads, it creates a local connection to the target movie (main.swf) and invokes its getData method, passing it the same function name and parameters from Step 1. For example, in the following code, the value of functionName is "showDialog", and the value of args is an array containing two strings: "Please choose a video." and "Help Dialog".

    lc.send("myConn", "getData", functionName, args);

  5. When the getData method is invoked in main.swf, the Function.apply method invokes the requested function (e.g., showDialog) along with its parameters, if any.

While this might seem like an arduous workaround, it all happens very quickly (agent.swf is only 500 bytes in size). Nevertheless, this approach may not be ideal for "twitch" applications that require ultra-fast response times, such as Flash games.

Creating the HTML Document (demo.htm)

Launch Dreamweaver or your favorite text editor and create a new HTML document. Insert the following code:

<html>
<head>
<title>Talking to Flash with JavaScript</title>
</head>

<body>
<h3>Talking to Flash with JavaScript</h3>
<form name="myForm" action="">
<p>Select a video from the drop-down menu:</p>
<p>
  <select name="videoMenu"
onChange="sendToFlash('playVideo', this.options[this.selectedIndex].value);">
    <option value="That's not a video!" selected>Choose Video</option>
    <option value="http://www.macromedia.com/devnet/mx/flash/
articles/vidtemplate_vidshowcase/Video/ATV.flv">ATV</option>
    <option value="http://www.communitymx.com/content/source/
FCBFF/videos/brahms.flv">Brahms</option>
    <option value="http://www.communitymx.com/content/source/
FCBFF/videos/respighi.flv">Respighi</option>
    <option value="http://www.macromedia.com/devnet/mx/flash/
articles/vidtemplate_vidshowcase/Video/Skiing.flv">Skiing</option>
  </select>
</p>
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<input type="button" value="Play" onClick="sendToFlash('playVideo');">
&nbsp;&nbsp;
<input type="button" value="Pause" onClick="sendToFlash('pauseVideo');">
&nbsp;&nbsp;
<input type="button" value="Stop" onClick="sendToFlash('stopVideo');">
&nbsp;&nbsp;
<input type="button" value="Help"
onClick="sendToFlash('showDialog', 'Please choose a video.,Help Dialog');">
</form>
</body>
</html>

Listing 1  At this point, the HTML document contains only a form, a select menu, and four HTML buttons

Save the document as demo.htm.

Embedding the Flash Movies

To embed the two Flash movies — main.swf and agent.swf — we're going to use FlashObject. Created by Geoff Stearns, FlashObject is an external JavaScript file that dynamically replaces DIV elements with Flash embed code. You can add this code manually, or you can download and install the CMX Insert Flash Object extension (requires Dreamweaver MX or higher).

If you're using Dreamweaver, switch to Code view and insert the following code after the select menu (and before the <br /> tag):

<div id="main">
  <!-- this appears if user doesn't have JavaScript enabled -->
  <strong>Please enable JavaScript in your browser to view this content</strong>.
</div>
<script type="text/javascript">
  // <![CDATA[

  var fo = new FlashObject("main.swf", "flashcontent", "320", "240", 7, "#0000ff");
  // this appears if user doesn't have the required Flash Player version
  fo.altTxt = "Please upgrade to the latest version of
<a href='http://www.macromedia.com/go/getflashplayer/'
target='blank'>Flash Player</a>.";
  fo.bypassTxt = "<p><a href='?detectflash=false&" + fo.sq + "'>
Click here</a> if you already have Flash Player " +   fo.version + " installed.</p>";
  fo.write("main");

  // ]]>
</script>

Listing 2  The main Flash movie (main.swf) is embedded in a DIV element called main

Next, insert the following code after the closing </form> tag:

<!-- This is the agent.swf that sends FlashVars to main.swf -->
<div id="agent"></div>

Listing 3   The "headless SWF" (agent.swf) is embedded in a DIV element called agent

Finally, insert the following code within the document's <head> tags:

<!-- FlashObject embed by Geoff Stearns geoff@deconcept.com http://blog.deconcept.com/ -->
<script type="text/javascript" language="JavaScript" src="inc/flashobject.js"></script>

Listing 4  The external JavaScript file, flashobject.js, resides in a folder called inc

Save your work.

The external JavaScript file, flashobject.js, is included with this article's support files. If you're not using the CMX Insert FlashObject extension, copy the inc folder from the support files into the folder that contains demo.htm. If you run into any problems, compare your document with the completed version of demo.htm.

Writing the JavaScript Function (sendToFlash)

We're almost done with demo.htm. At this point, we've defined two DIV elements — main and agent — for our Flash movies. All that's missing is the JavaScript sendToFlash() function that's invoked by the select menu and the four buttons (see Listing 1).

Insert the following code into the <head> of demo.htm:

<script type="text/javascript" language="JavaScript">
<!--
// change this false to hide debugger
var debug = true;

function sendToFlash(functionName, params){
  if(debug){
    var fo = new FlashObject("agent.swf", "agentSWF", "400", "200", 7, "#cccccc");
  }
  else{
    var fo = new FlashObject("agent.swf", "agentSWF", "10", "10", 7, "#0000ff");
    fo.addParam("wmode", "transparent");
  }

  if(params){
    fo.addParam("flashvars", "functionName=" + functionName
    + "&params=" + params + "&debug=" + debug);
  }
  else{
    fo.addParam("flashvars", "functionName=" + functionName + "&debug=" + debug);
  }
  fo.write("agent");
}
//-->
</script>

Listing 5  The JavaScript function, sendToFlash(), sends variables to agent.swf using FlashVars

The preceding code takes advantage of a nifty feature of FlashObject. Right now, the "headless SWF" (agent.swf) isn't loaded in demo.htm.

<div id="agent"></div>

To load it, we call the FlashObject.write() method and pass it the id of the target DIV as a parameter:

fo.write("agent");

In other words, every time sendToFlash() is invoked, FlashObject reloads agent.swf and passes it new parameters using FlashVars.

fo.addParam("flashvars", "functionName=" + functionName + "&debug=" + debug);

If debug is set to true in sendToFlash(), the dimensions of agent.swf are 400x200 pixels:

var fo = new FlashObject("agent.swf", "agentSWF", "400", "200", 7, "#cccccc");

If debug is set to false, agent.swf is hidden using the wmode parameter:

fo.addParam("wmode", "transparent");

For more on wmode, see "Flash, DHTML Menus and Accessibility" by Stephanie Sullivan and the following Flash TechNote.

Testing the Demo Page

To test the demo page, copy main.swf and agent.swf from the support files into the folder that contains demo.htm. Open demo.htm in a browser and select a video from the drop-down menu. After a brief pause, it should start playing.

TIP: When testing demo.htm, make sure not to have more than one instance of the page open in a browser.

Click the Play, Pause, Stop, and Help buttons. You should see the name of the ActionScript function, along with its parameters (if any), displayed in agent.swf. To hide the debugger, change debug to false (see Listing 5) and reload the page.

Notice that you can send multiple parameters to an ActionScript function by separating them with a comma. In the following example, Please choose a video. is the msg parameter, and Help Dialog is the title parameter of the showDialog() function:

onClick="sendToFlash('showDialog', 'Please choose a video.,Help Dialog');"

In the next two sections, we'll examine the source code of the Flash movies: main.swf and agent.swf.

The Video Player (main.swf)

If you open main.fla in Flash MX 2004, you'll see that it's very simple. The entire Flash movie consists of a MediaDisplay component on the stage, an Alert component in the library, and an include statement on frame 1 of the actions layer:

#include "main.as"

This is the source of main.as, which is included in the article support files:

import mx.controls.Alert;

// --------------------------------------------------
// Timeline variables
// --------------------------------------------------

var owner:MovieClip = this;
var time:Number = 0;
var myAlert:Alert;

// --------------------------------------------------
// LocalConnection object
// --------------------------------------------------

// receiving SWF
var lc:LocalConnection = new LocalConnection();
lc.getData = function(functionName:String, params:Array):Void{
  owner[functionName].apply(this, params);
}
lc.connect("myConn");

// --------------------------------------------------
// MediaDisplay listener
// --------------------------------------------------

function onVideoStart():Void{
  // hide Alert using PopUpManager.deletePopUp()
  myAlert.deletePopUp();
}
myVideo.addEventListener("start", onVideoStart);

// --------------------------------------------------
// Functions
// --------------------------------------------------

function showDialog(msg:String, title:String):Void{
  myAlert = Alert.show(msg, title);
}

function setMedia(path:String):Void{
  myVideo.contentPath = path;
}

function playVideo(path:String):Void{
  if(arguments.length == 1){
    showDialog("Loading video...", "Please wait");
    stopVideo();
    setMedia(path);
  }
  myVideo.play(time);
}

function pauseVideo():Void{
  myVideo.pause();
  time = myVideo.playheadTime;
}

function stopVideo():Void{
  myVideo.stop();
  time = 0;
}

Listing 6  The source of the Flash video player (main.as)

The ActionScript consists of four functions to control the MediaDisplay component (myVideo), plus a showDialog() function to display the Alert component. The Alert.show() method returns a reference to the Alert component, which is stored in the myAlert timeline variable. When the video starts, the Alert instance is hidden using the PopUpManager class.

NOTE: If you have a high-speed Internet connection, you'll hardly notice the Alert component when you make a selection from the drop-down menu.

The LocalConnection class enables agent.swf and main.swf to send and receive data over a common connection (myConn). In this application, both SWFs happen to be in the same HTML document, but this is not a requirement. In fact, if you've ever used Central's Debug panel, LocalConnection is what enables your trace() statements to appear in another Flash movie.

The following code creates an instance of LocalConnection called lc. When agent.swf sends data to main.swf, it specifies getData as the method to execute in main.swf (see Listing 8).

// receiving SWF
var lc:LocalConnection = new LocalConnection();
lc.getData = function(functionName:String, params:Array):Void{
  owner[functionName].apply(this, params);
}
lc.connect("myConn");

Listing 7  Using the LocalConnection.connect method to connect to myConn

When lc.getData is called in main.swf, the Function.apply method invokes the specified function (the functionName argument) with its parameters (the params argument). Obviously, this is much easier than using a switch/case statement to invoke a different function based on the functionName parameter. The other advantage of Function.apply is that it works "when the number of parameters to be passed is not known until the script actually executes."

The Agent (agent.swf)

The following is the source of agent.as, which is included on frame 1 of the actions layer of agent.fla:

// FlashVars
var functionName:String;
var params:String;
var bDebug:Boolean = (debug == "true");

var args:Array = params.split(",");
output_txt.text = "";
output_txt._visible = bDebug;

// --------------------------------------------------
// LocalConnection object
// --------------------------------------------------

// sending SWF file
var lc:LocalConnection = new LocalConnection();
lc.onStatus = function(info:Object):Void{
  if(bDebug){
    switch(info.level){
      case "status": output_txt.text += "Connected to target SWF.\n\n"; break;
      case "error": output_txt.text += "Unable to connect to target SWF.\n\n";
    }

    output_txt.text += "function: \t\"" + functionName + "\"\n";
    for(var i:Number=0; i<args.length; i++){
      output_txt.text += "param " + (i+1) + ": \t\"" + args[i] + "\"\n";
    }
  }
}
lc.send("myConn", "getData", functionName, args);

Listing 8   The source of the "headless SWF" or agent (agent.as)

The preceding code uses two different LocalConnection methods: LocalConnection.onStatus and LocalConnection.send. The lc.onStatus method is an event that's triggered after calling lc.send. If a successful connection is made to the receiving SWF — in this case, main.swf — the info.level property is "status". If the connection is unsuccessful, the info.level value is "error." In Listing 8, the returned value is used to display feedback if the user has enabled "debug" mode, and to output the function name and its arguments to a text field (output_txt).

You should be able to reuse agent.swf in your applications without any modifications. The only requirement is that you add the code from Listing 7, along with the owner Timeline variable, to your receiving SWF.

Conclusion

This article only scratches the surface of what's possible using this technique. For instance, you could create a hidden Flash MP3 player controlled by image buttons in an HTML document. With a little effort, you could even add file upload support to Flash via an HTML pop-up window.

If you have any questions or comments, please use the feedback form below.

Approximate download size: 424k