addFrameScript method of MovieClip class

Many animations can now be coded, so do not need frames. This means we do not have to associate “pre-filled” symbols in the Library with MovieClips, but can instead associate them with the Sprite class or with our own custom subclasses which extend that class.

Sometimes, however, we do need to use the timeline, so must export assets extending the MovieClip class. We can still avoid littering code down the timeline, though, by using the undocumented MovieClip method addFrameScript() in the contructor function of our class. This will insert code into the timeline as if it were on the frame, but it keeps everything tidy instead, inside the class file. It’s therefore highly useful (especially for stopping clips on their last frame!).

Example:

// imports
import flash.display.MovieClip;

// constructor
public function MyAnimation extends MovieClip()
{
   addFrameScript(totalFrames-1, finish);
}

// new method
private function finish():void
{
   trace("stopping on last frame!";)
   stop();
}

Note that the frame counting is zero-based (as with arrays): frame 1 is considered by Flash to be frame 0, so we use an offset.

Free preloader

View the preloader

In this post we release a Flash preloader component (an SWC file) which is suitable to be dropped on the first frame of any Flash movie and it’ll preload that timeline with precision.

(Click the image on the left to see the preloader in action.)

Preloader (1701 downloads)

There are a few notable things about this preloader which make it worth using:

  • It waits .2 of a second then checks whether more than 95% of the movie has already loaded. If it has, the loader aborts – you never even see it.
  • When it starts preloading it doesn’t include data already loaded (for example, itself). So, it doesn’t jump to 10% immediately. It preloads remaining data, always starting at 0%.
  • It will always centre itself on your stage and remain centred if the stage is resized.
  • It’s animated nicely and drops in and out of view.
  • It smoothly tweens between (even small) data chunks, rather than jumping.
  • It has appropriate pauses during its operations.
  • Its colour is configurable via the Component Inspector.
  • You can also decide via the Component Inspector whether or not the timeline it is loading should fade in once the load is complete.

How to install the component

This component uses ActionScript 3 so requires at least Flash Player 9. To use it you’ll need to have a copy of Flash CS3, CS4 or CS5 and place the Preloader folder (with the Preloader.swc inside it) into this directory or its equivalent on your system:

C:\Users\{User}\AppData\Local\Adobe\Flash CS4\en\Configuration\Components

How to use the component in the IDE

To use the component without writing any ActionScript at all, restart Flash or choose “Reload” from the submenu on your Components panel, and you should see the preloader in there.

Then, when you wish to add it to a project, make sure the first frame of the project is blank and drag the component from the Components panel onto the stage. It doesn’t need an instance name. You can then go to your Component Inspector and set its two properties.

Then just export your movie – try a Test Movie then hit ctrl-Enter again to simulate download.

How to use the component in pure ActionScript

If you wish to add the component using code instead, you will first need to drag it from the Components panel into your Library (as before), then your Document class will need to look something like this:

package {

 import flash.display.MovieClip;
 // Need to import the class..
 import com.orlandmedia.utils.preloader.Preloader;

 // Document class extends MovieClip..
 public class Main extends MovieClip {

  // I'm using a Singleton pattern here for the Document class
  private static var _instance:Main;

  // This'll hold the preloader..
  public var preloader:Preloader;

  // Constructor function
  public function Main()
  {
   if (_instance)
   {
    throw new Error("One instance exists! Please access via Main.getInstance()");
   } else {
    _instance = this;
    initialise();
   }
  }

  public function initialise():void
  {
   // This is not to do with the preloader, but stops a 2 frame movie on the *second* frame after the preloader has finished, as a means of stopping it looping
   addFrameScript(1, stop);
   // This instantiates the preloader - note parentheses are not needed when not passing any parameters. Preloader inherits from Sprite.
   preloader = new Preloader;
   // This optional line sets the colour of the preload bar - in this case to red
   preloader.colour = 0xFF0000;
   // This adds the preloader to the Display List - it'll do the rest
   addChild(preloader);
  }

  // Part of the Singleton pattern
  public static function get instance():Main
  {
   return _instance;
  }

 }

}

We have not yet implemented Live Preview for the component on the stage (showing the colour)  but may do so in the future.

This component is issued with the MIT license and you’re welcome to use it in your own projects both personal and professional. It utilises Jack Doyle’s excellent TweenLite library.

How to use localToGlobal() in ActionScript 3.0

localToGlobal() in AS3 not working? The LiveDocs on this potentially very useful method, along with its partner globalToLocal() are not actually very helpful. What they don’t make clear is that you need to be sure to overwrite your point when using it:

// WRONG:
var pt:Point = new Point(target.x, target.y);
target.parent.localToGlobal(pt);
parent.globalToLocal(pt);

// RIGHT:
var pt:Point = new Point(target.x, target.y);
pt = target.parent.localToGlobal(pt);
pt = parent.globalToLocal(pt);

Simply running a localToGlobal() method on a point in a given scope is not enough. You need to write the result back into the point. You might be forgiven for not seeing this, because it was not necessary for the equivalent method in AS2!

localToGlobal() and globalToLocal() can be the source of considerable frustration, especially as differences such as this are not mentioned by Adobe. But properly understood these methods are not complicated.

Choosing a Flash video format for pseudostreaming

Introduction

StarburstWe’ve been spending a lot of time recently examining the different video codecs available for Flash and conducting various tests. Our objective has been to choose the best quality video format which also enables pseudostreaming – without using custom servers such as Lighttpd.

Pseudostreaming means imitating the behaviour of a RTMP streaming server such as Red5 or Flash Media Server by simply using a PHP script such as xmoov.php. Using such a solution does not enable collaborative behaviour such as the broadcasting of live webcam streams, but it does allow jumping ahead to parts of a video which have not yet downloaded. This is probably the benefit most people seek from a streaming server solution and it is much easier to set up than a streaming server. It is the choice of YouTube and Google Video.

What’s required for pseudostreaming?

Pseudostreaming requires a video format which supports it and a video player which supports it too. Two leading open source video players, JW Player and Flowplayer both support it, because they send a request indicating how much of the video file should be sent back.

Flowplayer requires a plugin SWF. This player is configured using JSON. JSON is not especially complicated but we found this config method fairly verbose. It also uses its own flash embed routine. You have to get the config exactly right for pseudostreaming via xmoov to work with Flowplayer. There are scarce resources on the web. The main one points to this page, which is now outdated, but it can be done, and if you need to know how, ask us.

We find JW Player easier to configure for pseudostreaming with xmoov. The info page is here and after we pointed out a fault with the player, here, the latest release works properly with pseudostreaming.

Comparing video formats

Perhaps we will use JW Player as our player, then, but we’re still no closer to deciding which video format to use.

H.264 & F4V

It seems safe to say that the best quality format for the web at the moment is the H.264 standard. We are seeking to encode video using Adobe Media Encoder, which is included with Creative Suite 4. This program uses the MainConcept H.264 Video codec and using this codec it can encode into either MP4 format or F4V format. At least Flash Player 9.0.115.0 is required to play back this standard.

F4V was created by Adobe in order to address shortcomings in the FLV format. It determines additional file meta data and enables the embedding of H.264 video in this custom format.

At the time of writing, according to our research, you can forget about using H.264 for pseudostreaming unless you have shell access to your Linux server and wish to try compiling a PHP module like this or you are using lighttpd as your web server.

Neither Flowplayer nor JW Player support pseudostreaming of either F4V or MP4 videos using the H.264 standard along with xmoov.php.

Eric Lorenzo Benjamin Jr of xMoovStream also offers his own video player, which looks good, and a separate streaming server solution. This can stream MP4 (H.264) files to Apple’s iPhone and Quicktime, but not to Flash Player. We’re still limited to FLVs with this player.

F4V problems

A little more about F4Vs while we are on the topic: the format currently appears to be faulty – at least when encoded by Adobe Media Encoder CS4:

  • It does not add keyframes at navigation cue point times
  • It does not trigger cue point event listeners in the expected manner
  • It does not respect custom keyframe intervals

Some more reasons not to use this format yet, perhaps, despite Adobe having urged everybody to switch to it. If you wish to have high quality video without xmoov pseudostreaming, use MP4 instead for now. (Under our tests, MP4s embed seekpoints correctly whereas F4Vs do not.)

We’re left only with trusty old FLV as the format we can pseudostream.

Setting up FLVs for pseudostreaming

Choosing a codec

You have a choice of two different codecs when exporting FLVs using Adobe Media Encoder: Sorenson Spark or On2 VP6. The latter is the later, recommended, codec.

Choosing a keyframe interval

Notice the option to “Set Keyframe Distance” in the Export Settings under Video → Advanced Settings. When using pseudostreaming the player can only jump to the closest keyframe. The more keyframes, the more precise your pseudostreaming will be, but the larger your file will be.

The default is a keyframe interval of 30. If you take this down to 10 you will see a lot more precision and about a 17% rise in file size. If you take it down to 1, though, you will see a 1000% rise in file size along with your optimal precision. You will also find that the video player takes a long time reading the meta data of the FLV before it plays it. We are happy with the compromise of a keyframe interval of 10.

So you export the video using whatever quality you like, and this keyframe interval. You stream it into your player using referring to the config links above. You try seeking to a part of the video which has not yet downloaded. The playhead simply jumps back to the beginning of the video. What’s going on?

Inserting meta data

Well, it’s not enough to have the keyframes in the video. What Adobe Media Encoder unfortunately does not do is insert meta data in the FLV indicating where those frames are. This is vital for php pseudostreaming to work. (Adobe: please provide the option for this in the next release of AME.)

Luckily, there is quite a dated freeware tool from Buraks, of the ActionScript Viewer, which still does the job well: FLVMDI. There is even a GUI available so you don’t need to start working on the command line, and another tool enabling you to view the meta data without tracing it out using Flash.

Once you have exported your FLV file with the ideal keyframe interval, you’ll need to run it through FLVMDI to insert the meta data. Be sure to tick the “Include ‘keyframes’ object” option. It performs this task quickly, then, when you use the file with JWPlayer and xmoov, you should find you can move the playhead to any location and the file will “stream” from there.

Conclusion

The conclusions to be drawn at this point, we believe, are as follows:

  • F4V is a no-go area as there are problems with the format.
  • MP4 (H.264) is the optimal format if you do not require pseudostreaming via xmoov.
  • FLV remains the best format to use if you do wish to pseudostream using these players.

Since pseudostreaming is highly desirable, then, and xmoov is a quick and easy solution, we find FLV to be still the best format.

We hope this article helps get you up and running with Flash video including pseudostreaming. If you spot any problems with it do let us know, and if the article proves popular we may expand on it and insert relevant images.

Passing parameters to a MouseEvent listener

Problem:

Here is how we construct a MouseEvent handler in ActionScript 3.0:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   navigateToURL(new URLRequest("http://www.mysite.com"), "_blank");
}

Note that we set the fourth parameter, useWeakReference, to true. This ensures that if this listener is the the only reference to the myButton remaining, myButton can still be garbage collected if required.

The trouble with this is that every time myButton is clicked, the same URL will open. We can’t easily pass a parameter to the function which is acting as a listener. How can the same function be used by many buttons to open a different URL for each button? How can we pass a parameter?

Solution 1: Custom events (won’t work):

It would be nice if we could dispatch a custom event on mouse click. Custom events extend the event class enabling us to dispatch an event with additional properties (like a URL). This event is received by the handler then the property is accessed. The trouble is this approach won’t work here because the Flash Player automatically dispatches MouseEvents, we don’t dispatch them ourselves. (If anyone sees a way this may be done, please comment below.)

Solution 2: Embed the function call (not advised):

An interesting strategy is used here, whereby a call to a function is used as the event handler, and this returns the function to use, including the desired parameter. The trouble with this approach is that it is not really good form, in that there is no way we can reference the handler if we did want to remove it using removeEventListener().

We’re beginning to see that there is actually no way to “pass” a parameter, really. We have to rather detect the button-specific value by “association”. The following solutions all achieve this by using the MouseEvent’s target property:

Solution 3: Use a switch statement on the event’s target property

When the button is clicked, providing there is no further mouse-enabled object inside it, that button is the event’s target. We can say it dispatched the event. We can access the target object that dispatched the event using a property of the event itself:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   trace(e.target + "dispatched this event!");
   navigateToURL(new URLRequest("http://www.mysite.com"), "_blank");
}

If we can access the target we can decide what to do in the handler depending on the target:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   var u:URLRequest = new URLRequest;

   switch(e.target)
   {
      case myButton:
         u.url = "http://www.mysite.com";
         break;

      case myOtherButton:
         u.url = "http://www.myothersite.com";
         break;

      default:
         throw new Error("No URL set for " + e.target);
         return;
   }

   navigateToURL(u, "_blank");
}

This works well.

Solution 4: Use a public custom property inside the dispatching object

We could avoid a switch statement and instead have a property inside the dispatching class which can then be detected. So if our button extends Sprite, let’s say, we could have an instance property in there called url and we could detect that, as follows:

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:Event):void
{
   var u:URLRequest = new URLRequest;
   navigateToURL(new URLRequest(e.target.url), "_blank");
}

This is fine too, but we might need to throw an error if the object dispatching the event doesn’t have a url property.

Solution 5: Use a dictionary object to track variables

Dictionary objects in ActionScript are similar to associative arrays but they can accept complex objects, as opposed to strings, as their keys. After creating our button we could push a reference to it into a dictionary and specify a value to associate with it, like this:

var dict:Dictionary = new Dictionary(true);  // use weak references to the objects
var myButton:Sprite = new Sprite;
dict[myButton] = "http://www.mysite.com";
addChild(myButton);

myButton.addEventListener(MouseEvent.CLICK, openURL, false, 0, true);

private function openURL(e:MouseEvent):void
{
   navigateToURL(new URLRequest(dict[e.target]), "_blank");
}

This is perfectly workable too.

Solutions 3, 4 and 5 all seem equally good solutions to this problem. There is just one final option, which is to avoid the problem altogether by using separate listeners for each button. But that, of course, is what we were trying to solve in the first place.