Categories Displayed in Flash

Archive for September, 2007

Ignite MAX: I’m Speaking!

OOOohh new badges, we don't need no stinking badges!!!
I'm Speaking at Ignite

I'm one of the few, the proud, the presenting! Should be fun: 5 minutes, 20 slides on fast 15 second autoplay. I'll be discussing how to bridge the Designer, Coder, Manager gaps, using LOLCats whenever possible. Woo hoo!!!

First time at Max, you'd think after near a decade in working with flash I could get my butt to more than one. Hopefully next year I'll have better material and can present something finished.

If your going ping me (troy ta troyworks tod com) if your interested in meeting.

Flash on the Desktop: Zinc vrs Air vrs Director vrs F-in-Box

MDM Multimedia put there, with their comparison of AIR and Zinc, and I It's accurate but I feel an incomplete pitch. AIR doesn't really expect much more users than they currently expect with the flash plugin, and they expect much less from developers working in AIR, than Zinc or other lanaguages.

OTHER POINTS FOR AIR

  • Integrated with Flex and Flash production tools. 1 step publish. Zinc, F-in-box, Director have many more steps and over the course of a project it adds up.
  • integrated PDF and HTML and Javascript, with cross language communication.
  • has a SQL database built into the runtime, no need to setup one.
  • very quick installers and install from web.
  • it can load/play anything flash can play, and will support the same codec as quictime, which shortly will probably represent 80% of the video on the web, youtube is already streaming their content for iPhone consumption.

The first two I think are very important. It opens up desktop development to web developers which has normally been an different tier of, and it does it much faster than Java or C+ workflows. Within minutes and only a paragraph of code one can create simple tools for text and image manipulation. Flash is great, but it's not necessarily ideal for text or layout heavy docs (e.g. tables). It can also be hard to source talent for. HTML on the other hand most kids in junior high can code.
F-in-Box's key points are, ability to stream movies and FLV from memory...nothing ever hits the disk. Write in Delphi, C, etc. But this requires someone skilled in one of those 'hard' languages, familiar with memory management.
Director is the lowest of my list at this stage as it has not kept up, but it has 3d support and has had stable cross platform desktop access for years. I think it's weak point are windowing, it's primarily kiosk mode or nothing.

Audio-only FLV

Despite working with it for years, today learned something new: 1) FLV can be used in an audio only mode 2) FLV can support ADPCM, uncompressed, NellyMoser and of course Mp3, that flash can load dynamically. While most aren't that common, there are times when it's better. For one, Mp3 licensing can be expensive.
http://en.wikipedia.org/wiki/Flash_Video

Maybe even output PCM samples from dynamic sound generation?
http://www.flashbrighton.org/wordpress/?p=9

DRM, MetaData and swfs.

DRM in flash can be tricky, how is it you identify and pass id's to a swf duplicated a thousand times to know where it goes?
1) A temp approach is using server generated FlashVars in AS3, good for session ID's
http://blogs.adobe.com/pdehaan/2006/07/using_flashvars_with_actionscr.html
2) First not visible to actionscript is the metadata tag, preferrably writing it on th fly:

http://tutorials.lastashero.com/2005/10/using_swf_metadata_in_flash_8.html (via the IDE)

The metadata can easily be retrieved by some search engine swf plugins, it's actually what it was designed for.
There is a $30 dollar utility to batch do this to your swfs:

http://www.polarswf.com/

3) Accessible to actionscript is  rewriting of variables in the swf This is useful when root vars aren't an option, and or license keys (e.g. encrypted tokens) need to be embedded into the swf.
http://neurofuzzy.net/index.php?p=25

Both 2 and 3 can be accomplished via AS3 and ByteArray. Though I haven't tried it yet.

AS3 Error in ExternalInterface when passed null.

In AS3, calling ExternalInterface with a null value ends up tossing an error, presumeably the conversion to XML is missing a closing bracket somewhere.

TypeError: Error #1085: The element type "empty" must be terminated by the matching end-tag "".
at flash.external::ExternalInterface$/call()

What I did to workaround is create a ExternalInterfaceUtil Class that has a few static functions like:

public static function call3(param1:Object = null,  param2:Object = null, param3:Object = null):Object{

if(ExternalInterface.available){

if(param1 == null || param2 == null || param3 == null){

throw Error("cannot accept null argument");

}else{

return ExternalInterface.call(param1, param2, param3);

}
}

Which is also useful to keep calls from throwing errors when run in environements that don't support ExternalInterface (like flash projectors).

AVM2Loader Converting AVM1 to AVM2

I had the need to load up Flash7 swfs no script in an AS3.0+Flash9 application. Without the ability to edit the swf to say use LocalConnection as other sites suggest, as they are output from a video2swf convertor.
There was some hope of converting byteCode since the file is so simple, and the raw data isn't the same. I discovered AVM2Loader through ActionScript Classes, which got it from Fladdict who apparently was linkiing to the file on a snippets site with Trac, and that is/was down or no longer working. So I'm copying it here incase others need to find it.
While it helps, In testing it's very limited, which is true in general of AVM1Movie. I wanted to load Flash7 files as output by Turbine that contain PCM audio and no graphics for use with an AS3/Flash9 based UI. Sadly no dice. With or without AVM2Loader, the audio clip stilll shows up as an AVM1Movie and can be loaded/replayed 2 times before it stops working, despite all loading events being sent. This latter seems like a bug, as no matter what I try once it's loaded it won't play reliably by loading it again, trying to remove it prior gives caller must be child of Display object or something.

AVM2Loader does work with simple graphics, put a tween on stage...they appear as MovieClip in the AVM2 so can be added to the displayList and controlled with play(), stop() etc, but somehow audio packets throw off the translation. In testing it bypasses one of the 'hackA' segment, and the byteHeader is different, changing it to to match didn't help.

I wish I knew more about the swf file format(s). Working in hex is painful for me, feels like I'm starting a tire treads when I should be driving.

AVM2Loader.as

package {
import flash.display.Loader;
import flash.events.*;
import flash.net.*;
import flash.system.LoaderContext;
import flash.utils.ByteArray;
import flash.utils.Endian;

/**
* Loads both of AVM1 and AVM2 swf as AVM2.
*/
public class AVM2Loader extends Loader {
private var _urlLoader:URLLoader;
private var _context:LoaderContext;

/**
* loads both of AVM1 and AVM2 movie as AVM2 movie.
*/
override public function load(request:URLRequest,context:LoaderContext=null):void {
_context=context;

_urlLoader=new URLLoader ;
_urlLoader.dataFormat=URLLoaderDataFormat.BINARY;
_urlLoader.addEventListener(Event.COMPLETE,_binaryLoaded,false,0,true);
_urlLoader.addEventListener(IOErrorEvent.IO_ERROR,_transferEvent,false,0,true);
_urlLoader.addEventListener(ProgressEvent.PROGRESS,_transferEvent,false,0,true);
_urlLoader.addEventListener(Event.OPEN,_transferEvent,false,0,true);
_urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS,_transferEvent,false,0,true);
_urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,_transferEvent,false,0,true);
_urlLoader.load(request);
}
private function _binaryLoaded(e:Event):void {
loadBytes(ByteArray(_urlLoader.data),_context);
_urlLoader=null;
}
private function _transferEvent(e:Event):void {
dispatchEvent(e);
}
/**
* loads both of AVM1 and AVM2 movie as AVM2 movie.
*/
override public function loadBytes(bytes:ByteArray,context:LoaderContext=null):void {
//uncompress if compressed

bytes.endian=Endian.LITTLE_ENDIAN;
trace("loadBytes" + bytes[0] );
if (bytes[0] == 0x43) {
//trace(" hackA");
//many thanks for be-interactive.org
var compressedBytes:ByteArray=new ByteArray() ;
compressedBytes.writeBytes(bytes,8);
compressedBytes.uncompress();

bytes.length=8;
bytes.position=8;
bytes.writeBytes(compressedBytes);
compressedBytes.length=0;

//flag uncompressed
bytes[0]=0x46;
}
hackBytes(bytes);
super.loadBytes(bytes,context);
}
//if bytes are AVM1 movie, hack it!
private function hackBytes(bytes:ByteArray):void {
//trace(" hackB");
if (bytes[4] < 0x09) {
trace("hack 9");
bytes[4]=0x09;
}
//trace(" hackC");
//dirty dirty
var imax:int=Math.min(bytes.length,100);
for (var i:int=23; i < imax; i++) {
if (bytes[i - 2] == 0x44 && bytes[i - 1] == 0x11) {
bytes[i]=bytes[i] | 0x08;
return;
}
}
}
}
}

TEST CODE

import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.display.SimpleButton;
import AVM2Loader;

var avm1:AVM1Movie;

function testAudio(e:Event = null):void {
trace("testAudio");
//var request:URLRequest = new URLRequest("video.swf");
var request:URLRequest = new URLRequest("Flash8Movie.swf");
var ldr:Loader;
if (true) {
trace("start AVM2Loader");
ldr = new AVM2Loader();
} else {
ldr = new Loader();
}
ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, _onLoaderComplete);
ldr.load(request);
}

function _onLoaderComplete(event:Event):void {
trace(" _onLoaderComplete");
var info:LoaderInfo = event.target as LoaderInfo;
trace(" _onLoaderComplete " + info.content);
if (info.content is AVM1Movie) {
avm1 = AVM1Movie(info.content);
} else {
addChild(info.content);
MovieClip(info.content).stop();
var dO:DisplayObject = DisplayObject(info.content);
}

}
stage.addEventListener(MouseEvent.CLICK, testAudio);
testAudio();

UI: reusing flash.display.Loader and non-visible preloading

This covers how to preload images then scale/center appropriately prior to showing them.

Going Old Skool doing it Flash8 Style
In Flash8 preloading assets required all sorts of trickery to load a clip prior to resizing it, centering it and then displaying it. There were two techniques

1) loading the clip offstate

2) putting it into a nested clips, one was a container, the other a sacrificial clip. Any changes to realClip would get blown away as the new image/clip gets loaded in.

  • PictureThumbnail //200x300 normally scaled down to 100x100
    • loadTarget_mc
      • realClip_mc

so we would do something like

realClip_mc.loadMovie("image.jpg"); //400x500

loadTarget_mc._visible = false;

In addition we'd have to do some juggling of the scale/centering of realClip to match that of the PictureThumbnail.
Flash 9 suave.
Thankfully with flash.display.Loader, and the display list there is the capability to avoid jumping through these hoops.

One of the major changes for me was wrapping my head around the flash.display.Loader object and it's relation to the display list. The idea of adding a Loader to the display list as in the Adobe example, is similar the old school approach of putting it in undesired containers, we'd really prefer getting PictureFrame.image.jpg Getting at the loaded clip is even more difficult: loader.contentLoaderInfo.content ! In addition the need to addListeners to the

configureListeners(_loader.contentLoaderInfo);

instead of the loader object directly was non-intuitive.

The nice thing is with the DisplayList there is a whole world of nonrendered visual behavior. So we can Load our clips independent, then manipulate them prior to adding them to the display list exactly where we want/expect. And then proceed to reuse the Loader for other assets (as in a sequential preloader). Here's an example, in where we can reuse the loader, and then when it's complete match it's size to an onstage Flash IDE drawn placeholder.

Local Shared Objects…not so Shared. Cross Domain Projector Issues.

We have a a few Desktop Flash applications: One is a main application, the other an update utility for the main app. They use a Flash Cookie to find each other, as the main app could be installed anywhere (USB, C), so it writes to the cookie where it's installed at. The apps are based in Director using the Flash Xtra.

Say we start with a completely empty SharedObjects folder

Running the main app sets a cookie into a folder like

....Flash Player\#SharedObjects\BFYLPV7P\localhost\02000.sol

via a call like

var so : SharedObject = SharedObject.getLocal ("02000", "/");

where "/" should see the root of the Flash Cookies.

then say Pandora (or any other flash based website using cookies) runs it generates a new sandbox

....Flash Player\#SharedObjects\2S7GJDMQ\pandora.com

and then all future read requests are against that new '2S7GJDMQ' folder instead of the "BFYLPV7P" folder, and then neither the app or the updater, can see the old shared local object folder anymore. Which in the case of it containing lots of persistent configuration data, is the equivalent of clearing the Browser cache and forgetting everything.

Trying to understand how it's created, I can delete the contents of the shared Objects folder and then republish in the Flash IDE and get a new hash for the folder every time so it seems to be either random or time based. It's not as far as I can tell the behaviour documented, it's certainly not behaving as I would expect.

As mentioned here. It used to be that one could work around this via:
SharedObject.getRemote("my_so", "rtmp:/./..", "/");

sadly it no longer works in Flash 8, or 9.

I got this reply on the FlashCoders list:

>Movies running in different sandboxes cannot access shared objects of
>each other.

Yes, that makes sense, except for 2 things:

1) As far as I can tell, they should NOT be in separate sandboxes, they
use the exact same code, and are installed in next to the same folder. As far
as I can tell there is no way to specify the domain for a projector to get the
desired behavior.

2) It's also inconsistent. First run it WILL use the same sandbox as the Flash IDE/etc, then later something will trigger the generation of a new sandbox (install a second version of the app, or view pandora) then all further requests go against the new sandbox instead of the original. Meaning the Flash Player changes it's mind about which domain it thinks it belong to!

My best guess, likely due to it running in a projector without access to URL information, it's making stuff up.

Our first attempt at solving, required upgrading the Flash Xtra in both projectors to Flash8, apparently having one at Flash7 and the other Flash8 is enough to trigger the domain.

Also interesting is the Flash IDE never really seems to have this problem, presumably because it is using the Flash Player EXE as the handler for all swfs via the registry. So one potential workaround was firing a Flash Projector with the sole point in life to read/write the local Shared Object, with local connection object to read how flash sees it and then shut down.

Flash on the Desktop…everywhere. Flash CS3 supports AIR output

This week has been week of Desktop Based Flash. 1) Using Director + the Buddy API Xtra, 2) a client with a C++ and F-in-Box, and just finishing up save capability 3) AIR Local File Access for Flash
Testing applications inside of these projectors can really slow down application development as it's compile in flash, and then test in the projector.

Still I'm quite stoked to see that Flash IDE has an update to support building AIR apps in the IDE. This rocks for several reasons as many apps I make are based off XML configuration or could benefit from making tools to help generate the data.  Here is a good post on actually using the update to create a hello world example.

This is after spending a week battling with bizarre Shared Object behavior. Memo to self, don't depend on SharedObjects for reliable desktop storage!

Serialization: IExernalizable and Evolving Classes

What is evolving a class? It's the inevitable:

  • addition of a field
  • change the superclass
  • remove a field
  • change the name of a field
  • change a field to static (from non-static)
  • change a field to transient (from non-transient)
  • change the type of a field type (e.g. int to float)

As soon as you do any of the above whatever you've previously saved something to disk, be it local or remote will break upon loading back in.

This isn't common in most web based flash development which lives in isolation on a webpage and doesn't save anything, but introduce any sort of saving. With Desktop Flash apps (AIR, Zinc), and RIA's, offline online applications, it IS becoming something that is important.
There are 3 different strategies to serializing objects, only one is particularly useful for evolving classes.

  1. Direct: writeObject(yourClassObject);
  2. Customize with IExternalizeable: iterate over each attribute of your object and write each of those to the stream.
  3. Customize with IExternalizeable, iterate over each attribute of your object write it with a key to a Dictionary / HashTable.

Approach 1 is great for simple uses and first passes. However it doesn't scale well or at all. Approach 2 Is better but as the stream is sequential, this can make dealing with multiple versions difficult or impossible as you have to know the exact order in which to parse. Approach 3 is the best, it uses a Dictionary as an intermediate map, it's only a bit more code to write, and is a *whole* lot easier to parse. This is also the approach that Remoting uses when talking to the server with the ASObject, and in someways writing to disk is identical to writing to the filesystem.

Here is the list of imports:

  1.  
  2. import flash.utils.Dictionary;
  3. import reflection.introspectable;
  4. import flash.utils.IExternalizable;
  5. import flash.utils.IDataInput;
  6. import flash.utils.IDataOutput;
  7. import flash.net.registerClassAlias;

Here is the version number, we'd need to increase everytime a significant data change has happened, and some of the static register class necessary to get the class to serialize and deserialize correctly:

  1.  
  2. public static const serialVersionUID:Number = 01;
  3. private static const REG_DICT:* = registerClassAlias("flash.utils.Dictionary",Dictionary);
  4. private static const REG:* = registerClassAlias("reflection.IntrospectableObj",IntrospectableObj);
  5. [RemoteClass(alias="reflection.IntrospectableObj")]
  6.  

Here are the modified IExternalizable functions. Note that the read from the stream has to deal with all permutations for a given version to version, as this number of changes gets large, the normalization can be quite large, thankfully good architecture up front can prevent much of this.

  1.  
  2. ///////////////////////////////////////////////
  3. public function readExternal(input:IDataInput):void
  4. {
  5. var d:Dictionary = input.readObject();
  6. var serialVersionUID:Number =    d["serialVersionUID"] as Number ;
  7. trace("readExternal " + serialVersionUID);
  8. /////////////// COMMON TO ALL VERSIONS ///////////////////////////
  9. if(serialVersionUID == 1 || serialVersionUID == 2 || serialVersionUID == 3){
  10. introspectable::introVarStr = d["introVarStr"]as String;
  11. defVarStr  = d["defVarStr"]as String;
  12.  
  13. pubByteArray =d["pubByteArray"] as ByteArray;
  14. pubBitmapData=    (d["pubBitmapData"] as BitmapDataWrapper).getBitMapData();
  15. }
  16. /////////////// COMMON TO TWO VERSIONS //////////////////////
  17. if(serialVersionUID == 1 || serialVersionUID == 2){
  18. }
  19.  
  20. if(serialVersionUID == 2 || serialVersionUID == 3){
  21.  
  22. }
  23. if(serialVersionUID == 1 || serialVersionUID == 3){
  24.  
  25. }
  26. ///////////// COMMON TO ONE VERSION /////////////////////////
  27. if(serialVersionUID == 1 ){
  28.  
  29. }else if(serialVersionUID == 2 ){
  30. }else if(serialVersionUID == 3 ){
  31. }
  32.  
  33. }
  34.  
  35. public function writeExternal(output:IDataOutput):void
  36. {
  37. trace("writeExternal....");
  38. var d:Dictionary = new Dictionary();
  39. d["serialVersionUID"] = serialVersionUID;
  40.  
  41. // name spaced var
  42. d["introVarStr"] = introspectable::introVarStr;
  43. d["defVarStr"] = defVarStr;
  44.  
  45. // this has already packed data
  46. d["pubByteArray"] = pubByteArray;
  47.  
  48. // since BitmapData can't be retrieved, we've created a wrapper that can be.
  49. d["pubBitmapData"] = new BitmapDataWrapper(pubBitmapData);
  50. output.writeObject(d);
  51. }
« Previous Entries Next Entries »