A really common problem that shows up in a lot of the Flash apps I build is the need to load external files and use them in multiple places. For Bitmaps, that’s not such a big deal. Copying the bitmap data and generating a new bitmap is a one line task. Things get more complex when you try to do the same thing with SWFs, though.
You can’t clone a SWF because you can’t clone a MovieClip in AS3. The problem is actually due to the Shape class. Unlike most of the other core display classes, Shape does not have a clone method. With a bit of recursion, you can duplicate most complex objects, but without Shape things hit a brick wall.
Depending on the type of SWF I’m loading, I’ve found a few silly ways around the problem. If the SWF is just a still image, you can do a bitmap clone in much the same way as you’d handle a normal Bitmap. You lose the ability to scale the vector information smoothly, but sometimes that’s okay. If you have more control of the source SWFs, you can build your assets into Library symbols with some linkage information, then instantiate as you need them. This has been my preferred method in the past as it allows you to perform only one external load, and you can control each instance as a fully vector, fully functional MovieClip object. Sometimes, though, you just don’t have the access and control needed to pull off that method. That’s where this other solution comes in.
Enter DuplicateLoader, another handy utility class from yours truly. This class loads your external SWF as a ByteArray, keeps a reference to it, and then as you need an instance, it processes that ByteArray through a Loader and voila, presto-chango, MovieClip! Simple right? Lets take a look.
?package org.tomasino.display
{
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.SecurityErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.ErrorEvent;
import flash.events.IOErrorEvent;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
public class DuplicateLoader extends EventDispatcher
{
private var _byteLoader:URLLoader;
private var _request:URLRequest;
private var _instances:Array;
private var _application:ApplicationDomain = new ApplicationDomain ();
private var _context:LoaderContext = new LoaderContext ( false, _application );
public function DuplicateLoader ( url:String = null )
{
if (url) load (url);
}
public function load ( url:String ):void
{
// Cleanup
destroy ();
// New Instances
_instances = new Array();
_byteLoader = new URLLoader ();
_byteLoader.dataFormat = URLLoaderDataFormat.BINARY;
// Listeners
_byteLoader.addEventListener ( Event.COMPLETE, onBytesLoaded );
_byteLoader.addEventListener ( SecurityErrorEvent.SECURITY_ERROR, onError );
_byteLoader.addEventListener ( IOErrorEvent.IO_ERROR, onError );
try
{
_request = new URLRequest ( url );
_byteLoader.load ( _request );
}
catch (e:Error)
{
trace (e);
}
}
public function convert ():void
{
if (_byteLoader && _byteLoader.data)
{
var converter:Loader = new Loader ();
converter.contentLoaderInfo.addEventListener (Event.COMPLETE, onConvert, false, 0, true);
try
{
converter.loadBytes ( _byteLoader.data , _context);
}
catch (e:Error)
{
trace (e);
}
}
else
{
var e:ErrorEvent = new ErrorEvent ( ErrorEvent.ERROR, false, false, 'No data available to convert');
dispatchEvent (e);
}
}
public function getInstance ():DisplayObject
{
var returnInst:DisplayObject;
if (_instances && _instances.length)
{
returnInst = _instances.shift();
}
return returnInst;
}
public function destroy ():void
{
// Remove any orphaned instances before loading a new byte-array
if (_instances && _instances.length)
{
while (_instances.length)
{
_instances[0] = null;
_instances.shift ();
}
}
_instances = null;
_byteLoader = null;
_request = null;
}
/*
* Event Handling
*/
private function onError (event:ErrorEvent):void
{
_byteLoader.removeEventListener ( Event.COMPLETE, onBytesLoaded );
_byteLoader.removeEventListener ( SecurityErrorEvent.SECURITY_ERROR, onError );
_byteLoader.removeEventListener ( IOErrorEvent.IO_ERROR, onError );
var e:ErrorEvent = new ErrorEvent ( ErrorEvent.ERROR, false, false, event.text);
dispatchEvent (e);
}
private function onBytesLoaded (event:Event):void
{
_byteLoader.removeEventListener ( Event.COMPLETE, onBytesLoaded );
_byteLoader.removeEventListener ( SecurityErrorEvent.SECURITY_ERROR, onError );
_byteLoader.removeEventListener ( IOErrorEvent.IO_ERROR, onError );
var e:Event = new Event ( Event.COMPLETE );
dispatchEvent ( e );
}
private function onConvert (event:Event):void
{
var loaderInfo:LoaderInfo = event.target as LoaderInfo;
var converter:Loader = loaderInfo.loader as Loader;
converter.removeEventListener ( Event.COMPLETE, onConvert, false );
_instances.push ( converter.content );
converter = null;
var e:Event = new Event ( Event.CHANGE );
dispatchEvent ( e );
}
}
}
There are two main segments to the class. The first is the load() method that grabs your external content and loads it up into the ByteArray using a URLLoader. It’s pretty self-explanatory. Following that process, we need to convert the ByteArray into a usable Flash DisplayObject. This type of decode operation is best left to the folks at Adobe. They’ve written some wonderful magic into the Loader class that lets us pass in just about anything to a loadBytes() method and get back a useful object.
Calling the convert() method tells the class that you’d like a new instance of your SWF to be made available. You might be asking, “Why can’t I just use a getter and grab an instance right away?” If you were asking that, kudos. I was asking the same thing myself. The short answer is, Adobe sucks. The long answer is, the Loader class loadBytes() method is asynchronous only. Stupid, right? Right.
If that’s something that annoys you as much as it annoys me, please feel free to vote for change on the Flash Bug Tracker. Maybe we’ll start getting methods with the option to perform the operation synchronously or asynchronously. A simple change like that would allow me to stop making wordy blog posts like this.
Back to the class at hand. We were talking about the convert() method and how it will tell the class to make a new instance available. You can call this guy over and over and over to your heart’s content. Each time you do so, it preps a new instance and stores it. Once the instance is ready, DuplicateLoader fires off a CHANGE event to let you know things have been converted. Finally, you can hit the getInstance() method and get back the handy instance you’ve always wanted.
Now, a few internal notes. DuplicateLoader loads all SWFs into their own LoaderContext to avoid collisions and avoid a nasty security hole left by loadBytes. Also, as soon as you getInstance(), the class gets rid of its reference to that instance. The idea behind this was, when you are done with the SWF, you should be able to just delete it yourself. If I were maintaining a reference in my class as well, poor ol’ garbage collector would never know it was okay to delete it. Also, if there’s an error anywhere in the class, I grab the messages and dispatch them to a nice generic ErrorEvent to simplify event handling. Too many listeners make my head hurt.
If you’d like to see the class in action, here is a sample project that shows it in action. As always, the class is free to use, rip apart, call names, drunk-dial, or whatever floats your boat. I’m always happy to hear your comments and see projects where you’ve found my code useful. Enjoy!
Special thanks to Kristine McDermott for pointing my head in the right direction on this one. She’s such a smarty.
Update: Please make sure to get the latest version of this code from my github repository.
I was wondering. Could you not just do following? :
package
{
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.net.URLRequest;
/**
* …
* @author York Gibson
*/
public class LoadIt extends MovieClip
{
public function LoadIt() {
var l:Loader = new Loader();
var r:URLRequest = new URLRequest(“loadme.swf”)
l.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete)
l.load(r)
};
private function loadComplete(e:Event):void
{
// instanciate one
var aLoadMe:* = new e.target.content.constructor()
addChild(aLoadMe)
// instanciate another
var anotherLoadMe:* = new e.target.content.constructor()
anotherLoadMe.x = 100;
addChild(anotherLoadMe)
}
}
}
Sorry. I just realised, if you don’t have control over the source of the loaded flash movies, you can’t give them a document class. Without a document class you would, of course, just get MovieClip for the constructor – not very helpful.
However if you do have the ability to specify document classes in your swf’s then this works pretty well I think.
You’re totally right. If we can control the SWF content being loaded, we can do things that are much smarter than having to reprocess a byte-array. It is, as you pointed out, a workaround for the problem that arises when we don’t have that level of control. Even so, it’s not perfect, but it can certainly get the job done in a pinch.
just what I was looking for….was gonna write my own but, deadline calls. Respect!
Hey there.. need to load an AS2/AVM1 swf into a Flex3.2/AS3/AVM2 app… want to load once over wire/net… then duplicate and use a few instances in my app. Only thing is loaded AVM1 swf is parameterized with flash vars/qs params.. params are consumed by AVM1 swf for purposes of setting up local connection.. each needs diff local connection names as params, etc. Get where I am going? Need to pass different params in for each new instance! Any help anyone?
Thanks so much!
Outstanding piece of work.
I have been working towards this for 2 weeks and i wasnt happy with the byte array method because it was so messy. You have cleared up all of the issues and wrapped it into a nice class.
Thanks very much.
Clark.
[...] AS3 Duplicate Loaded SWF | Tomasino Blog – [...]
Thank you so very much! This is just what I needed! It was driving me crazy that there was nothing in AS3 to help me out with this problem. I thought there would be more posts and solutions about this. Anywho, thanks again for a great piece of work.
Cheers!
Thank you so much for posting this solution, James. I’m working on a project which is comprised of nothing but external PNGs and SWFs, some of which need to be cloned but still controlled and still with access to all their internal elements. The PNGs I had no problem with, but the SWF cloning was driving me nuts. Your solution is incredibly elegant and works like a charm!! Saved me many many hours of work. You, sir, are a gentleman and a scholar. Cheers!! Niilo.
@Niilo – Thanks for the compliments; glad I could help! I still cross my fingers that one day Adobe will add a synchronous loader so I can perfect the class.