Understanding Basic Flash Panorama Player Plugin Architecture

This is the first part of a multi part series of tutorials written by Zephyr Renner and Patrick Cheatham that will look at some of the advanced functionality a user can produce by getting beyond what can be accomplished with Flash Panorama Player's XML configuration files, and getting into programming Actionscript (AS3).

There are a number of ways to create Actionscript based interaction with FPP, and primary amongst them is with a plugin, so that is where we are going to start.

To make your own plugin by following this tutorial, you could just copy and paste the below code into the Actionscript pane on a new layer in Flash CS3. And since this is AS3, you will need Flash CS3 (or later). With a few slight modifications this can be refactored for compilation with the free, open source Flex SDK provided by Adobe, but for now we are focusing on designers using Flash.

Below is the basic architecture of a plugin, enabling your plugin to register itself with FPP, and to interact with your pano and its hotspots. Take a look at it and then we will dissect it block by block. Later tutorials will cover hotspot events in depth, getting and setting values/attributes of hotspots, and how to let your hotspots talk to the plugin directly.

All the tutorials in this series assume that you have at least a basic understanding of things such as variables, arrays, and general AS3 syntax -- though we'll try to be clear enough that all users can get the idea.

///////////// REQUIRED VARIABLES

var id:String="descriptionbox";
var version:String="1.0";
var panoController:Object=null;
var pano:Object=null;
var hotspots:Object;
var waitTimer:Timer;

/////////////  FUNCTIONS CALLED BY FPP

// this function executes when FPP loads the plug-in we use it to set up a timer to wait for everything else to load and be ready for interactivity:
function init (panoMain:Object) {
        if (panoMain.addExternal(this)) {
                panoController = panoMain;
                waitTimer = new Timer(50);
                waitTimer.addEventListener(TimerEvent.TIMER, waitHotspots, false, 0, true);
                waitTimer.start();
        }
}

// this function is called whenever a new panorama is loaded
function newPano (link:Object) {
        pano = link;
}

// this function handles XML parameters.  It is called when FPP is first started and passed the parameters from the plugin's node in the XML if it exists:
function newParams (str:String) {
        // a little RegExp pattern matching is good for you:
        var lines:Array = str.match(/[^\s\n\r]+[^\n\r]+/g);
        for (var i:int=0;i<lines.length;i++) {
                var pair:Array = lines[i].split(/[\s]*=[\s]*/);
                if (pair[2] != null){
                        for (var j:Number=2; j<pair.length; j++){
                                pair[1] = pair[1].concat("=",pair[j]);
                        }
                }
                setAttribute(pair[0], pair[1], null, null, null, null, 0 );
        }
}

var params:Object = new Object();

// this function receives calls commands in the XML that start with external, e.g. "external.descriptionbox.mode=...":
function setAttribute (name:String, value:String, time:String=null, type:String=null, onDoneFunction:String=null, onInterruptFunction:String=null, relative:int=0 ) {
        params[name] = value;

}

// this function is called by the timer when hotspots.swf is loaded and defines the hotspots variable:
function waitHotspots (event:Event) {
        if (panoController.externals.hotspots!=null) {
                // wait for XML parsing
                if (panoController.externals.hotspots.ready) {
                        waitTimer.stop();
                        waitTimer = null
                        hotspots = panoController.externals.hotspots;
                }
        }
}

// this function is called by FPP when the plugin is unloaded (which is not often, and generally only when you ask it to be):
function remove () {

}
/////////////////END

So let's explain this piece by piece.

var id:String="descriptionbox";
var version:String="1.0";
var panoController:Object=null;
var pano:Object=null;
var hotspots:Object;
var waitTimer:Timer;

The id and version variables are printed in the Flash trace file. The id variable identifies the plugin to FPP so that commands such as external.descriptionbox.p=q will be directed to the correct plugin. panoController will contain a reference to FPP itself, and pano will contain a reference to the current panorama. hotspots will contain a reference to FPP's hotspots plugin. And waitTimer is just a little timer object we will use to watch for hotspots.swf to finish loading.

Not much to see here, moving along:

function init (panoController:Object) {
        if (panoMain.addExternal(this)) {
                this.panoController = panoController;
                waitTimer = new Timer(50);
                waitTimer.addEventListener(TimerEvent.TIMER, waitHotspots, false, 0, true);
                waitTimer.start();
        }
}

The init function is the first function that will be executed as soon as FPP loads our plugin, and it receives a reference to FPP (panoController). First it checks to see that FPP has registered the plugin. Then it defines panoController within the scope of the plugin, and then it sets up the timer to check every 50 ms whether hotspots.swf has is loaded and ready. Each 50 ms the waitHotspots function will be called, and it will contain the code to check and see if hotspots has loaded. This function could be slightly modified and the entire plugin pasted into an embedPano style wrapper to create a pseudo-plugin to the end of registering the wrapper as a plugin so that it can receive name and value communiques from hotspots.swf. This will be the subject of a later tutorial.

function newPano (pano:Object) {
        this.pano = pano;
}

newPano is the function that will be called whenever a new panorama is loaded. It will be called once in the beginning when the first pano loads and then whenever a loadPano(...) command is executed in hotspots.swf. newPano is passed a link to the pano object, and will define the pano variable for the scope of the plugin. newPano can be useful as a substitute for figuring out how to put an event listener in hotspots that will tell you when a new pano is loading.

function newParams (str:String) {
        var lines:Array = str.match(/[^\s\n\r]+[^\n\r]+/g);
        for (var i:int=0;i<lines.length;i++) {
                var pair:Array = lines[i].split(/[\s]*=[\s]*/);
                if (pair[2] != null){
                        for (var j:Number=2; j<pair.length; j++){
                                pair[1] = pair[1].concat("=",pair[j]);
                        }
                }
                setAttribute(pair[0], pair[1], null, null, null, null, 0 );
        }
}

The newParams function is called right after the plugin is registered, and it is passed the XML node bearing the plugin's name, if it exists, in the XML. code>newParams then parses the whole string it receives from the XML, breaking it on line breaks and then on the first "=" in each line; it will then send the name and value pairs on to the setAttribute function for further use.

var params:Object = new Object();
function setAttribute (name:String, value:String, time:String=null, type:String=null, onDoneFunction:String=null, onInterruptFunction:String=null, relative:int=0 ) {
        params[name] = value;
}

The setAttribute function has two roles: 1, as the receiver of the name/value pairs from the newParams function, and 2, as the receiver of name/value pairs from commands in hotspots that are directed towards our plugin (a la external. descriptionbox). All setAttribute does is add the name/value pairs into an empty object called params; params stores them for later reference in the scope of the plugin. So they will always be accessible in the plugin with a call like params["name"], which will return the value half of the name/value pair.

So when, for example, there is a hotspot with onClick="external.plugin_name.name=value", this function will receive the string's name and value and store them in the params object. This function can be modified to further use the name/value pairs as they arrive, to call other actionscript functions in the plugin. This will be the subject of a later tutorial.

function waitHotspots (event:Event) {
        if (panoController.externals.hotspots!=null) {
                // wait for XML parsing
                if (panoController.externals.hotspots.ready) {
                        waitTimer.stop();
                        waitTimer = null
                        hotspots = panoController.externals.hotspots;
                        // this is a good place to call a function of your own that starts doing whatever your plugin should do.
                        // myFunction();
                }
        }
}

The waitHotspots function is called by the timer and simply checks to see if hotspots.swf has loaded; it then defines hotspots in the scope of the plugin.

Once hotspots has been defined, everything that you need to work with in your plugin should be loaded and ready to go. This is an excellent place to call a new function of your own that starts doing the work of your plugin. In most cases this is the single most important aspect of this whole tuturial, since you can use the plugin architecture without modification, and it is only if you are doing something unusual that you might need to edit the actual functions themselves. We will cover making a plugin actually do something in another tutorial.

function remove () {

}

The remove function is called by FPP if you ever unload your plugin. In normal usage, this doesn't happen until the user closes down the browser -- in which case the entire flash plugin has its memory cleaned out by the operating system, making this function is superfluous. However, in cases where you wish to attempt to dynamically load and unload plugins, it is of paramount importance that you clear out objects you have created in your plugin, and remove any event listeners you may have placed.

Good places to look for reference are the various of the open source plugins supplied with FPP, like glassmeter.fla.

This tutorial should have given you the knowledge to recognize the basic architecture of the plugin, and to start to understand how a plugin does what it does.

Cheers!

Zephyr & Patrick

Excellent! Now some questions...

Zephyr,

This tutorial is great! Definitely learning a thing or two or ten from you, even about functions I've used in my own projects. Now a couple questions to better understand:

1)"panoController will contain a reference to FPP itself..."

Just wondering if it "contains" anything else of use or interest. Or might it be just as correct to say, "panoController refers (or points) to FPP itself, and pano points (or refers) to the current panorama."

2) waitTimer: When is it necessary to use this and when not? I notice that Denis' plugins don't, at least the ones I've looked at.

3) waitTimer: When you are finished with the waitTimer should you remove its eventListener? I get the impression that is considered best practice but I'm not sure how much difference it makes. Or does "waitTimer = null" effectively do this?

4) Timer (ms): It may not matter in this case, but it appears that timer events actually only fire once per frame if my observations are correct. On a timer sensitive event like measuring the playhead position of a sound that can make a big difference. You can change the frames per second for a SWF, but will the plugin automatically take on the fps of FPP or will it run at the pace you set when creating it? Are there any sync problems if the fps of your plugin and FPP aren't the same?

5) function remove: It may be superfluous in a plugin, but if you run a SWF as a hotspot (or is that what you mean by "load dynamically"?) it becomes critically important. If you don't remove eventListeners it can really mess up the hotspot the next time you load (or reload) it.

In the case of a plugin, when you load a new XML along with a new pano, the plugin stays put but init and newParams runs again, if I understand correctly. In fact, the whole plugin runs anew as it did when first loaded I think. If so wouldn't you really want to remove eventListeners lest they be reloaded? I believe function remove fires when changing XML

MORE! We need MORE! Seriously, you have done a great service with this tutorial and I can't wait for the next installment. I can read through Denis' code, even follow it blindly, but I'm often left scratching my head about why something is done and how best to use or modify it. Your tutorial has gone a long way toward making it all clear(er). THANKS!

re: FPP plugin tutorial / frame rate

Hey Scott:

I'll let Z answer your questions, but:

Quote:

4) Timer (ms): It may not matter in this case, but it appears that timer events actually only fire once per frame if my observations are correct. On a timer sensitive event like measuring the playhead position of a sound that can make a big difference. You can change the frames per second for a SWF, but will the plugin automatically take on the fps of FPP or will it run at the pace you set when creating it? Are there any sync problems if the fps of your plugin and FPP aren't the same?

* Any SWF which you load in will be beholden to the root-most container's Frame Rate (in most cases). If you load in a SWF which contains a Sound whose parameter is set to "stream", though, the parent SWF will adopt the frame rate of the sound.

* In AS3 you can use Stage.frameRate to programmatically alter the frame rate. If you have a generic SWF which you load in to FPP, and it must play at a specific frame rate, you might look into using this. I am unsure how this will affect FPP itself though. Test in an inconspicuous area. Wink

* Event.ENTER_FRAME ostensibly fires once per frame at whatever the current frame rate is. Likewise, Timer will fire off at whatever interval (ms) you specify. Both of these events, though, are best-case estimations. Actual firing times are going to vary depending on how much work Flash is doing (processor), which can alter the amount of resources Flash gives to your events; So, you can end up with dropped frames or functions that take 6 seconds to do something versus the programmed 4.5 seconds (for example).

* Loading in streamed video and/or sounds, Flash should play them at their native frame rate; if you're going to be using a lot of sound or video, you might try to create your plugins (or use Stage.frameRate) with a base frame rate equivalent to video (24 fps approximately).

HTH!

framerate: some clarity -- with egg on my face

Thanks, Patrick. That certainly helps, especially knowing that everything will run at the stage fps however it gets set. I still wasn't sure about timer events firing only once per frame, though. Couldn't find anything clear on the net either so I delved into it further myself.

And? Boy, do I feel embarrassed. Timer events apparently have little or no relation to frame events. I eventually proved this by setting frame rate to 1 fps (and making sure of it with a enter frame trace) My timer events -- including such things as monitoring playhead position and fading sound -- executed more or less on schedule. I did find that setting a timer event to 100ms is only a rough approximation. My events fired anywhere from 90ms to 175ms even with the CPU running only 1-2%. It seems to try averaging to 100ms although not that well. That lack of precision, which accumulated to differences of 200ms to 500ms, is apparently what I was seeing, not some frame vs. timer mismatch.

(I'll just keep telling myself, "We learn more through mistakes than success".)

Hi Scott, Answers are inline

Hi Scott,

Answers are inline below

1)"panoController will contain a reference to FPP itself..."

Just wondering if it "contains" anything else of use or interest. Or might it be just as correct to say, "panoController refers (or points) to FPP itself, and pano points (or refers) to the current panorama."

Z: It most certainly does contain other things of interest since it is the parent of everything that FPP loads, and so you can get to everything from there, if you know the address. Since the API of FPP is largely undocumented, it is the knowing of the address that is hard, and requires digging through the internals with trace( describeType(object_of_interest)). Practically speaking you probably won't use it very often, since there are other shorter ways of getting most places. And yes, pano is the pano, which is a child of panoController.

2) waitTimer: When is it necessary to use this and when not? I notice that Denis' plugins don't, at least the ones I've looked at.

Z:Any time your plugin needs access to anything pertaining to hotspots, which is very often, you are advised to use this, because if you don't you often end up trying to access hotspots.swf before it is ready to use, and get errors.

3) waitTimer: When you are finished with the waitTimer should you remove its eventListener? I get the impression that is considered best practice but I'm not sure how much difference it makes. Or does "waitTimer = null" effectively do this?

Z: Yeah, you probably should do that. waitTimer = null does not affect the event listener.

4) Timer (ms): It may not matter in this case, but it appears that timer events actually only fire once per frame if my observations are correct. On a timer sensitive event like measuring the playhead position of a sound that can make a big difference. You can change the frames per second for a SWF, but will the plugin automatically take on the fps of FPP or will it run at the pace you set when creating it? Are there any sync problems if the fps of your plugin and FPP aren't the same?

Z: There is only one frame rate across the flash environment, and I believe Flash defers to the parent frame rate, so your plugins frame rate is going to be ignored. There will only be problems if you need your plugin to go at a different frame rate.

5) function remove: It may be superfluous in a plugin, but if you run a SWF as a hotspot (or is that what you mean by "load dynamically"?) it becomes critically important. If you don't remove eventListeners it can really mess up the hotspot the next time you load (or reload) it.

Z: If you run a swf as a hotspot, it is NOT a plugin. At least not in my "dictionary". I believe Denis refers to hotspot swf's as "smart plugins". And basically none of this information applies, except in the most general way to a "smart plugin".

In the case of a plugin, when you load a new XML along with a new pano, the plugin stays put but init and newParams runs again, if I understand correctly. In fact, the whole plugin runs anew as it did when first loaded I think. If so wouldn't you really want to remove eventListeners lest they be reloaded? I believe function remove fires when changing XML

Z: hmmm.... Now I don't know the answer to that one. If it is the case, the remove function should probably be paid more attention to.... Anyone know the answer to this one?

I believe function remove

I believe function remove fires when changing XML

Z: hmmm.... Now I don't know the answer to that one. If it is the case, the remove function should probably be paid more attention to.... Anyone know the answer to this one?

I do... Now.

It seems to only fire for "smart plugins" not regular ones. I put a trace into the remove section of your tooltips plugin and that never appeared in the Flash Tracer output. Not when changing XML nor when exiting FPP. Unless I made some mistake.

Next I traced the remove function for a "smart plugin" I created. That fires when changing XML. Still doesn't appear on closing FPP.

Here is my confusion: The remove function is fired by a command from FPP. I assumed FPP just issues a remove command blindly and any swf with that function will execute it. If so, why doesn't it fire the remove function in the plugin? It seems FPP keeps an internal list of smart plugins running and issues a remove command specifically to them.

re: remove()

Quote:

Here is my confusion: The remove function is fired by a command from FPP. I assumed FPP just issues a remove command blindly and any swf with that function will execute it. If so, why doesn't it fire the remove function in the plugin? It seems FPP keeps an internal list of smart plugins running and issues a remove command specifically to them.

Scott:

I believe that remove() is called by FPP with a scope only of hotspots.

In, for example, the glassMeter.fla example supplied with FPP, the glassMeter's Actionscript manually calls remove() once the panorama has finished loading.

So, you can call remove() yourself from a plugin -- or in the case of a hotspot, allow FPP to call it for you. Obviously, you can put whatever code you want in remove().

I think this is by design -- so that hotspots can be removed, when the user loads an entirely new set of <hotspots/> in new XML; and so that plugins are merely restarted rather than reloaded with new XML.

?

hi there you mentioned a

hi there you mentioned a tutorial on creating your own plugin with hotspots or something have you done this one yet as i can not find it and could do with it.

kind regards Noel

re: basic FPP plugin architecture

Hey Noel:

Using the code above in the tutorial, in waitHotspots you could put something like:

hotspots = panoController.externals.hotspots;

hotspots.stage.addEventListener(Event.RESIZE, myResizeFunction);

hotspots.addEventListener(MouseEvent.MOUSE_OVER, myOverFunction, false, 0, true);
hotspots.addEventListener(MouseEvent.MOUSE_OUT, myOutFunction, false, 0, true);
hotspots.addEventListener(MouseEvent.CLICK, myClickFunction, false, 0, true);

And then add your function(s), similar to:

function myOverFunction(e:MouseEvent):void {

trace("My name is: " + e.target.id);
trace("My url is: " + e.target.url);

}

HTH!

Run a function in the XML file from the plugin ?

Hello,
Thank you for this very usefull tutorial.

I'd like to run a function "loadpano02" wich is inside the tag in the xml file from the plugin.

Part of my xml file:

<?xml version = '1.0'?>
<panorama>
<parameters>
        panoName = panos/test0001
        panoType = cylinder
        layer_2 = files/hotspots.swf
        layer_5 = files/cylConverter.swf
</parameters>
        <hotspots>
                <global
                        loadpano01="loadPano(panoName=panos/test0001&pan=0&tilt=0&zoom=0.80 &panoType=cylinder,500,none);"
                        loadpano02="loadPano(panoName=panos/test0002&pan=0&tilt=0&zoom=0.80 &panoType=cylinder,500,none);"
                >

                </global>
        </hotspots>
</panorama>

Is it possible ?
If yes, what syntax should i use ?

Regards,
Dimitri Lekien

How to run a function in the XML file via your FPP plugin

Hi there!

Following the example above, in your plugin/FLA, you can put a line similar to:

hotspots.execute("global.loadpano01")

Or:

hotspots.execute("loadPano(panoName=panos/test0001&pan=0&tilt=0&zoom=0.80 &panoType=cylinder,500,none)")

--
If you want to add an event listener, to listen for clicks from your FPP hotspots, you can:

// this function is called by the timer when hotspots.swf is loaded and defines the hotspots variable:
function waitHotspots (event:Event) {
        if (panoController.externals.hotspots!=null) {
                // wait for XML parsing
                if (panoController.externals.hotspots.ready) {
                        waitTimer.stop();
                        waitTimer = null
                        hotspots = panoController.externals.hotspots;
                         hotspots.addEventListener(MouseEvent.CLICK, doClick, false, 0, true);
                }
        }
}

function doClick(event:Event) {
        var obj:Object = event.target;
        while (!obj.hasOwnProperty("attributes")) {
                if (obj.hasOwnProperty("parent")) {
                        obj = obj.parent;
                } else {
                        return;
                }
        }

        if (obj.attributes.getParam("id")) {
                var AttributeText:String = obj.attributes.getParam("id");
                if (AttributeText!=null || AttributeText != "") {

                        if (AttributeText.toLowerCase() == "myID") {
                           hotspots.execute("loadPano(panoName=panos/test0001&pan=0&tilt=0&zoom=0.80 &panoType=cylinder,500,none)")                            
                        }

                }

        }
}

HTH!

Cheers,

Patrick

where can I find the src of this project

This comment has been moved here.

found it and posting it for prosperity

This comment has been moved here.